Wie ich versucht habe, eine Kartensuche nach Treibern zu reparieren. Teil 2

Das erste, was ich sagen möchte, war, dass es schwierig war. Viel schwieriger als ich dachte. Ich hatte vor dieser sehr schwierigen Erfahrung, Produkte bei der Arbeit herauszubringen, aber ich habe nie nach persönlichen Projekten gegriffen. Sie alle endeten mit Prototypen von unterschiedlichem Ekel, aber dieser schien zu überleben. Momentan wurde es für mehr als 80 Länder (ganz Europa, Asien und Nordamerika) auf beiden mobilen Plattformen gestartet. Am Ende des Artikels werden Download-Links angezeigt. Daher lade ich alle Interessierten ein, es zu versuchen, zu brechen und zu schelten.

Hier ist ein kurzer Gedanke, mit dem alles begann: Meiner Meinung nach wird eine Suche auf vorhandenen mobilen Karten für Fußgänger durchgeführt und funktioniert für Fahrer überhaupt nicht. Sie müssen anhalten, in die Karten eintauchen, die mit überschüssigen Informationen und Werbung übersät sind, und kleine Symbole anstechen. Es ist unpraktisch, es hilft dir nicht an einem unbekannten Ort, am Ende ist es nur gefährlich. Es wird eine intuitive und saubere Lösung benötigt, die nicht ablenkt und Sie nicht verlangsamt.

Im ersten Teil habe ich meinen Weg von diesem einfachen Gedanken zu einer funktionierenden Lösung beschrieben, und dann werde ich beschreiben, wie ich diese Lösung in die Version gezogen habe.

Um Zeit zu sparen, beginne ich mit einer kurzen Nacherzählung des vorherigen Teils: Dort schreibe ich, dass ich mich anstelle der Suche für das Scannen in Bewegung entschieden habe und die Anwendungsoberfläche so weit wie möglich vereinfacht wurde. Anstelle einer absurden Eingangszeile für den Fahrer fügte er mehrere große Tasten für Dinge hinzu, die auf der Straße nützlich sein können: Tankstelle, Aufladung, Geldautomat, Parkplatz, Apotheke. Anstelle einer Karte habe ich eine Liste erstellt. Wenn ich ein Ergebnis auswähle, wird die Navigation durch Apple / Google Maps geöffnet. Für die Anwendung habe ich mich für Flutter entschieden (gleichzeitig habe ich erfahren, um welche Art von Tier es sich handelt) und die Daten von OpenStreetMap übernommen. Ich beendete meine Geschichte mit der Tatsache, dass ein mehr oder weniger vernünftiger Prototyp fertig war.

Dann dauerte alles ungefähr 4-5 Monate, dann begannen die Veränderungen im Leben und das Projekt blieb auf der Strecke - und ich wurde es langsam leid. Einen Monat später wischte ich es ab, erfrischte es in meinem Kopf, indem ich einen Artikel über einen Hub schrieb, und entschied: Lass es uns beenden. Jeder, der den Unterschied zwischen einem Prototyp und einem Produkt kennt, wird an dieser Stelle traurig lächeln.

Was als nächstes geschah, dauerte weitere vier Monate. Ich besorgte mir einen Task-Tracker, bildete allgemein eine Liste von Problemen, sammelte Geräte zum Testen. Abends und am Wochenende setzte ich mich zur Arbeit und brachte das Projekt voran. In welchen Momenten schien die Liste nur zu wachsen, und ich ertrank in endlosen Nuancen und letzten Schlägen. Er nahm sich in die Hand, warf etwas weg, fuhr irgendwo im Gegenteil zum Perfektionismus. Als nächstes werde ich versuchen, über die interessantesten Punkte bei der Entwicklung eines scheinbar einfachen Projekts zu sprechen.

Technologie


Allgemeine Architektur


Irgendwann mitten in der Arbeit begann sich das Gefühl zu zeigen, dass sich die Architektur außer Kontrolle ausbreitete. Es wurden zu viele Komponenten und Verbindungen abgewickelt und mehrere Engpässe behoben. Glücklicherweise ist das Projekt klein und ich habe es rechtzeitig gespürt, so dass es nicht schwierig war, die Dinge in Ordnung zu bringen. Auf der Ebene der einzelnen Komponenten ging es darum, unnötige Bibliotheken umzugestalten und wegzuwerfen. Auf globaler Ebene verteilte ich die Funktionalität auf drei kleine Server, die ich in DigitalOcean gestartet hatte.

  1. API-Server (Python) - das Haupt-Server-Laying, wir wenden uns ihm zu. Es gibt nicht viel Logik, hauptsächlich die Bildung von Ergebnissen für die Ausgabe. Am sparsamsten in Bezug auf Ressourcen.
  2. Elastic Server (Java) - Elasticsearch und Photon (Open Source Geocoder) drehen sich darauf. Sie verwenden denselben Index, in den der gesamte Planet aus OpenStreetMap importiert wird. Serverfunktionen: Suche nach Orten per Deponie und Geocoder. Elastisch ist von Natur aus sehr schnell und leicht, so dass der Server auch nicht sehr fettig ist.
  3. Ein Geo-Server (Node) ist der schwierigste von allen. Basierend auf der Open Source Routing Machine habe ich eine kleine API geschrieben. Zu ihren Aufgaben gehören alle geografischen Berechnungen: Verlegen von Routen, Berechnen von Isochronen und Generieren von Kacheln. Jede einzelne Operation ist nicht sehr einfallsreich, aber Dutzende von ihnen werden für jede Suche benötigt, und dies wird zu einem Engpass. Momentan funktioniert auf diesem Server mit 16 GB RAM und im Allgemeinen alles in Sekundenbruchteilen - außer beim Generieren von Kacheln. Wenn sich viele davon in der Warteschlange befinden, können Sie in wenigen Sekunden auf Bilder mit Karten warten. Glücklicherweise erscheinen sie asynchron auf dem Client, was das Gesamtbild nicht wesentlich beeinträchtigt (hoffe ich).

Außerdem habe ich beschlossen, Auszüge aus OpenStreetMap für Geoberechnungen nach Ländern getrennt zu füttern. Das funktioniert so: Wir stellen die erste Anfrage mit Koordinaten an unseren Geocoder, bestimmen das Land und nehmen dann die heruntergeladenen Dateien dieses Landes nur für die Manipulationen, die wir benötigen. Dies ist notwendig, da selbst mein ziemlich leistungsfähiger Server den Extrakt, der größer als zwei Gigabyte ist, nicht konvertieren kann - der Prozess verbraucht schnell den gesamten Speicher und die Drosseln. Glücklicherweise passen fast alle Länder in diese Grenze, mit Ausnahme der Vereinigten Staaten: Dieses Monster musste in Staaten aufgeteilt werden. Um anderthalbhundert Auszüge zu erhalten, habe ich mich entschlossen, eine Reihe von Skripten zu schreiben, die ihren Zustand überprüfen, reparieren und aktualisieren.



Im Allgemeinen klingt dies alles komplizierter als es in der Praxis funktioniert. Im Prinzip bin ich mit der resultierenden Lösung zufrieden - sie verfügt über eine gute Reserve für die Skalierung nach Anzahl der unterstützten Gebiete und Lastniveau.

Dynamisches Isochron


Eines der Hauptprobleme für mich war lange Zeit die heterogene Dichte der Ergebnisse. Der Grund ist durchaus verständlich - dies ist die heterogene Dichte des Straßennetzes selbst und der darauf befindlichen Gebäude. In einer mittelgroßen Stadt im Umkreis von 5 Minuten kann es 2-3 Geldautomaten geben, und jetzt bewegen wir uns ins Zentrum der Metropole - und für die gleichen 5 Minuten können 20 oder 30 Ergebnisse erzielt werden. Schließlich springen wir ins Grüne und beobachten fast garantierte 0-Ergebnisse, bis wir uns der Stadt nähern und der Suchradius etwas erfasst.

Dieses Problem führt zu einer nichtlinearen und unvorhersehbaren Belastung des Servers und vor allem zu einer ziemlich miesen Erfahrung für den Benutzer. Das Hinzufügen eines Filters zu den Optionen (5 Minuten, 10 Minuten, 30 Minuten) löst im Grunde nichts. Im Dorf kann sogar ein Radius von 30 Minuten nichts zurückgeben, aber in einer Großstadt werden Sie sogar 5 Minuten mit Ergebnissen überwältigen. Außerdem haben wir zusätzliche Funktionen hinzugefügt, in deren Schaltflächen der Fahrer unterwegs sein sollte. Unsinn, im Allgemeinen brauchen Sie eine grundlegend andere Lösung.

Als eine Lösung gefunden wurde, stellte sich heraus, dass sie sehr einfach war. Anstatt vom Gegenteil abzuweichen und den Benutzer zur Auswahl eines Suchradius aufzufordern, können wir diesen Radius automatisch einstellen. Die Logik ist eigentlich elementar:

  1. Sie setzen den Ergebnissen Grenzen - zum Beispiel mindestens 1 und nicht mehr als 20 - und beginnen mit 10 Minuten
  2. Führen Sie eine Suche nach Ort durch. Bisher brauchen wir keine Wegbeschreibung zu ihnen, also müssen wir nur die Isochron berechnen und nach Polygon im Gummiband filtern - beide Operationen sind sehr billig
  3. Wenn die Anzahl der Ergebnisse von den Grenzwerten in eine Richtung abweicht (in unserem Fall 0 oder 20+), teilen oder multiplizieren Sie die Zeit durch 2 und führen Sie die Suche erneut durch. Wenn es im Limit enthalten ist, erstellen wir bereits Routen, sortieren nach Zeit usw.

Tatsächlich ist dies etwas komplizierter und es gibt einige Nuancen, zum Beispiel ein ultradichtes Stadtnetz, wenn wir die Zeit bereits auf ein Minimum reduziert haben und es immer noch zu viele Ergebnisse gibt. Hier ist es bereits notwendig, Routen zu sortieren und damit zu verlegen, was etwas teuer ist. Dies sind jedoch Extremfälle und sie sind nicht sehr auffällig.

In der Realität ist es unwahrscheinlich, dass eine Person die Liste unter 5-6 Positionen scrollt. In 95% der Szenarien löste das dynamische Isochron das Problem. Wir haben den Engpass beseitigt - eine unvorhersehbare Menge an Ergebnissen - und die Belastung des geografischen Servers für jede Anforderung nahezu flach gemacht. Das Auschecken ist sehr einfach:

Alter Weg: Nehmen Sie einen Radius von 10 Minuten und 30 Ergebnisse
Ergebnis: 1 Anfrage für Isochron + 30 Anfragen für Routen = 31

Neuer Weg: Überprüfen Sie, 30 Ergebnisse sind viele, teilen Sie den Radius in zwei Hälften, jetzt erhalten wir 10 Ergebnisse
Ergebnis: 2 Anfragen nach Isochron + 10 Anfragen nach Routen = 12



Neue Kartenlogik


Im letzten Teil habe ich den Mechanismus zum Generieren von Karten mit festgelegten Routen beschrieben. Es stellte sich dann heraus, dass das Rechnen ziemlich kompliziert und teuer war, aber ich mochte sie so sehr, dass ich mich entschied, sie zu verlassen. Gleichzeitig verstand ich, dass sie in ihrer jetzigen Form wenig praktischen Nutzen hatten - es war ihnen nicht klar, in welche Richtung Sie gingen, und sie wurden alle nach Norden gedreht. Es war notwendig zu verfeinern.

Das erste, was ich beschlossen habe, war, die Karten in Echtzeit mit einem Kompass bereitzustellen. Beim Flattern wurde dies durch mikroskopische Logik beschrieben und funktionierte sehr schnell. Mit mehr als 10 Ergebnissen, die sich ständig drehten, begann die Leistung zu sinken. Außerdem sah es absolut widerlich aus: Tatsächlich drehten sich statische Bilder, und dies war während der Fahrt verwirrender, als es irgendwie half.

Die nächste Idee war, auf den Karten die Bewegungsrichtung des Pfeils anzugeben. Es war sehr einfach - ich hatte bereits einen Vektor berechnet und musste nur eine geometrische Form des Pfeils erzeugen. Gleichzeitig zeigten die Karten in einer statischen Position weiterhin die Position des Fahrers mit einem runden Marker. Es gab eine Einschränkung: Es war notwendig, die Größe der Markierungen und Pfeile für verschiedene Zoomstufen zu normalisieren. Dies scheint eine einfache Aufgabe zu sein, aber ich habe lange daran festgehalten. Die Sache war folgende: Ich habe alle Symbole auf der Karte in Metern generiert und den Bruchteil der Höhe der gesamten Karte in Metern zugrunde gelegt. Es stellte sich heraus, dass sich beim Erstellen von Karten - Bestimmen von quadratischen Begrenzungsrahmen, Kleben und Trimmen von Kacheln usw. - Fehler ansammelten und diese kleinen Fehler letztendlich zu sehr unterschiedlichen Größen von Markierungen führten. Besonders höllisch war die Situation mit kleinen Karten. Ich werde nicht auf die Details der Lösung eingehen, aber aufgrund dieser Fehler musste die Logik der Kartengenerierung komplett neu gezeichnet werden. Turf hat dabei sehr geholfen - eine großartige Sammlung von Werkzeugen zur Bearbeitung von Geodaten.

Mit dem Pfeil waren Karten schon nützlicher, aber es fehlte noch etwas. Nach Live-Tests wurde klar, dass alle Karten nach Norden aufgedeckt waren. In der Statik war dies nicht auffällig, aber es wurde sofort deutlich, wenn Sie sich ans Steuer setzen. Der Fahrer erwartet unbewusst, dass der Pfeil beim Fahren immer nach oben zeigt. Nachdem ich das entdeckt hatte, setzte ich mich wieder zur Arbeit. Dies war wieder eine dieser Aufgaben, die sehr einfach zu sein scheinen, aber Sie werden ein paar Tage danach verbringen. Es scheint - berechnen Sie den Azimut und drehen Sie den endgültigen GeoJSON vor der Rasterung. Aber es gab wieder eine Nuance: Dieser endgültige GeoJSON wurde durch einen direkten Begrenzungsrahmen erzeugt und erkennt beim Drehen und Zuschneiden leere Stellen.



Im obigen Diagramm habe ich grob meine Lösung angegeben. Infolgedessen stellte sich heraus, dass es in Bezug auf Ressourcen nicht sehr teuer war und 99% der Szenarien abdeckte (ich denke, dass Fehler irgendwo in der Nähe der Pole aufsteigen werden). Im Allgemeinen ist der Geo-Berechnungsserver immer noch der ressourcenintensivste Teil des Projekts, aber jetzt sind neben der Ästhetik auch die Routenkarten sehr praktisch. Ich habe sogar versucht, ausschließlich mit diesen Karten an den Ort zu gelangen, ohne Navigation. Und sogar angekommen.



Datenqualität


Ich habe alle meine Daten auf unterschiedliche Weise aus OpenStreetMap übernommen. Wie Sie wissen, ist diese Ressource zu 100% gemeinnützig und wird von einem kollektiven Geist unterstützt. Dies ist ein Plus (es ist kostenlos und mit einer klaren Struktur), es ist auch ein Minus - die Daten sind sehr heterogen.

Auf hohem Niveau bedeutet dies eine ungleichmäßige Abdeckung des gesamten Globus: In Ländern und Städten mit einer großen Anzahl von Enthusiasten werden jeder Rasen und jeder Pfad beschrieben, und an anderen Stellen gibt es fast leere Zonen mit einfachen skizzenhaften Objekten. Die Daten werden mit genau der gleichen Ungleichmäßigkeit aktualisiert. Beim Testen meiner Anwendung stieß ich einige Male auf neue Tankstellen, Cafés und manchmal ganze Straßen, die ich noch nicht auf Karten gesetzt hatte. Sich darüber zu beschweren ist dumm: Das gleiche Google gibt astronomische Budgets aus und enthält eine ganze Reihe von Autos, die für die Relevanz seiner Daten verantwortlich sind. Das Beste, was wir hier tun können, ist, häufiger mit OpenStreetMap-Extrakten zu synchronisieren. Nun, wünsche ihrer Gemeinde viel Glück.

Auf einer niedrigeren Ebene gibt es jedoch aufgrund der chaotischen Bearbeitung von Karten eine Reihe anderer Probleme, die vollständig gelöst werden können. Dies betrifft hauptsächlich Junk-Daten und Duplikate. Die Vielfalt dieses Durcheinanders ist bemerkenswert: Der gleiche Ort kann dreimal auf unterschiedliche Weise beschrieben werden, Institutionen haben keine Namen, Typen und Tags sind falsch geschrieben und so weiter. All dies hat keine einheitliche Lösung, vielmehr ist ein Komplex von Maßnahmen erforderlich, um den Inhalt zu systematisieren. Zum Beispiel habe ich die folgenden Bedingungen:

Es gibt mehrere Synonyme und Variationen desselben Tags -> wir beschreiben Alias-Wörterbücher (z. B. Parken, Parkplatz, Parkeintritt usw.).

Es gibt mehrere Orte mit demselben Typ und denselben Koordinaten:

  • Wenn jeder keinen Namen hat -> wird der Ortstyp zum Namen
  • nur einer hat einen Namen -> nimm ihn
  • Jeder hat einen Namen und sie sind anders -> nimm den Nachnamen chronologisch

Es gibt mehrere Orte mit demselben Typ und fast denselben Koordinaten:

  • Wenn jeder keinen Namen hat -> höchstwahrscheinlich Duplikate, werden wir nicht komplizieren. Verschmelzen Sie zu einem Punkt mit gemittelten Koordinaten, in dem der Ortstyp zum Namen wird. Ein Mann wird kommen und verstehen
  • nur einer hat einen Namen -> das gleiche, nur jetzt haben wir schon einen Namen
  • Jeder hat einen Namen und sie sind anders -> aber dies ist ein Cluster

Der Cluster ist in unserem Fall eine Karte, in deren Kopf mehrere Stellen beschrieben sind. Meist handelt es sich dabei um Gruppen von Geschäften oder Tankstellen in der Nähe. Oder ein Geldautomat befindet sich innerhalb des Bankgebäudes und der andere außerhalb. Sie stellen für uns keine Schwierigkeit dar: Wir berechnen die durchschnittlichen Koordinaten und zeichnen die Route zu ihnen auf. In der Benutzeroberfläche zeigen wir dies sauber und einfach:



Schnittstelle und Design


So kam es mir vor, dass ich mir vor Beginn der Entwicklung normalerweise schon das endgültige Bild vorstelle. Gleichzeitig zeichne ich nicht gerne Diagramme und Konzepte, sondern ziehe es vor, ein Design parallel zu Funktionen zu erstellen (wenn dies natürlich mein Projekt ist). Dieser iterative Ansatz ist einerseits sehr cool, da Sie andererseits zwischen Grafik und Code wechseln können - manchmal müssen Sie alles zu oft wiederholen. Das gleiche passierte hier: Ich habe anscheinend hundert Mal die einfachste Oberfläche geschaufelt. Ausgehend von Kleinigkeiten wie Symbolen und Einrückungen bis hin zur Zusammenstellung von Karten, Menüs usw. Ich werde nicht alles beschreiben, ich werde schnell auf wichtige Themen eingehen. Wenn Sie nicht an Design interessiert sind, können Sie es gerne überspringen.

Farbpalette


Lange konnte ich nicht verstehen, was ich mit der Palette anfangen sollte. Ich wollte wirklich die Kategorien von Orten in verschiedenen Farben kennzeichnen, mit Ausnahme von Grün - ich habe beschlossen, es als Akzent zu speichern. Ich habe leicht unterscheidbare und satte Farben gewählt, alles scheint in Ordnung zu sein. Nach einiger Zeit stellte ich fest, dass das Blau für die Tankstelle das Blau widerspiegelt, das auf der Karte die Position des Fahrers angibt. Er hat nichts damit gemacht, es so belassen wie es ist - aber der interne Perfektionist ist unzufrieden.



"Auf dem Weg" und "Du bist in der Nähe"


Nachdem die Logik erschienen war, die die Bewegungsrichtung des Fahrers bestimmt, wurde es möglich, die Routen in „entlang des Weges“ und den Rest zu unterteilen. Wie ich bereits sagte, wird dies durch den ersten Abschnitt der Route bestimmt, der an den Ort verlegt wurde: Stimmt er mit dem letzten Abschnitt der Route des Fahrers überein? Wenn ja, dann gehen wir schon an diesen Ort. Dann stellte sich die Frage, wie dies in der Benutzeroberfläche angezeigt werden soll. Zusätzlich zu den Änderungen in der Karte, die ich oben beschrieben habe, kam die Idee auf das Schild „Unterwegs“ (oder „Unterwegs“ auf Englisch - es scheint, dass sie dasselbe bedeuten). Ich verwende denselben Würfel für ein anderes Szenario: Wenn die Entfernung zum gefundenen Ort weniger als 25 Meter beträgt. Dann macht es keinen Sinn, eine Wegbeschreibung zu erhalten. Ich verstecke die Karte und schreibe, dass Sie bereits in der Nähe sind („Sie sind in der Nähe“ / „Schauen Sie sich um“).



Gemeinschaftskarte


Zu Beginn der Debug-Entwicklung habe ich eine statische Karte von Google verwendet, um das Isochron und die Ergebnisse anzuzeigen. Dann rannte er lange mit ihr herum und wusste nicht, wo er bleiben sollte: Es scheint, dass die Karte eine interessante Sache ist, aber es scheint, dass sie keinen Platz einnehmen sollte. Darüber hinaus wollte schmerzlich nicht einmal in einer solchen Kleinigkeit auf Google angewiesen sein. Am Ende entfernte ich dann die Karte, aber nach einiger Zeit begann ich Routenkarten zu generieren und stellte fest, dass ich selbst technologisch auf die große Karte „gewachsen“ war. Es stellte sich heraus, dass dies nicht so schwierig war, obwohl die gemeinsame Karte bislang das ressourcenintensivste Teil des gesamten Projekts bleibt. Und damit es keinen Platz in der Benutzeroberfläche einnimmt, lege ich die Karte auf eine separate Seite (sie wird seltener gezogen).



Lokalisierung


Für eine normale Produktion ist eine Lokalisierung erforderlich. Es ist immer einerseits eine sehr direkte und einfache Arbeit, andererseits - wenn Sie damit beginnen, kriechen Scharen von Kakerlaken von überall her. In meinem Fall war der Hauptinhalt von OSM bereits lokalisiert, sodass nur noch Ortstypen und Schnittstellenelemente übrig blieben. Mit Ausnahme einiger Stecker (lange Zeit konnte ich keine Matrize „Unterwegs“ formulieren) war alles einfach. Es ist erwähnenswert, dass Ortsnamen sowohl 2 als auch 3 Zeilen belegen können und möglicherweise nicht in Bildschirme mit geringer Breite passen. Daher hat das Widget auto_size_text hier geholfen. Ich empfehle es in angemessenen Grenzen.



Aber auf der technischen Seite war es nicht so glatt. Bisher ist die Intl_translation- Bibliothek fast die einzige Lösung für die Lokalisierung unter Flattern, und es ist seltsam. , . , (!) - , … , , .

, , — - . , , .

Erscheinungsdatum


, , . , -, — . , . , . .

- support androidx: - , , . , — . : . , , .

, , :






. - — . , №2:

  • (, )
  • ,
  • , (, -)
  • ( — - , )

Android Auto Apple CarPlay. , .

, .

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


All Articles