La première chose que je veux dire, c'est que c'était difficile. Beaucoup plus difficile que je ne le pensais. J'ai eu avant cette expérience très difficile en apportant des produits à sortir au travail, mais je n'ai jamais atteint pour des projets personnels. Ils se sont tous terminés avec des prototypes de degrés de dégoût différents, mais celui-ci semblait survivre. Pour le moment, il a été lancé pour plus de 80 pays (toute l'Europe, l'Asie et l'Amérique du Nord), sur les deux plates-formes mobiles, et à la fin de l'article, il y aura des liens de téléchargement - par conséquent, j'invite toutes les personnes intéressées à essayer, casser et gronder.

Voici une brève réflexion avec laquelle tout a commencé:
À mon avis, une recherche sur les cartes mobiles existantes est effectuée pour les piétons et ne fonctionne pas du tout pour les conducteurs. Vous devez vous arrêter, vous plonger dans les cartes jonchées d'informations et de publicités excessives, piquer de petites icônes. Ce n'est pas pratique, cela ne vous aidera pas dans un endroit inconnu, au final c'est juste dangereux. Une solution intuitive et propre est nécessaire qui ne distrait pas et ne vous fasse pas ralentir.
Dans la
première partie, j'ai décrit mon cheminement de cette simple pensée à une solution de travail, puis je décrirai comment j'ai fait glisser cette solution vers la version.
Pour gagner du temps, je vais commencer par une brève relecture de la partie précédente: là j'écris qu'au lieu de chercher j'ai décidé d'utiliser la numérisation en mouvement, et l'interface de l'application a été simplifiée autant que possible. Au lieu d'une ligne d'entrée absurde pour le conducteur, il a ajouté plusieurs gros boutons pour les choses qui peuvent être utiles sur la route: station-service, recharge, guichet automatique, parking, pharmacie. Au lieu d'une carte, j'ai fait une liste et lorsque je sélectionne un résultat, la navigation dans Apple / Google Maps s'ouvre. Pour l'application, j'ai décidé d'utiliser Flutter (en même temps que j'ai rencontré quel genre de bête c'était), j'ai pris les données d'OpenStreetMap. J'ai terminé mon histoire sur le fait qu'un prototype plus ou moins sain d'esprit était prêt.
Ensuite, cela a pris environ 4 à 5 mois, puis les changements dans la vie ont commencé et le projet a été abandonné - et j'ai commencé à m'en lasser. Un mois plus tard, je l'ai dépoussiéré, rafraîchi dans ma tête en écrivant un article sur un hub et j'ai décidé: finissons-le. Quiconque connaît la différence entre un prototype et un produit sourira tristement à cet endroit.
Ce qui s'est passé ensuite a pris encore quatre mois. Je me suis procuré un tracker de tâches, en termes généraux formé une liste de problèmes, collecté des appareils pour les tests. Le soir et le week-end, je me suis assis au travail et j'ai fait avancer le projet. À quels moments il semblait que la liste ne faisait que s'allonger, et je me noyais dans des nuances et des finitions infinies. Il se prit en main, jeta quelque chose, quelque part au contraire poussa au perfectionnisme. Ensuite, je vais essayer de parler des points les plus intéressants dans le développement d'un projet aussi simple en apparence.
La technologie
Architecture générale
Quelque part au milieu de l'œuvre, un sentiment a commencé à apparaître que l'architecture se répandait de façon incontrôlable. Trop de composants et de connexions ont été rompus, plusieurs goulots d'étranglement ont été recherchés. Heureusement, le projet est petit et je l'ai ressenti à temps, donc mettre les choses en ordre n'a pas été difficile. Au niveau des composants individuels, cela se résumait à une refactorisation et à la suppression des bibliothèques inutiles; au niveau mondial, j'ai étendu la fonctionnalité à 3 petits serveurs que j'ai démarrés dans
DigitalOcean .
- Serveur API (Python) - le serveur principal, nous nous tournons vers lui. Il n'y a pas beaucoup de logique, principalement la formation de résultats pour la sortie. Le plus économique en termes de ressources.
- Serveur élastique (Java) - Elasticsearch et Photon (géocodeur open source) tournent dessus. Ils utilisent le même index dans lequel la planète entière est importée d'OpenStreetMap. Fonctions serveur: recherche de lieux par décharge et géocodeur. De par sa nature, l'élastique est très rapide et léger, donc le serveur n'est pas non plus très gras.
- Un serveur géo (Node) est le plus difficile de tous. Basé sur l'Open Source Routing Machine, j'ai écrit une petite API, et ses tâches incluent tous les calculs géographiques: pose d'itinéraires, calcul d'isochrones et génération de tuiles. Chaque opération individuelle n'est pas très ingénieuse, mais des dizaines d'entre elles sont nécessaires pour toute recherche, et cela devient un goulot d'étranglement. Pour le moment, sur ce serveur 16 Go de RAM, et en général, tout fonctionne en une fraction de seconde - sauf pour générer des tuiles. Lorsqu'il y en a beaucoup dans la file d'attente, vous pouvez attendre des photos avec des cartes en quelques secondes. Heureusement, ils apparaissent sur le client de manière asynchrone, et cela ne gâche pas beaucoup l'image globale (j'espère).
De plus, j'ai décidé d'alimenter les extraits d'OpenStreetMap pour les géo-calculs séparément par pays. Cela fonctionne comme ceci: nous faisons la première demande de coordonnées à notre géocodeur, il détermine le pays, puis nous prenons les fichiers téléchargés uniquement de ce pays pour les manipulations dont nous avons besoin. Cela est nécessaire, car même mon serveur plutôt puissant n'est pas en mesure de convertir l'extrait de plus de deux gigaoctets - le processus consomme rapidement toute la mémoire et s'étouffe. Heureusement, presque tous les pays entrent dans cette limite, à l'exception des États-Unis: ce monstre a dû être divisé en États. Enfin, pour maintenir une centaine et demi d'extraits, j'ai décidé d'écrire un tas de scripts qui vérifient leur santé, leur réparation et leur mise à jour.

En général, tout cela semble plus compliqué que cela ne fonctionne dans la pratique. En principe, je suis satisfait de la solution qui en résulte - elle a une bonne réserve de mise à l'échelle en fonction du nombre de territoires pris en charge et du niveau de charge.
Isochron dynamique
Pendant longtemps, l'un des problèmes clés pour moi a été la densité hétérogène des résultats. La raison est tout à fait compréhensible - c'est la densité hétérogène du réseau routier lui-même et des bâtiments qui le composent. Dans une ville de taille moyenne dans un rayon de 5 minutes, il peut y avoir 2-3 distributeurs automatiques de billets, et maintenant nous allons nous déplacer au centre de la métropole - et pour les mêmes 5 minutes, il peut y avoir 20 ou 30 résultats. Enfin, nous sautons dans la campagne et observons un résultat presque garanti jusqu'à ce que nous nous rapprochions de la ville et que le rayon de recherche capture quelque chose.
Ce problème donne une charge non linéaire et imprévisible sur le serveur, et surtout - une expérience plutôt moche pour l'utilisateur. L'ajout d'un filtre aux options (5 minutes, 10 minutes, 30 minutes) ne résout fondamentalement rien. Dans le village, même un rayon de 30 minutes peut ne rien rapporter, mais dans une mégalopole, même 5 minutes vous accableront de résultats. De plus, nous avons ajouté des fonctionnalités supplémentaires aux boutons dont le conducteur devrait se déplacer. En général, non-sens, vous avez besoin d'une solution fondamentalement différente.
Lorsqu'une solution a été trouvée, elle s'est avérée très simple. Au lieu d'aller à l'opposé et d'inviter l'utilisateur à sélectionner un rayon de recherche, nous pouvons rendre ce rayon automatique. La logique est en fait élémentaire:
- Vous définissez des limites sur les résultats - par exemple, au moins 1 et pas plus de 20 - et commencez par 10 minutes
- Faites une recherche par lieu. Jusqu'à présent, nous n'avons pas besoin d'obtenir des directions pour eux, nous avons donc juste besoin de calculer l'isochrone et de filtrer par polygone dans l'élastique - les deux opérations sont très bon marché
- Si le nombre de résultats s'échappe dans une direction des limites (dans notre cas 0 ou 20+), divisez ou multipliez le temps par 2 et recommencez la recherche. S'il est inclus dans la limite, alors nous construisons déjà des itinéraires, trions par temps, etc.
En fait, c'est un peu plus compliqué et il y a quelques nuances, par exemple, un réseau urbain ultra-dense, alors que nous avons déjà réduit le temps au minimum, et qu'il y a encore trop de résultats. Ici, il est déjà nécessaire de trier, et donc de tracer des itinéraires, ce qui est un peu cher. Cependant, ce sont des cas extrêmes et ils ne sont pas très frappants.
En réalité, il est peu probable qu'une personne fasse défiler la liste en dessous de 5-6 positions, donc dans 95% des scénarios, l'isochron dynamique a résolu le problème. Nous avons supprimé le goulot d'étranglement - une quantité imprévisible de résultats - et rendu la charge sur le serveur géographique pour toute demande presque plate. Le vérifier est très simple:
À l'ancienne: prenez un rayon de 10 minutes et 30 résultats
Résultat: 1 demande d'isochron + 30 demandes d'itinéraires = 31
Nouvelle façon: vérifiez, 30 résultats sont nombreux, divisez le rayon en deux, maintenant nous obtenons 10 résultats
Résultat: 2 demandes d'isochrone + 10 demandes d'itinéraires = 12
Nouvelle logique de carte
Dans la dernière partie, j'ai décrit le mécanisme de génération de cartes avec des itinéraires posés. Il s'est ensuite avéré assez compliqué et coûteux en informatique, mais je les aimais tellement que j'ai décidé de les quitter. En même temps, j'ai compris que sous leur forme actuelle, ils n'avaient que peu d'avantages pratiques - ils ne savaient pas clairement dans quelle direction vous alliez, et ils étaient tous orientés vers le nord. Il fallait affiner.
La première chose que j'ai décidé de faire a été de déployer les cartes en temps réel à l'aide d'une boussole. En flottement, cela a été décrit par la logique microscopique et a fonctionné très rapidement, cependant, avec plus de 10 résultats qui tournent constamment, les performances ont commencé à s'égoutter. De plus, cela avait l'air absolument écoeurant: en fait, des images statiques tournaient, et c'était plus déroutant pendant le trajet que cela ne l'aidait en quelque sorte.
L'idée suivante était d'indiquer sur les cartes le sens de déplacement de la flèche. C'était très simple - j'avais déjà un vecteur calculé, et tout ce que j'avais à faire était de générer une forme géométrique de la flèche. Dans le même temps, dans une position statique, les cartes ont continué à indiquer la position du conducteur avec un marqueur rond. Il y avait une mise en garde - il était nécessaire de normaliser les tailles des marqueurs et des flèches pour différents niveaux de zoom. Cela semble être une tâche simple, mais j'ai été coincé dessus pendant longtemps. La chose était la suivante: j'ai généré tous les symboles sur la carte en mètres, et pris comme base la fraction de la hauteur de la carte entière en mètres. Il s'est avéré que lors de la création des cartes - détermination des boîtes de délimitation carrées, collage et rognage des tuiles sur celles-ci, etc. - des erreurs se sont accumulées, et ces petites erreurs ont finalement conduit à des tailles de marqueurs visuellement très différentes. La situation des cartes à petite échelle était particulièrement infernale. Je n'entrerai pas dans les détails de la solution, cependant, à cause de ces erreurs, la logique de génération de carte a dû être complètement redessinée.
Turf a beaucoup aidé à cela - un excellent ensemble d'outils pour manipuler les géodonnées.
Avec les flèches, les cartes étaient déjà plus utiles, mais il manquait encore quelque chose. Après des tests en direct, il est devenu clair que toutes les cartes étaient retournées vers le nord. En statique, ce n'était pas frappant, mais cela est immédiatement devenu évident lorsque vous prenez le volant. Le conducteur s'attend inconsciemment à ce que la flèche pointe toujours vers le haut lors de la conduite. Ayant découvert cela, je me suis de nouveau assis pour travailler. C'était encore une de ces tâches qui semblent très simples, mais vous passerez quelques jours après. Il semblerait - calculer l'azimut et tourner le GeoJSON final avant la pixellisation. Mais il y avait encore une nuance - ce GeoJSON final a été généré par une boîte de délimitation directe, et, étant tourné et recadré dessus, il détecte les endroits vides.

Dans le diagramme ci-dessus, j'ai donné grossièrement ma solution. En conséquence, cela s'est avéré peu coûteux en termes de ressources et couvrant 99% des scénarios (je pense que les bugs vont grimper quelque part près des pôles). En général, le serveur de géo-calcul est toujours la partie la plus consommatrice de ressources du projet, mais maintenant ses cartes de route, en plus de l'esthétique, sont également très pratiques. J'ai même essayé de me rendre sur place en utilisant exclusivement ces cartes, sans compter la navigation. Et même arrivé.

Qualité des données
J'ai pris toutes mes données de différentes manières depuis OpenStreetMap. Comme vous le savez, cette ressource est 100% sans but lucratif et est soutenue par un esprit collectif. C'est un plus (c'est gratuit et avec une structure claire), c'est aussi un moins - les données sont très hétérogènes.
À un niveau élevé, cela signifie une couverture inégale du globe dans son ensemble: dans les pays et les villes avec un grand nombre de passionnés, chaque pelouse et chaque chemin sont décrits, et dans d'autres endroits, il y a des zones presque vides avec des objets sommaires de base. Les données sont mises à jour avec exactement la même irrégularité. Lors du test de mon application, je suis tombé sur plusieurs fois de nouvelles stations-service, des cafés et parfois des routes entières que je n'avais pas encore réussi à mettre sur les cartes. Se plaindre de cela est stupide: le même Google dépense des budgets astronomiques et contient toute une équipe de voitures responsables de la pertinence de ses données. Ici, le mieux que nous puissions faire est de synchroniser plus souvent avec les extraits d'OpenStreetMap. Eh bien, je souhaite bonne chance à leur communauté.
Mais à un niveau inférieur, en raison de l'édition chaotique des cartes, il existe un certain nombre d'autres problèmes qui peuvent être complètement résolus. Cela concerne principalement les données indésirables et les doublons. La variété de ce gâchis est frappante: le même endroit peut être décrit 3 fois de différentes manières, les institutions n'ont pas de noms, les types et les étiquettes sont mal inscrits, etc. Tout cela n'a pas de solution unifiée, mais un ensemble de mesures est nécessaire pour systématiser le contenu. Par exemple, j'ai les conditions suivantes:
Il existe plusieurs synonymes et variantes de la même balise -> nous décrivons des dictionnaires d'alias (par exemple parking, parking_space, parking_entrance, etc.).
Il y a plusieurs endroits avec le même type et les mêmes coordonnées:
- si tout le monde n'a pas de nom -> le type de lieu devient le nom
- un seul a un nom -> prends-le
- tout le monde a un nom et ils sont différents -> prendre le nom de famille par ordre chronologique
Il y a plusieurs endroits avec le même type et presque les mêmes coordonnées:
- si tout le monde n'a pas de nom -> les doublons les plus probables, nous ne compliquerons pas. Fusionner en un point avec des coordonnées moyennes, dans lequel le type de lieu devient le nom. Un homme viendra et comprendra
- un seul a un nom -> la même chose, seulement maintenant nous avons déjà un nom
- tout le monde a un nom et ils sont différents -> mais c'est un cluster
Le cluster dans notre cas est une carte dans l'en-tête dont plusieurs emplacements sont décrits. Le plus souvent, ce sont des grappes de magasins ou de stations-service à proximité. Ou, par exemple, un guichet automatique est situé à l'intérieur du bâtiment de la banque et l'autre à l'extérieur. Ils ne représentent pas de difficulté pour nous: nous calculons les coordonnées moyennes et tracons l'itinéraire vers elles. Dans l'interface, nous le montrons clairement et simplement:

Interface et conception
Il m'est donc arrivé qu'avant le début du développement, j'imagine généralement déjà l'image finale. En même temps, je n'aime pas dessiner des diagrammes et des concepts, préférant former un design en parallèle avec des fonctions (si c'est mon projet, bien sûr). Cette approche itérative est très cool d'une part, car elle vous permet de basculer entre les visuels et le code, d'autre part - parfois vous devez tout refaire trop souvent. La même chose s'est produite ici: il me semble avoir pelleté l'interface la plus simple cent fois. À partir de bagatelles telles que des icônes et des retraits, se terminant par la composition de cartes, de menus, etc. Je ne décrirai pas tout, je passerai rapidement en revue les questions clés. Si vous n'êtes pas intéressé par le design, n'hésitez pas à le sauter.
Palette de couleurs
Pendant longtemps, je ne pouvais pas comprendre quoi faire avec la palette. Je voulais vraiment désigner les catégories de lieux de différentes couleurs, à l'exception du vert - j'ai décidé de l'enregistrer comme accent. J'ai choisi des couleurs riches et faciles à distinguer, tout semble aller bien. Après un certain temps, j'ai découvert que le bleu de la station-service fait écho au bleu, qui sur la carte indique la position du conducteur. Il n'a rien fait de tel, l'a laissé tel quel - mais le perfectionniste interne n'est pas satisfait.

«En route» et «Vous êtes près»
Après que la logique soit apparue qui détermine la direction du mouvement du conducteur, il est devenu possible de diviser les itinéraires en «le long du chemin» et le reste. Comme je l’ai déjà dit, cela est déterminé par le premier segment de l’itinéraire tracé à l’endroit: coïncide-t-il avec le dernier segment de l’itinéraire du conducteur. Si oui, alors nous allons déjà à cet endroit. Ensuite, la question s'est posée de savoir comment afficher cela dans l'interface. En plus des changements dans la carte que j'ai décrits ci-dessus, l'idée est venue de la plaque «On the way» (ou «En route» en anglais - il semble qu'ils signifient la même chose). Je réutilise le même dé pour un autre scénario: lorsque la distance à l'endroit trouvé est inférieure à 25 mètres. Ensuite, cela n'a aucun sens d'obtenir des directions, je cache la carte et j'écris que vous êtes déjà proche («Vous êtes près» / «Regardez autour»).

Carte communautaire
Au tout début du développement du débogage, j'ai utilisé une carte statique de Google pour voir l'isochron et les résultats. Puis il a couru avec elle pendant longtemps, sans savoir où s'en tenir: il semble que la carte soit une chose intéressante, mais il semble qu'elle ne devrait pas prendre de place. De plus, douloureusement ne voulait même pas dépendre de Google dans une si petite bagatelle. Donc, à la fin, j'ai retiré la carte, mais après un certain temps, j'ai commencé à générer des cartes d'itinéraire et j'ai réalisé que j'avais moi-même «évolué» technologiquement vers la grande carte. Il s'est avéré que ce n'était pas si difficile à faire, même si jusqu'à présent la carte commune reste l'élément le plus gourmand en ressources de l'ensemble du projet. Et pour qu'elle ne prenne pas de place dans l'interface, je mets la carte sur une page séparée (ils la tireront moins souvent).

Localisation
Pour une production normale en production, la localisation est nécessaire.
C'est toujours d'une part un travail très direct et simple, d'autre part - lorsque vous commencez à le faire, des foules de cafards rampent de partout. Dans mon cas, le contenu principal d'OSM était déjà localisé, de sorte qu'il ne restait que les types de place et les éléments d'interface. À l'exception de quelques bouchons (pendant longtemps je n'ai pas pu formuler un dé «En route») tout a été facile. Il convient de noter que les noms de lieux peuvent occuper à la fois 2 et 3 lignes et ne peuvent pas entrer dans des écrans de petite largeur - par conséquent, le widget auto_size_text a aidé ici , je le recommande dans des limites raisonnables.
Mais sur le plan technique, ce n'était pas si fluide. À ce jour, presque la seule solution de localisation sous flutter est la bibliothèque Intl_translationet elle est ... bizarre. Il est clair qu'ils doivent s'asseoir sur deux chaises et générer des formats de ligne complètement différents pour Android et iPhone. Cependant, cette approche consiste à transférer les traductions dans une classe distincte, puis à exécuter des scripts à partir de la console (!) Pour créer des fichiers intermédiaires, puis à les manipuler ... Tout cela n'est pas évident pour un débutant et, surtout, difficile à maintenir, car chaque modification est accompagnée de danses manuelles avec un tambourin.Cependant, il est possible que je ne l'ai pas compris jusqu'au bout - ou peut-être que certains environnements de développement le font déjà automatiquement. En tout cas, mon expérience a été assez stressante et j'espère que les mécanismes de localisation du flutter seront retravaillés.Date de sortie
En fait, il n'y a rien d'intéressant à dire sur la sortie elle-même. Il y avait de vagues soupçons que le Apple Store refuserait de flotter l'application, mais ils ne se sont pas matérialisés - tout s'est bien passé. J'ai redessiné l'icône et esquissé une page d'introduction qui rencontre l'utilisateur pour la première fois. J'ai dû bricoler un peu avec des dessins, mais il semble que rien ne se soit produit. Aucun écueil avec des permishens et leur localisation ne sont également apparus.Au dernier moment, la construction de la version de construction pour Android est tombée en panne en raison de la transition du support à androidx: certaines bibliothèques que j'ai utilisées ne le supportaient pas immédiatement. En conséquence, j'ai juste attendu quelques jours, mais nous devons rendre hommage aux auteurs de ces bibliothèques - ils les ont réparés très rapidement. Néanmoins, cet incident m'a encore une fois convaincu: je ne vais pas encore traîner un grand projet commercial. Malgré le fait que je l'aimais bien, toute l'histoire est encore très, très brute.Eh bien, comme promis, téléchargez les liens:

Plans
Et quelques mots à la fin sur les plans. Si cette chose est utile pour quelqu'un d'autre que moi et qu'il y a des téléchargements, j'ai beaucoup d'idées pour un développement ultérieur. Voici un exemple de liste de ce que j'aimerais inclure dans la version n ° 2:- plus de données sur les lieux (par exemple, les prix de l'essence ou les types de charges pour les voitures électriques)
- thème sombre pour les conducteurs de nuit
- mode compact pour voir plus de résultats sans faire défiler (par exemple, désactivez la mini-carte si vous le souhaitez)
- accélération de la cartographie (vous devez vraiment tenir compte de ce goulot d'étranglement - soit optimiser en quelque sorte délicat, soit transférer vers la virtualisation des conteneurs)
J'aimerais soutenir encore plus Android Auto et Apple CarPlay. Je n'ai jamais fait de demande pour eux, je suis donc curieux de l'essayer moi-même.C'est tout, merci à tous pour votre attention.