O 2GIS está ao seu alcance. Como adicionamos um mapa ao Apple Watch


O Apple Watch rapidamente ganhou popularidade e se tornou o relógio mais popular do mundo, à frente da Rolex e de outros fabricantes. A ideia de criar um aplicativo para relógios está no escritório 2GIS desde 2015.


Antes de nós, apenas a própria Apple lançou um aplicativo completo com um cartão no relógio. O aplicativo Yandex.Map exibe apenas os widgets de tráfego e o tempo de viagem para casa e trabalho. Yandex.Navigator, Google Maps, Waze e Maps.Me geralmente não estão disponíveis no relógio.


De fato, devido às muitas limitações do sistema e à complexidade do desenvolvimento, as empresas não criam aplicativos de observação, ou as tornam muito simples. Você não pode simplesmente pegar e desenhar um mapa no relógio. Mas nós poderíamos.


Dê uma espiada sob o gato para descobrir como o projeto do animal de estimação se transformou em um produto completo.


UPD.: Https://github.com/teanet/DemoWatch


Decidimos fazer um mapa. O que havia no começo?


  1. Experiência de desenvolvimento em serviço - 2 dias de trabalho em um projeto de teste.
  2. Experiência com SpriteKit - 0 dias.
  3. Experiência de escrita no MapKit - 0 dias.
  4. Duvida que algo possa dar errado - ∞.

Iteração 1 - Fuga do pensamento


Como somos pessoas sérias, decidimos, de início, elaborar um plano de trabalho. Levamos em conta que trabalhamos em um sprint bem planejado, temos cinco histórias para “tarefas de pequenos produtos” e completa ignorância sobre por onde começar.


Um mapa é uma imagem muito grande. Podemos mostrar fotos no relógio, o que significa que podemos lidar com a exibição do cartão.


Temos um serviço que pode cortar um cartão em pedaços:



Se você cortar essa imagem e colocar a WKImage, obteremos o protótipo de trabalho mais simples por cinco centavos.


E se você adicionar PanGesture a esta imagem e instalar uma nova imagem em cada golpe, obteremos uma simulação de interação com o cartão.


/ Alegra-se / Parece horrível, parece o mesmo, funciona ainda pior, mas na verdade a tarefa está concluída.


Iteração 2 - protótipo mínimo


Os downloads contínuos de imagens são caros para uma bateria em horas. Sim, e o próprio tempo de inicialização sofre. Queríamos obter algo mais completo e responsivo. Pelo canto do ouvido, ouvimos dizer que o relógio tem suporte para SpriteKit - a única estrutura para o WatchOS, com a capacidade de usar coordenadas, ampliar e personalizar todo esse esplendor por si mesmo.


Após algumas horas de StackOverflow Driven Development (SDD), obtemos a segunda iteração:
Um SKSpriteNode, um WKPanGestureRecognizer.



/ Alegra-se / Sim, este é o MapKit para 6 kopecks, totalmente funcionando. Liberação urgente!


Iteração 3 - Adicionando mosaicos e zoom


Quando as emoções dormiam, eles se perguntavam para onde ir a seguir.


Entendeu que a coisa mais importante:


  • Substitua a imagem por ladrilhos.
  • Anexe 4 blocos ao pacote de aplicativos e conecte-os.
  • Forneça imagens com zoom.
    Vamos colocar 4 peças no pacote de aplicativos e colocá-las em uma certa:

let rootNode = SKSpriteNode() 

com a ajuda da matemática simples, vamos conectá-los.
Ampliamos o zoom através do WKCrownDelegate:


 internal func crownDidRotate( _ crownSequencer: WKCrownSequencer?, rotationalDelta: Double ) { self.scale += CGFloat(rotationalDelta * 2) self.rootNode.setScale(self.scale) } 


/ Alegra-se / Bem, agora isso é certo! Algumas correções, e para o mestre.


Iteração 4 - otimizando a interação com o mapa


No dia seguinte, o SpriteKit anchorPoint não afetou o zoom. O zoom ignora completamente o anchorPoint e ocorre em relação ao centro do rootNode. Acontece que, para cada etapa do zoom, precisamos ajustar a posição.


Também seria bom carregar blocos do servidor, em vez de armazenar o mundo inteiro na memória do relógio.
Não esqueça que os blocos devem ser amarrados às coordenadas para que não fiquem no centro do SKScene, mas nos locais apropriados no mapa.


As peças são mais ou menos assim:



Cada zoomLevel (daqui em diante "z") tem seu próprio conjunto de peças. Para z = 1, temos 4 peças que compõem o mundo inteiro.



para z = 2 - para cobrir o mundo inteiro, você já precisa de 16 peças,
para z = 3-64 blocos.
para z = 18 ≈ 68 * 10 ^ 9 blocos.
Agora eles precisam ser colocados no mundo do SpriteKit.


O tamanho de um bloco é 256 * 256 pt, o que significa
para z = 1, o tamanho do "mundo" será 512 * 512 pt,
para z = 2, o tamanho do "mundo" será igual a 1024 * 1024 pt.
Para facilitar o cálculo, coloque blocos no mundo da seguinte maneira:



Codifique o bloco:


 let kTileLength: CGFloat = 256 struct TilePath { let x: Int let y: Int let z: Int } 

Defina a coordenada do bloco em um mundo assim:


 var position: CGPoint { let x = CGFloat(self.x) let y = CGFloat(self.y) let offset: CGFloat = pow(2, CGFloat(self.z - 1)) return CGPoint(x: kTileLength * ( -offset + x ), y: kTileLength * ( offset - y - 1 )) } var center: CGPoint { return self.position + CGPoint(x: kTileLength, y: kTileLength) * 0.5 } 

A localização é conveniente, pois permite trazer tudo para as coordenadas do mundo real: latitude / longitude = 0, que fica bem no centro do "mundo".


A latitude / longitude do mundo real é transformada em nosso mundo da seguinte maneira:


 extension CLLocationCoordinate2D { //     ( -1 < TileLocation < 1 ) func tileLocation() -> CGPoint { var siny = sin(self.latitude * .pi / 180) siny = min(max(siny, -1), 1) let y = CGFloat(log( ( 1 + siny ) / ( 1 - siny ))) return CGPoint( x: kTileLength * ( 0.5 + CGFloat(self.longitude) / 360 ), y: kTileLength * ( 0.5 - y / ( 4 * .pi ) ) ) } //       zoomLevel func location(for z: Int) -> CGPoint { let tile = self.tileLocation() let zoom: CGFloat = pow(2, CGFloat(z)) let offset = kTileLength * 0.5 return CGPoint( x: (tile.x - offset ) * zoom, y: (-tile.y + offset) * zoom ) } } 

Com o nivelamento do zoom, houve problemas. Eu tive que passar alguns dias de folga para montar todo o aparato matemático e garantir a perfeita fusão dos ladrilhos. Ou seja, o ladrilho para z = 1 deve idealmente entrar em quatro ladrilhos para z = 2 e vice-versa, os quatro ladrilhos para z = 2 devem entrar em um ladrilho para z = 1.



Além disso, foi necessário transformar o zoom linear em um exponencial, pois os zooms variam de 1 <= z <= 18, e o mapa é dimensionado como 2 ^ z.


O zoom suave é fornecido ajustando constantemente a posição dos ladrilhos. É importante que os ladrilhos sejam costurados exatamente no meio: ou seja, que o ladrilho de nível 1 entre em 4 ladrilhos de nível 2 com um zoom de 1,5.


O SpriteKit usa um flutuador sob o capô. Para z = 18, obtemos uma dispersão de coordenadas (-33 554 432/33 554 432) e a precisão da flutuação é de 7 bits. Na saída, temos um erro na região de 30 pt. Para evitar a ocorrência de "lacunas" entre as metades, colocamos o ladrilho visível o mais próximo possível do centro do SKScene.


/ Alegra-se / Após todos esses gestos, temos um protótipo pronto para teste.


Data de lançamento


Como o aplicativo realmente não tinha TK, encontramos alguns voluntários para realizar um pequeno teste. Eles não encontraram problemas específicos e decidiram rolar para o lado.


Após o lançamento, verificou-se que, no relógio da primeira série, o processador não tem tempo para desenhar o primeiro quadro do cartão em 10 segundos e cai por tempo limite. Inicialmente, tive que criar um cartão completamente vazio para caber em 10 segundos e depois carregar gradualmente o substrato. Primeiro, desenvolva todos os níveis do mapa - e depois carregue blocos para eles.


Demorou muito tempo para depurar a rede, configurar corretamente o cache e uma pequena Pegada de Memória, para que o WatchOS não tentasse matar nosso aplicativo pelo maior tempo possível.


Após criar o perfil do aplicativo, descobrimos que, em vez dos blocos usuais, é possível usar os blocos Retina, quase sem prejudicar o tempo de carregamento e o consumo de memória, e no novo lançamento eles já mudaram para eles.


Resultados e planos para o futuro


Já podemos exibir no mapa uma rota com manobras construídas no aplicativo principal. O recurso estará disponível em um dos próximos lançamentos.


O projeto, que inicialmente parecia impossível, provou ser extremamente útil para mim pessoalmente. Costumo usar o aplicativo para entender se é hora de descer na parada certa. Acredito que no inverno será ainda mais útil.


Ao mesmo tempo, ele estava mais uma vez convencido de que a complexidade do projeto, a fé de outras pessoas no sucesso da tarefa ou a disponibilidade de tempo livre no trabalho não eram tão importantes. O principal é o desejo de fazer um projeto e um movimento chato e gradual em direção à meta. Como resultado, temos um MapKit completo, que é quase ilimitado e funciona com 3 WatchOS. Ele pode ser modificado como você deseja, sem esperar pela Apple lançar a API apropriada para o desenvolvimento.


PS Para os interessados, posso apresentar um projeto finalizado. O nível do código está longe de ser produzido. Mas, de acordo com o princípio militar, não importa como funciona, o principal é que funciona!

Source: https://habr.com/ru/post/pt422935/


All Articles