Cómo intenté arreglar un mapa de búsqueda de controladores. Parte 2

Lo primero que quiero decir es que fue difícil. Mucho más difícil de lo que pensaba. Tenía antes esta experiencia tan dura en traer productos para lanzar en el trabajo, pero nunca busqué proyectos personales. Todos terminaron con prototipos de diversos grados de disgusto, pero este parecía sobrevivir. Por el momento, se ha lanzado para más de 80 países (toda Europa, Asia y América del Norte), en ambas plataformas móviles, y al final del artículo habrá enlaces de descarga, por lo tanto, invito a todos los interesados ​​a probar, romper y regañar.

Aquí hay un breve pensamiento con el que comenzó todo: en mi opinión, una búsqueda en los mapas móviles existentes se realiza para peatones y no funciona para los conductores en absoluto. Debe detenerse, profundizar en los mapas llenos de exceso de información y publicidad, hurgar en pequeños iconos. Es inconveniente, no te ayudará en un lugar desconocido, al final es peligroso. Se necesita una solución intuitiva y limpia que no distraiga y que no disminuya la velocidad.

En la primera parte, describí mi camino de este simple pensamiento a una solución de trabajo, y luego describiré cómo arrastré esta solución al lanzamiento.

Para ahorrar tiempo, comenzaré con un breve recuento de la parte anterior: allí escribo que, en lugar de buscar, decidí usar el escaneo en movimiento, y la interfaz de la aplicación se simplificó lo más posible. En lugar de una línea de entrada absurda para el conductor, agregó varios botones grandes para cosas que pueden ser útiles en la carretera: gasolinera, carga, cajero automático, estacionamiento, farmacia. En lugar de un mapa, hice una lista, y cuando selecciono un resultado, se abre la navegación a través de Apple / Google Maps. Para la aplicación, decidí usar Flutter (al mismo tiempo que supe qué tipo de animal es), tomé los datos de OpenStreetMap. Terminé mi historia sobre el hecho de que un prototipo más o menos cuerdo estaba listo.

Luego todo tomó alrededor de 4 a 5 meses, luego comenzaron los cambios en la vida y el proyecto quedó en el camino, y comencé a cansarme de ello. Un mes después, lo desempolvé, lo actualicé en mi cabeza escribiendo un artículo en un centro y decidí: terminemos. Cualquiera que sepa la diferencia entre un prototipo y un producto sonreirá tristemente en este lugar.

Lo que sucedió después tomó otros cuatro meses. Me conseguí un rastreador de tareas, en términos generales formé una lista de problemas, reuní dispositivos para probar. Por las tardes y los fines de semana, me sentaba en el trabajo y avanzaba el proyecto. En qué momentos parecía que la lista solo estaba creciendo, y me estaba ahogando en infinitos matices y toques finales. Se tomó de la mano, tiró algo, por el contrario condujo al perfeccionismo. A continuación, trataré de hablar sobre los puntos más interesantes en el desarrollo de un proyecto aparentemente tan simple.

Tecnología


Arquitectura general


En algún lugar en el medio del trabajo, comenzó a aparecer una sensación de que la arquitectura se estaba extendiendo fuera de control. Se han enrollado demasiados componentes y conexiones; se han tocado varios cuellos de botella. Afortunadamente, el proyecto es pequeño y lo sentí a tiempo, por lo que poner las cosas en orden no fue difícil. A nivel de componentes individuales, esto se redujo a refactorizar y desechar bibliotecas innecesarias; a nivel global, extendí la funcionalidad a 3 pequeños servidores que comencé en DigitalOcean .

  1. Servidor API (Python): el servidor principal, por el que recurrimos. No hay mucha lógica, principalmente la formación de resultados para la producción. El más económico en términos de recursos.
  2. Servidor Elastic (Java): Elasticsearch y Photon (geocodificador de código abierto) están girando sobre él. Usan el mismo índice al que se importa todo el planeta desde OpenStreetMap. Funciones del servidor: búsqueda de lugares por vertedero y geocodificador. Por su naturaleza, el elástico es muy rápido y ligero, por lo que el servidor tampoco es muy grasiento.
  3. Un servidor geográfico (Nodo) es el más difícil de todos. Basado en la máquina de enrutamiento de código abierto, escribí una pequeña API, y sus tareas incluyen todos los cálculos geográficos: establecer rutas, calcular isócronas y generar mosaicos. Cada operación individual no es muy ingeniosa, pero se necesitan docenas de ellas para cualquier búsqueda, y esto se convierte en un cuello de botella. Por el momento, en este servidor, 16 GB de RAM y, en general, todo funciona en una fracción de segundo, excepto para generar mosaicos. Cuando hay muchos de ellos en la cola, puede esperar imágenes con tarjetas en unos segundos. Afortunadamente, aparecen en el cliente de forma asincrónica, y esto no estropea en gran medida la imagen general (espero).

Además, decidí alimentar extractos de OpenStreetMap para realizar geocálculos por separado por país. Funciona así: hacemos la primera solicitud con coordenadas a nuestro geocodificador, determina el país y luego tomamos los archivos descargados solo de este país para las manipulaciones que necesitamos. Esto es necesario, porque incluso mi servidor bastante potente no puede convertir el extracto con un tamaño superior a dos gigabytes; el proceso consume rápidamente toda la memoria y las obstrucciones. Afortunadamente, casi todos los países se ajustan a este límite, excepto Estados Unidos: este monstruo tuvo que dividirse en estados. Finalmente, para mantener mil quinientos extractos, decidí escribir un montón de scripts que verificaran su estado, reparación y actualización.



En general, todo esto suena más complicado de lo que funciona en la práctica. En principio, estoy satisfecho con la solución resultante: tiene una buena reserva para escalar según el número de territorios admitidos y el nivel de carga.

Isochron dinámico


Durante mucho tiempo, uno de los problemas clave para mí fue la densidad heterogénea de resultados. La razón es bastante comprensible: esta es la densidad heterogénea de la red de carreteras en sí y de los edificios en ella. En una ciudad mediana dentro de un radio de 5 minutos, puede haber 2-3 cajeros automáticos, y ahora nos trasladaremos al centro de la metrópoli, y durante los mismos 5 minutos puede haber 20 o 30 resultados. Finalmente, saltamos al campo y observamos un resultado 0 casi garantizado hasta que nos acercamos a la ciudad y el radio de búsqueda captura algo.

Este problema genera una carga no lineal e impredecible en el servidor y, lo más importante, una experiencia bastante desagradable para el usuario. Agregar un filtro a las opciones (5 minutos, 10 minutos, 30 minutos) básicamente no resuelve nada. En el pueblo, incluso un radio de 30 minutos puede no devolver nada, pero en una megalópolis, incluso 5 minutos lo abrumarán con resultados. Además, agregamos una funcionalidad adicional, en los botones de los cuales el conductor debe estar en movimiento. En general, sin sentido, necesita una solución fundamentalmente diferente.

Cuando se encontró una solución, resultó ser muy simple. En lugar de pasar de lo contrario y pedirle al usuario que seleccione un radio de búsqueda, podemos hacer que este radio sea automático. La lógica es realmente elemental:

  1. Establece límites en los resultados, por ejemplo, al menos 1 y no más de 20, y comienza con 10 minutos
  2. Haz una búsqueda por lugar. Hasta ahora, no necesitamos obtener indicaciones para llegar a ellos, así que solo necesitamos calcular la isócrona y filtrar por polígono en el elástico; ambas operaciones son muy baratas
  3. Si el número de resultados se arrastra en una dirección desde los límites (en nuestro caso 0 o 20+), divida o multiplique el tiempo por 2 y vuelva a buscar. Si está incluido en el límite, entonces ya construimos rutas, ordenamos por tiempo, etc.

De hecho, esto es un poco más complicado y hay un par de matices, por ejemplo, una cuadrícula de ciudad ultradensa, cuando ya hemos reducido el tiempo al mínimo, y todavía hay demasiados resultados. Aquí ya es necesario clasificar y, por lo tanto, establecer rutas, lo cual es un poco costoso. Sin embargo, estos son casos extremos y no son muy llamativos.

En realidad, es poco probable que una persona se desplace por la lista debajo de 5-6 posiciones, por lo que en el 95% de los escenarios la isócrona dinámica resolvió el problema. Eliminamos el cuello de botella, una cantidad impredecible de resultados, e hicimos la carga en el servidor geográfico para cualquier solicitud casi plana. Verificar esto es muy fácil:

A la antigua usanza: tome un radio de 10 minutos y 30 resultados
Resultado: 1 solicitud de isochron + 30 solicitudes de rutas = 31

Nueva forma: compruebe, 30 resultados son muchos, divida el radio por la mitad, ahora obtenemos 10 resultados
Resultado: 2 solicitudes de isocrona + 10 solicitudes de rutas = 12



Nueva lógica de mapa


En la última parte, describí el mecanismo para generar tarjetas con rutas establecidas. Luego resultó ser bastante complicado y costoso en términos de computación, pero me gustaron tanto que decidí dejarlos. Al mismo tiempo, entendí que en su forma actual tenían pocos beneficios prácticos: no estaba claro de qué manera iban, y todos estaban orientados hacia el norte. Era necesario refinar.

Lo primero que decidí hacer fue desplegar los mapas en tiempo real usando una brújula. En cambio, esto fue descrito por lógica microscópica y funcionó muy rápidamente, sin embargo, con más de 10 resultados que giran constantemente, el rendimiento comenzó a disminuir. Además, parecía absolutamente repugnante: de hecho, las imágenes estáticas giraban, y esto fue más confuso durante el viaje de lo que de alguna manera ayudó.

La siguiente idea era indicar en los mapas la dirección del movimiento de la flecha. Era muy simple: ya tenía un vector calculado y todo lo que tenía que hacer era generar una forma geométrica de la flecha. Al mismo tiempo, en una posición estática, las tarjetas continuaron mostrando la posición del conductor con un marcador redondo. Hubo una advertencia: era necesario normalizar los tamaños de los marcadores y las flechas para diferentes niveles de zoom. Esto parece ser una tarea simple, pero estuve atrapado en ella durante mucho tiempo. La cuestión era esta: generé todos los símbolos en el mapa en metros, y tomé como base la fracción de la altura de todo el mapa en metros. Resultó que durante la creación de mapas, determinando cuadros cuadrados delimitadores, pegando y recortando mosaicos en ellos, etc., se acumularon errores, y estos pequeños errores finalmente condujeron a marcadores de tamaños muy diferentes. Especialmente infernal fue la situación con las tarjetas a pequeña escala. Sin embargo, no entraré en los detalles de la solución, debido a estos errores, la lógica de la generación de tarjetas tuvo que ser completamente rediseñada. El césped ayudó mucho con esto: un gran conjunto de herramientas para manipular geodatos.

Con las flechas las cartas ya eran más útiles, pero aún faltaba algo. Después de las pruebas en vivo, quedó claro que todas las cartas se volvieron hacia el norte. En estática, esto no fue sorprendente, pero se hizo evidente de inmediato cuando te pones al volante. El conductor inconscientemente espera que la flecha siempre apunte hacia arriba cuando conduce. Después de descubrir esto, nuevamente me senté a trabajar. Esta fue nuevamente una de esas tareas que parecen muy simples, pero pasará un par de días después. Parecería: calcule el acimut y gire el GeoJSON final antes de la rasterización. Pero nuevamente hubo un matiz: este GeoJSON final fue generado por un cuadro de delimitación directa y, al rotarlo y recortarlo, detecta espacios vacíos.



En el diagrama de arriba, aproximadamente di mi solución. Como resultado, resultó no ser muy costoso en términos de recursos y cubría el 99% de los escenarios (creo que los errores subirán en algún lugar cerca de los polos). En general, el servidor de computación geográfica sigue siendo la parte más intensiva en recursos del proyecto, pero ahora sus tarjetas de ruta, además de la estética, también son muy prácticas. Incluso intenté llegar al lugar usando estas tarjetas exclusivamente, sin incluir la navegación. E incluso llegó.



Calidad de los datos


Tomé todos mis datos de diferentes maneras de OpenStreetMap. Como saben, este recurso es 100% sin fines de lucro y está respaldado por una mente colectiva. Esto es una ventaja (es gratis y con una estructura clara), también es una desventaja: los datos son muy heterogéneos.

En un nivel alto, esto significa una cobertura desigual del mundo en su conjunto: en países y ciudades con una gran cantidad de entusiastas, se describe cada césped y camino, y en otros lugares hay zonas casi vacías con objetos básicos incompletos. Los datos se actualizan con exactamente la misma desigualdad. Mientras probaba mi aplicación, encontré un par de veces nuevas estaciones de servicio, cafés y, a veces, carreteras enteras que aún no había logrado poner en los mapas. Quejarse de esto es estúpido: el mismo Google gasta presupuestos astronómicos y contiene todo un equipo de automóviles responsables de la relevancia de sus datos. Entonces, lo mejor que podemos hacer es sincronizar más a menudo con los extractos de OpenStreetMap. Bueno, deseen buena suerte a su comunidad.

Pero en un nivel inferior, debido a la edición caótica de los mapas, hay una serie de otros problemas que pueden resolverse por completo. Esto se refiere principalmente a datos basura y duplicados. La variedad de este desorden es sorprendente: el mismo lugar se puede describir 3 veces de diferentes maneras, las instituciones no tienen nombres, tipos y etiquetas incorrectamente, etc. Todo esto no tiene una solución unificada; más bien, se necesita un complejo de medidas para sistematizar el contenido. Por ejemplo, tengo las siguientes condiciones:

Hay varios sinónimos y variaciones de la misma etiqueta:> describimos los diccionarios de alias (por ejemplo, estacionamiento, estacionamiento_espacio, estacionamiento_entrada, etc.).

Hay varios lugares con el mismo tipo y las mismas coordenadas:

  • si todos no tienen nombre -> el tipo de lugar se convierte en el nombre
  • solo uno tiene un nombre -> tómalo
  • todos tienen un nombre y son diferentes -> toma el apellido cronológicamente

Hay varios lugares con el mismo tipo y casi las mismas coordenadas:

  • si todos no tienen nombre -> probablemente duplicados, no lo complicaremos. Combinar en un punto con coordenadas promedio, en el que el tipo de lugar se convierte en el nombre. Un hombre vendrá y entenderá
  • solo uno tiene un nombre -> lo mismo, solo que ahora ya tenemos un nombre
  • todos tienen un nombre y son diferentes -> pero este es un clúster

El clúster en nuestro caso es una tarjeta en el encabezado de la cual se describen varios lugares. Muy a menudo, estos son grupos de tiendas o estaciones de servicio cercanas. O, por ejemplo, un cajero automático está ubicado dentro del edificio del banco y el otro afuera. No representan ninguna dificultad para nosotros: calculamos las coordenadas promedio y trazamos la ruta hacia ellas. En la interfaz, mostramos esto de manera limpia y simple:



Interfaz y diseño


Entonces me ocurrió que antes del inicio del desarrollo, generalmente ya me imagino la imagen final. Al mismo tiempo, no me gusta dibujar diagramas y conceptos, prefiero formar un diseño en paralelo con las funciones (si este es mi proyecto, por supuesto). Este enfoque iterativo es muy bueno, por un lado, porque le permite cambiar entre imágenes y código, por otro lado, a veces tiene que rehacer todo con demasiada frecuencia. Aquí sucedió lo mismo: parece que he introducido la interfaz más simple cien veces. Comenzando desde pequeños detalles como iconos y sangrías, terminando con la composición de tarjetas, menús, etc. No describiré todo, pasaré rápidamente por cuestiones clave. Si no está interesado en el diseño, no dude en saltearlo.

Paleta de colores


Durante mucho tiempo no pude entender qué hacer con la paleta. Realmente quería designar las categorías de lugares en diferentes colores, con la excepción del verde: decidí guardarlo como un acento. Elegí colores fácilmente distinguibles y ricos, todo parece estar bien. Después de un tiempo, descubrí que el azul de la estación de servicio hace eco del azul, que en el mapa indica la posición del conductor. No hizo nada con esto, lo dejó como está, pero el perfeccionista interno no está satisfecho.



"En el camino" y "Estás cerca"


Después de que apareció la lógica que determina la dirección del movimiento del conductor, se hizo posible dividir las rutas en "a lo largo del camino" y el resto. Como ya dije, esto está determinado por el primer segmento de la ruta establecida al lugar: ¿coincide con el último segmento de la ruta del conductor? Si es así, entonces ya vamos a este lugar. Entonces surgió la pregunta de cómo mostrar esto en la interfaz. Además de los cambios en el mapa que describí anteriormente, surgió la idea de la placa "En el camino" (o "En ruta" en inglés, parece que significan lo mismo). Reutilizo el mismo dado para otro escenario: cuando la distancia al lugar encontrado es inferior a 25 metros. Entonces no tiene sentido obtener indicaciones, oculto el mapa y escribo que ya estás cerca ("Estás cerca" / "Mira a tu alrededor").



Tarjeta comunitaria


Al comienzo del desarrollo de la depuración, utilicé un mapa estático de Google para ver isocrón y resultados. Luego corrió con ella durante mucho tiempo, sin saber dónde quedarse: parece que el mapa es algo interesante, pero parece que no debería ocupar un lugar. Además, dolorosamente ni siquiera quería depender de Google tan poco. Entonces, al final, eliminé el mapa, pero después de un tiempo comencé a generar tarjetas de ruta y me di cuenta de que yo mismo había "crecido" tecnológicamente en el mapa grande. Resultó que no fue tan difícil hacer esto, aunque hasta ahora el mapa común sigue siendo la pieza más intensiva en recursos de todo el proyecto. Y para que no ocupe espacio en la interfaz, coloco la tarjeta en una página separada (la extraerán con menos frecuencia).



Localización


Para una producción normal en producción, la localización es necesaria. Siempre es, por un lado, un trabajo muy directo y simple, por el otro, cuando comienzas a hacerlo, multitudes de cucarachas se arrastran por todas partes. En mi caso, el contenido principal de OSM ya estaba localizado, por lo que solo quedaban los tipos de lugar y los elementos de la interfaz. Con la excepción de algunos tapones (durante mucho tiempo no pude formular un dado "en camino") todo fue fácil. Vale la pena señalar que los nombres de lugares pueden ocupar 2 y 3 líneas, y pueden no caber en pantallas de ancho pequeño; por lo tanto, el widget auto_size_text ayudó aquí, lo recomiendo dentro de límites razonables.



Pero en el aspecto técnico no fue tan suave.Hasta la fecha, casi la única solución para la localización bajo flutter es la biblioteca Intl_translation , y ... es extraño. Está claro que tienen que sentarse en dos sillas y generar formatos de línea completamente diferentes para Android y iPhone. Sin embargo, este enfoque consiste en transferir traducciones a una clase separada, luego ejecutar secuencias de comandos desde la consola (!) Para crear algunos archivos intermedios, luego preocuparse por ellos ... Todo esto no es evidente para un principiante, y lo más importante es que es difícil de mantener, ya que cada edición está acompañada de bailes manuales. con pandereta

Sin embargo, existe la posibilidad de que no lo entendí hasta el final, o tal vez algunos entornos de desarrollo ya lo hacen todo automáticamente. En cualquier caso, mi experiencia fue bastante estresante, y espero que los mecanismos de localización del aleteo se vuelvan a trabajar.

Fecha de lanzamiento


En realidad, no hay nada interesante que contar sobre el lanzamiento en sí. Hubo vagas sospechas de que la tienda de manzanas se negaría a revolver la aplicación, pero no se materializaron, todo salió bien. Volví a dibujar el icono y dibujé una página de introducción que se encuentra con el usuario por primera vez. Tuve que jugar un poco con los dibujos, pero parece que no pasó nada. Tampoco surgieron dificultades con los permisos y su localización.

En el último momento, la compilación de la compilación de lanzamiento para Android se rompió debido a la transición del soporte a androidx: algunas bibliotecas que utilicé no lo admitieron de inmediato. Como resultado, solo esperé un par de días, pero debemos rendir homenaje a los autores de estas bibliotecas, que los arreglaron muy rápidamente. Sin embargo, este incidente una vez más me convenció: no voy a arrastrar un gran proyecto comercial todavía. A pesar de que me gustó, toda la historia aún es muy, muy cruda.

Bueno, como se prometió, descargue enlaces:





Planes


Y algunas palabras al final sobre los planes. Si esto es útil para alguien que no sea yo y hay descargas, tengo muchas ideas para un mayor desarrollo. Aquí hay una lista de muestra de lo que me gustaría incluir en la versión No. 2:

  • más datos sobre lugares (por ejemplo, precios de la gasolina o tipos de cargos para automóviles eléctricos)
  • tema oscuro para conductores nocturnos
  • modo compacto para ver más resultados sin desplazarse (por ejemplo, apague el minimapa si lo desea)
  • aceleración de la cartografía (realmente necesita considerar este cuello de botella, ya sea optimizar de alguna manera complicado o transferir a la virtualización de contenedores)

Me gustaría admitir Android Auto y Apple CarPlay aún más. Nunca he hecho aplicaciones para ellos, así que tengo curiosidad por probarlo yo mismo.

Eso es todo, gracias a todos por su atención.

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


All Articles