Como eu tentei consertar uma pesquisa no mapa por drivers. Parte 2

A primeira coisa que quero dizer é que foi difícil. Muito mais difícil do que eu pensava. Eu já tive essa experiência muito difícil de trazer produtos para lançamento no trabalho, mas nunca alcancei projetos pessoais. Todos eles terminaram com protótipos de diferentes graus de nojo, mas este parecia sobreviver. No momento, ele foi lançado para mais de 80 países (toda a Europa, Ásia e América do Norte), em ambas as plataformas móveis, e no final do artigo haverá links para download - por isso, convido todos os interessados ​​a tentar, quebrar e repreender.

Aqui está uma breve reflexão com a qual tudo começou: Na minha opinião, é feita uma pesquisa nos mapas móveis existentes para pedestres e não funciona para os motoristas. Você precisa parar, mergulhar nos mapas repletos de informações e publicidade em excesso, cutucar pequenos ícones. É inconveniente, não o ajudará em um lugar desconhecido; no final, é apenas perigoso. É necessária uma solução intuitiva e limpa, que não distraia e não faça você diminuir a velocidade.

Na primeira parte, descrevi meu caminho desse pensamento simples para uma solução funcional e depois descreverei como arrastei essa solução para o lançamento.

Para economizar seu tempo, começarei com uma breve recontagem da parte anterior: lá escrevo que, em vez de pesquisar, decidi usar a digitalização em movimento, e a interface do aplicativo foi simplificada o máximo possível. Em vez de uma linha de entrada absurda para o motorista, ele adicionou vários botões grandes para coisas que podem ser úteis na estrada: posto de gasolina, cobrança, caixa eletrônico, estacionamento, farmácia. Em vez de um mapa, fiz uma lista e, quando seleciono um resultado, a navegação pelo Apple / Google Maps é aberta. Para o aplicativo, decidi usar o Flutter (ao mesmo tempo em que soube que tipo de animal é), peguei os dados do OpenStreetMap. Terminei minha história com o fato de que um protótipo mais ou menos sadio estava pronto.

Depois, tudo levou cerca de 4-5 meses, depois as mudanças na vida começaram e o projeto foi esquecido - e eu comecei a me cansar disso. Um mês depois, limpei o pó, refresquei-o na cabeça, escrevendo um artigo em um hub e decidi: vamos terminar. Quem sabe a diferença entre um protótipo e um produto sorrirá tristemente para este lugar.

O que aconteceu depois levou mais quatro meses. Eu consegui um rastreador de tarefas, em termos gerais, formou uma lista de problemas, coletou dispositivos para teste. À noite e nos fins de semana, me sentava no trabalho e avançava o projeto. Em que momentos parecia que a lista estava apenas crescendo, e eu estava me afogando em infinitas nuances e retoques finais. Ele se pegou na mão, jogou algo fora, em algum lugar levou ao perfeccionismo. A seguir, tentarei falar sobre os pontos mais interessantes no desenvolvimento de um projeto aparentemente tão simples.

Tecnologia


Arquitetura geral


Em algum lugar no meio do trabalho, começou a parecer que a arquitetura estava se espalhando fora de controle. Muitos componentes e conexões foram encerrados; vários gargalos foram agarrados. Felizmente, o projeto é pequeno, e senti-o a tempo, portanto, colocar as coisas em ordem não foi difícil. No nível de componentes individuais, isso se resumia à refatoração e eliminação de bibliotecas desnecessárias; no nível global, eu espalhei a funcionalidade para três pequenos servidores iniciados no DigitalOcean .

  1. Servidor API (Python) - a principal configuração de servidor, nos voltamos para ele. Não há muita lógica, principalmente a formação de resultados para saída. O mais econômico em termos de recursos.
  2. Servidor elástico (Java) - O Elasticsearch e o Photon (geocoder de código aberto) estão girando nele. Eles usam o mesmo índice para o qual todo o planeta é importado do OpenStreetMap. Funções do servidor: procure locais por aterro e geocoder. Por sua natureza, o elástico é muito rápido e leve, portanto o servidor também não é muito oleoso.
  3. Um servidor geográfico (Nó) é o mais difícil de todos. Com base na Open Source Routing Machine, escrevi uma pequena API, e suas tarefas incluem todos os cálculos geográficos: definir rotas, calcular isócrones e gerar blocos. Cada operação individual não é muito engenhosa, mas são necessárias dezenas para qualquer pesquisa, e isso se torna um gargalo. No momento, neste servidor, 16 GB de RAM e, em geral, tudo funciona em uma fração de segundo - exceto para gerar blocos. Quando houver muitos deles na fila, você poderá esperar por fotos com cartões em alguns segundos. Felizmente, eles aparecem no cliente de forma assíncrona, e isso não estraga muito a imagem geral (espero).

Além disso, decidi alimentar extratos do OpenStreetMap para geo-cálculos separadamente por país. Funciona assim: fazemos a primeira solicitação com coordenadas para o nosso geocoder, determina o país e, em seguida, pegamos os arquivos baixados somente deste país para as manipulações de que precisamos. Isso é necessário, porque mesmo o meu servidor bastante poderoso não é capaz de converter a extração maior que dois gigabytes de tamanho - o processo consome rapidamente toda a memória e engasga. Felizmente, quase todos os países se enquadram nesse limite, exceto os Estados Unidos: esse monstro teve que ser dividido em estados. Por fim, para manter uma centena e meia de extratos, decidi escrever vários scripts que verificam sua saúde, reparo e atualização.



Em geral, tudo isso parece mais complicado do que na prática. Em princípio, estou satisfeito com a solução resultante - ela tem uma boa reserva para escalar pelo número de territórios suportados e pelo nível de carga.

Isócrono dinâmico


Durante muito tempo, um dos principais problemas para mim foi a densidade heterogênea dos resultados. A razão é bastante compreensível - essa é a densidade heterogênea da própria rede de estradas e dos prédios nela. Em uma cidade de tamanho médio, dentro de um raio de 5 minutos, pode haver de 2 a 3 caixas eletrônicos, e agora vamos para o centro da metrópole - e pelos mesmos 5 minutos, pode haver 20 ou 30 resultados. Finalmente, pulamos no campo e observamos quase 0 resultados garantidos até nos aproximarmos da cidade e o raio de busca capturar alguma coisa.

Esse problema fornece uma carga não linear e imprevisível no servidor, e o mais importante - uma experiência bastante ruim para o usuário. Adicionar um filtro às opções (5 minutos, 10 minutos, 30 minutos) basicamente não resolve nada. Na vila, mesmo um raio de 30 minutos pode não retornar nada, mas em uma megalópole, mesmo 5 minutos irão sobrecarregá-lo com resultados. Além disso, adicionamos funcionalidade extra aos botões nos quais o driver deve estar em movimento. Em geral, bobagem, você precisa de uma solução fundamentalmente diferente.

Quando uma solução foi encontrada, ela se mostrou muito simples. Em vez de seguir o oposto e solicitar ao usuário que selecione um raio de pesquisa, podemos torná-lo automático. A lógica é realmente elementar:

  1. Você define limites para os resultados - por exemplo, pelo menos 1 e não mais que 20 - e começa com 10 minutos
  2. Faça uma pesquisa por local. Até agora, não precisamos obter instruções para eles, portanto, apenas precisamos calcular a isócrona e filtrar por polígono no elástico - ambas as operações são muito baratas
  3. Se o número de resultados subir em uma direção a partir dos limites (no nosso caso, 0 ou 20+), divida ou multiplique o tempo por 2 e faça a pesquisa novamente. Se estiver incluído no limite, já construímos rotas, classificamos por tempo, etc.

De fato, isso é um pouco mais complicado e existem algumas nuances, por exemplo, uma grade de cidade ultra-densa, quando já reduzimos o tempo ao mínimo e ainda há muitos resultados. Aqui já é necessário classificar e, portanto, estabelecer rotas, o que é um pouco caro. No entanto, esses são casos extremos e não são muito impressionantes.

Na realidade, é improvável que uma pessoa role a lista abaixo de 5 a 6 posições, portanto, em 95% dos cenários, o isócrono dinâmico resolveu o problema. Removemos o gargalo - uma quantidade imprevisível de resultados - e reduzimos a carga no servidor geográfico para qualquer solicitação. Verificar isso é muito fácil:

Caminho antigo: pegue um raio de 10 minutos e 30 resultados
Resultado: 1 pedido de isócrono + 30 pedidos de rotas = 31

Nova maneira: verifique, 30 resultados são muitos, divida o raio ao meio, agora obtemos 10 resultados
Resultado: 2 solicitações de isócrona + 10 solicitações de rotas = 12



Nova lógica de mapa


Na última parte, descrevi o mecanismo para gerar cartões com rotas definidas. Acabou sendo bastante complicado e caro em termos de computação, mas eu gostei tanto deles que decidi deixá-los. Ao mesmo tempo, entendi que, na forma atual, eles tinham poucos benefícios práticos - não estava claro para eles para onde você estava indo, e todos estavam virados para o norte. Era necessário refinar.

A primeira coisa que decidi fazer foi implantar os mapas em tempo real usando uma bússola. No flutter, isso foi descrito pela lógica microscópica e funcionou muito rapidamente, no entanto, com mais de 10 resultados que giram constantemente, o desempenho começou a diminuir. Além disso, parecia absolutamente doentio: de fato, as imagens estáticas estavam girando, e isso foi mais confuso durante a viagem do que de alguma forma ajudou.

A idéia seguinte foi indicar nos mapas a direção do movimento da seta. Era muito simples - eu já tinha um vetor calculado e só precisava gerar uma forma geométrica da seta. Ao mesmo tempo, em uma posição estática, os cartões continuavam mostrando a posição do motorista com um marcador redondo. Havia uma ressalva - era necessário normalizar os tamanhos dos marcadores e setas para diferentes níveis de zoom. Esta parece ser uma tarefa simples, mas fiquei presa por um longo tempo. A questão era a seguinte: gerei todos os símbolos no mapa em metros e tomei como base a fração da altura do mapa inteiro em metros. Descobriu-se que durante a criação de mapas - determinando caixas delimitadoras quadradas, colando e aparando peças sobre elas, etc. - erros acumulados, e esses pequenos erros acabaram levando a tamanhos muito diferentes de marcadores. Especialmente infernal foi a situação com cartões de pequena escala. Não vou entrar em detalhes da solução, no entanto, por causa desses erros, a lógica da geração de cartões precisou ser completamente redesenhada. O Turf ajudou muito com isso - um ótimo conjunto de ferramentas para manipular dados geográficos.

Com as flechas, as cartas já eram mais úteis, mas ainda faltava alguma coisa. Após os testes ao vivo, ficou claro que todas as cartas foram viradas para o norte. Na estatística, isso não era impressionante, mas imediatamente se tornou aparente quando você fica atrás do volante. O motorista espera subconscientemente que a seta esteja sempre apontando para cima ao dirigir. Tendo descoberto isso, sentei-me novamente para trabalhar. Essa foi novamente uma daquelas tarefas que parecem muito simples, mas você passará alguns dias depois. Parece - calcule o azimute e gire o GeoJSON final antes da rasterização. Mas havia novamente uma nuance - esse GeoJSON final foi gerado por uma caixa delimitadora direta e, sendo girado e recortado nela, ele detecta lugares vazios.



No diagrama acima, dei aproximadamente a minha solução. Como resultado, acabou não sendo muito caro em termos de recursos e cobrindo 99% dos cenários (acho que os erros subirão em algum lugar perto dos polos). Em geral, o servidor de geo-computação ainda é a parte que consome mais recursos do projeto, mas agora suas cartas de rota, além da estética, também são muito práticas. Até tentei chegar ao local usando esses cartões exclusivamente, sem incluir a navegação. E chegou mesmo.



Qualidade dos dados


Peguei todos os meus dados de maneiras diferentes do OpenStreetMap. Como você sabe, esse recurso é 100% sem fins lucrativos e é suportado por uma mente coletiva. Isso é um plus (é gratuito e com uma estrutura clara), também é um sinal de menos - os dados são muito heterogêneos.

Em um nível alto, isso significa uma cobertura desigual do globo como um todo: em países e cidades com um grande número de entusiastas, cada gramado e caminho são descritos e em outros lugares existem zonas quase vazias com objetos básicos de esboço. Os dados são atualizados com exatamente a mesma irregularidade. Ao testar meu aplicativo, me deparei com algumas vezes novos postos de gasolina, cafés e, às vezes, estradas inteiras que ainda não consegui colocar nos mapas. Reclamar é estúpido: o mesmo Google gasta orçamentos astronômicos e contém toda uma equipe de carros responsáveis ​​pela relevância de seus dados. Portanto, aqui o melhor que podemos fazer é sincronizar com mais frequência com as extrações do OpenStreetMap. Bem, deseje boa sorte à sua comunidade.

Porém, em um nível mais baixo, devido à edição caótica de mapas, há vários outros problemas que podem ser completamente resolvidos. Isso diz respeito principalmente a dados indesejados e duplicatas. A variedade dessa bagunça é impressionante: o mesmo local pode ser descrito três vezes de maneiras diferentes, as instituições não têm nomes, os tipos e as tags são colocados incorretamente e assim por diante. Tudo isso não possui uma solução unificada; é necessário um complexo de medidas para sistematizar o conteúdo. Por exemplo, eu tenho as seguintes condições:

Existem vários sinônimos e variações da mesma tag -> descrevemos dicionários de alias (por exemplo, parking, parking_space, parking_entrance, etc.).

Existem vários lugares com o mesmo tipo e as mesmas coordenadas:

  • se todo mundo não tem nome -> o tipo de lugar se torna o nome
  • apenas um tem um nome -> aceite
  • todo mundo tem um nome e eles são diferentes -> pegue o sobrenome cronologicamente

Existem vários lugares com o mesmo tipo e quase as mesmas coordenadas:

  • se todo mundo não tiver nome -> provavelmente duplicados, não complicaremos. Mesclar em um ponto com coordenadas médias, em que o tipo de lugar se torna o nome. Um homem virá e entenderá
  • apenas um tem um nome -> a mesma coisa, só que agora já temos um nome
  • todo mundo tem um nome e eles são diferentes -> mas este é um cluster

O cluster, no nosso caso, é uma placa no cabeçalho da qual vários locais são descritos. Na maioria das vezes, existem grupos de lojas ou postos de gasolina nas proximidades. Ou, por exemplo, um caixa eletrônico está localizado dentro do prédio do banco e o outro fora. Eles não representam nenhuma dificuldade para nós: calculamos as coordenadas médias e traçamos o caminho até elas. Na interface, mostramos isso de forma limpa e simples:



Interface e Design


Então aconteceu comigo que, antes do início do desenvolvimento, eu já imagino a imagem final. Ao mesmo tempo, não gosto de desenhar diagramas e conceitos, preferindo formar um design em paralelo com funções (se esse for o meu projeto, é claro). Essa abordagem iterativa é muito legal, por um lado, porque permite alternar entre recursos visuais e código, por outro lado - às vezes você precisa refazer tudo com muita frequência. O mesmo aconteceu aqui: pareço ter escavado a interface mais simples cem vezes. Começando com ninharias como ícones e recuos, terminando com a composição de cartões, menus, etc. Não vou descrever tudo, vou passar rapidamente por questões-chave. Se você não está interessado em design, fique à vontade para ignorá-lo.

Paleta de cores


Durante muito tempo, não entendi o que fazer com a paleta. Eu realmente queria designar as categorias de lugares em cores diferentes, com exceção do verde - decidi salvá-lo como um sotaque. Eu escolhi cores facilmente distinguíveis e ricas, tudo parece estar bem. Depois de algum tempo, descobri que o azul do posto de gasolina ecoa o azul, o que no mapa indica a posição do motorista. Ele não fez nada com isso, deixou como está - mas o perfeccionista interno está insatisfeito.



"A caminho" e "Você está perto"


Depois que surgiu a lógica que determina a direção do movimento do motorista, tornou-se possível dividir as rotas em "ao longo do caminho" e no resto. Como eu já disse, isso é determinado pelo primeiro segmento da rota estabelecida no local: coincide com o último segmento da rota do motorista. Se sim, então já estamos indo para este lugar. Surgiu então a questão de como mostrar isso na interface. Além das mudanças no mapa que descrevi acima, surgiu a idéia da placa “A caminho” (ou “A caminho” em inglês - parece que eles significam a mesma coisa). Eu reuso o mesmo dado para outro cenário: quando a distância até o local encontrado é inferior a 25 metros. Então não faz sentido obter instruções, oculto o mapa e escrevo que você já está perto ("Você está perto" / "Olhe ao seu redor").



Cartão comunitário


No início do desenvolvimento para depuração, usei um mapa estático do Google para ver o isócrono e os resultados. Então ele correu com ela por um longo tempo, sem saber onde ficar: parece que o mapa é uma coisa interessante, mas parece que não deve ocupar um lugar. Além disso, dolorosamente nem queria depender do Google dessa maneira. Então, no final, removi o mapa, mas depois de algum tempo comecei a gerar cartões de rota e percebi que eu havia tecnologicamente “crescido” para o grande mapa. Verificou-se que não era tão difícil fazer isso, embora até agora o mapa comum continue sendo a parte que mais consome recursos em todo o projeto. E, para não ocupar espaço na interface, coloquei o cartão em uma página separada (eles o puxam com menos frequência).



Localização


Para uma saída normal na produção, a localização é necessária. É sempre, por um lado, um trabalho muito direto e simples, por outro - quando você começa a fazê-lo, multidões de baratas se arrastam de todos os lugares. No meu caso, o conteúdo principal do OSM já estava localizado, de modo que apenas os tipos de lugar e os elementos da interface permaneciam. Com exceção de alguns plugues (por um longo tempo, não pude formular um dado “ao longo do caminho”) tudo foi fácil. Vale ressaltar que os nomes dos locais podem ocupar 2 e 3 linhas e podem não caber em telas de largura pequena - portanto, o widget auto_size_text ajudou aqui, eu o recomendo dentro de limites razoáveis.



Mas no lado técnico não foi tão tranquilo. — Intl_translation , … . , . , (!) - , … , , .

, , — - . , , .


Na verdade, não há nada interessante a dizer sobre o lançamento em si. Havia suspeitas vagas de que a Apple se recusaria a usar o aplicativo, mas elas não se concretizaram - tudo correu bem. Redesenhei o ícone e desenhei uma página de introdução que atende ao usuário pela primeira vez. Eu tive que mexer um pouco com desenhos, mas parece que nada aconteceu. Nenhuma armadilha com permishens e sua localização também surgiram.

No último momento, a compilação da versão para Android foi interrompida devido à transição do suporte para o androidx: algumas bibliotecas que eu usei não o suportaram imediatamente. Como resultado, esperei apenas alguns dias, mas devemos prestar homenagem aos autores dessas bibliotecas - eles os consertaram muito rapidamente. No entanto, esse incidente me convenceu mais uma vez: ainda não vou iniciar um grande projeto comercial. Apesar de gostar dele, a história toda ainda é muito, muito crua.

Bem, como prometido, baixe os links:





Planos


E algumas palavras no final sobre planos. Se isso for útil para alguém que não seja eu e houver downloads, tenho muitas idéias para um maior desenvolvimento. Aqui está uma lista de amostra do que eu gostaria de incluir no release No. 2:

  • mais dados sobre locais (por exemplo, preços da gasolina ou tipos de cobranças para carros elétricos)
  • tema escuro para motoristas noturnos
  • modo compacto para ver mais resultados sem rolar (por exemplo, desative o minimapa, se desejar)
  • aceleração da cartografia (você realmente precisa pensar nesse gargalo - otimizar de alguma forma complicada ou transferir para a virtualização de contêiner)

Gostaria de apoiar ainda mais o Android Auto e o Apple CarPlay. Eu nunca fiz solicitações para eles, por isso estou curioso para tentar sozinho.

É tudo, obrigado a todos pela atenção.

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


All Articles