Postleitzahl-Umkreissuche mit PHP und OpenGeoDb

Karte: Verwaltungsbezirke Leipzig von Elsaxo, Wikimedia Commons,   Creative Commons Attribution-Share Alike 3.0 Unported license / Magnifying Glass Color: antsorin, Openclipart.org, Public Domain License

Karte: Verwaltungsbezirke Leipzig von Elsaxo, Wikimedia Commons, Creative Commons Attribution-Share Alike 3.0 Unported license / Magnifying Glass Color: antsorin, Openclipart.org, Public Domain License

Für ein kleines Projekt brauchte ich neulich eine einfache Möglichkeit, eine Umkreissuche auf Basis einer Postleitzahl mittels PHP zu realisieren. Was man dafür benötigt ist klar: Postleitzahlen sowie deren geografische Verortung. Beides liefert die OpenGeoDB, praktischerweise sogar unter Public Domain Lizenz. In deren Wiki lassen sich auch fertige Lösungen für eben diese Aufgabenstellung der PLZ-Umkreissuche finden – allerdings alle unter Zuhilfenahme einer SQL-Datenbank. Ich wollte jedoch eine sehr schlanke Lösung, die auch ohne Datenbank auskommt. Daher habe ich eine eigene kleine Umkreissuche auf Basis von Postleitzahlen geschrieben; als Grundlage und Inspiration diente mir dabei das OgdbDistance-Skript von Manuel Hoppe.

Um genau zu sein: Meine Lösung ist eigentlich keine Umkreissuche, sondern vielmehr eine „Um-Quadrat-Suche“ da der Zielbereich quadratisch abgesteckt wird. Bei kleinen Radien (die man bei einer Umkreissuche in der Regel verwendet) liefert das kleine PHP-Skript aber brauchbare Ergebnisse. Und da Postleitzahlengebiete sowieso unregelmäßig geformt sind, sich eigentlich nicht auf eine einzige Koordinate reduzieren lassen und Straßenverläufe bei der Distanz-Eingrenzung nicht berücksichtig werden ist quasi „by design“ schon eine gewisse Unschärfe vorhanden. Von daher würde ich das Skipt mal als „gut genug“ einstufen.

Ursprünglich hatte ich natürlich im Blick, einfach alle Postleitzahlen in der Postleitzahl-Koordinaten-Datei (PLZ.tab) zu durchlaufen und mittels Manuel Hoppes OgdbDistance-Skript die Entfernungen zur Basis-Postleitzahl zu ermitteln. Damit wäre man theoretisch näher an einem UmKREIS aber duch die eben beschriebene Unschärfe erschien mir die quadratische Eingrenzung als ausreichend – bei (vermutlich) geringerem Rechnenaufwand.

Funktionsweise

Das PHP-Skript nimmt im Wesentlichen einen Postleitzahl und eine Entfernung in Kilometer entgegen, errechnet aus den Koordinaten der übergebenen Postleitzahl und der Entfernung die Maximal- und Minimal-Koordinaten für alle Postleitzahlen innerhalb des damit eingegrenzten Gebietes und durchläuft mit diesen Angaben die gesamte Postleitzahl-Koordinaten-Datei (PLZ.tab). Diese Postleitzahl-Koordinaten-Datei wird beim ersten Aufruf des Skripts – nach dem Vorbild von Manuel Hoppes OgdbDistance-Skript von http://fa-technik.adfc.de/code/opengeodb/ herunter geladen. Man mag sich in diesem Zusammenhang fragen, warum die Dateien von fa-technik.adfc.de abgerufen werden und nicht von opengeodb.org. Auf die Frage habe ich auch keine Antwort, aber fa-technik.adfc.de wird auch in der offiziellen Doku von OpenGeoDB als Quelle genannt. Von daher ist das offenbar in Ordnung.

Hinweis: Zum Herunterladen der aktuellen PLZ.tab wird die PHP-Funktion file_get_contents() verwendet. Auf etlichen (Shared)-Servern ist der Download externer Quellen über diese Funktion jedoch via allow_url_fopen=off unterbunden. In diesem Fall muß die PLZ.tab händisch herunter geladen und im Ordner des Skripts abgelegt werden.

Beispielaufrufe

Im Download-Paket befindet sich eine beispiel.php, die die grundlegende Verwendung der PLZ-Umkreissuche aufzeigt: Zuerst muß die (Mini-)Biblothek in das eigene Skript eingebunden werden:

require_once("ogdbPLZnearby.lib.php");

Anschließend steht die Funktion ogdbPLZnearby() zur Nutzung bereit. Als Parameter werden der Funktion die Ausgangspostleitzahl und der Umkreis (in Kilometer) übergeben. ogdbPLZnearby() liefert als Ergbenis ein Array mit den Postleitzahlen im Umkreis zurück – auf Wunsch auch mit dem Namen der Stadt. Nehmen wir mal an, wir suchen Postleitzahlen im Umkreis von 10 Kilometer um meine Wahlheimatstadt Naunhof (04683):

Ausgabe der Postleitzahlen im Umkreis

Mit folgendem Aufruf wird ein Array sämtlicher Postleitzahlen im definierten Umkreis zurück geliefert:

ogdbPLZnearby('04683','10')

Ausgabe der Postleitzahlen inkl. Ortsnamen im Umkreis

Über eine dritten, optionalen Parameter (boolean, true/false) kann bei Bedarf auch die Rückgabe der ermittelten Städtenamen ausgelöst werden:

ogdbPLZnearby('04683','10',true)

Die mitgelieferte beispiel.php zeigt beide Anwendungsfälle, schaut einfach mal rein und probiert aus.

Performance

Ohne das objektiv gemessen zu haben, ist das Durchwühlen einer Datei im Dateisystem vermutlich weniger flott als die auf opengeodb.org vorgeschlage SQL-gestützte Variante. In meinem Anwendungsfall funktioniert das Skript allerdings ohne merkliche Latenzen und kommt dabei komplett ohne Datenbank aus. Ob das auf hochfrequentierten Websites immernoch so gut funktioniert? Keine Ahnung, probiert es aus.

Danksagung

Mein Dank geht an Manuel Hoppe, der mit ogdbDistance nicht nur die Code-Basis für mein Skript geliefert hat sondern auch so freunlich war, mein kleines Werk für die Postleitzahl-Umkreissuche einem kurzen Review zu unterziehen und mir nützliche Tipps zu liefern. Danke dafür und für den sehr freundlichen Kontakt.

Ebenso geht mein Dank an das OpenGeoDB-Projekt, das seine fantastische Arbeit unter der liberalen Public-Domain-Lizenz zur Verfügung stellt. Chapeau!

[UPDATE, 13.06.2014] – Verbesserte Umkreissuche

Bei einem aktuellen Projekt wünschte der Kunde die zusätzliche Anzeige der Entfernung in den Ergebnissen der Postleitzahl-Umkreissuche. Für die Entfernungsbestimmung der durch die Umkreissuche ermittelten Postleitzahlen habe ich dort daher zusätzlich Manuel Hoppes ogdbDistance() genutzt. Dabei zeigte sich allerdings die Differenz zwischen der quadratförmigen Umkreissuche von ogdbPLZnearby() und der kreisförmigen Berechnung von ogdbDistance() – in den auf z.B. 20 km beschränkten Suchergebnissen wurden dann auch Entfernungen knapp über der 20km-Grenze angezeigt. Das war natürlich unschön und für den Nutzer verwirrend, deshalb habe ich mich für eine Überarbeitung von ogdbPLZnearby() entschieden.

Bei Bedarf kann nun statt der ogdbPLZnearby.lib.php die überarbeitete Bibliothek ogdbPLZnearby2.lib.php genutzt werden. Dort werden nicht wie bisher einfach alle Postzeitzahlen deren Koordinaten zwischen definierten Minimum- und Maximalwerten liegen eingesammelt, sondern nach dem Vorbild von Manuel Hoppes ogdbDistance() tatsächlich die Entfernungen zwischen den Koordinaten berechnet und aller Treffer innerhalb des definierten Radius in das Ergebnis-Array übernommen. Und weil die Entfernung nun einmal berechnet wird, lässt diese sich nun auch durch einen zusätzlichen Parameter im Funktionsaufruf in das Ergebnis-Array übernehmen um sie später weiter zu verwenden – oder, wie in meinem Fall, in einer Trefferliste anzuzeigen.

In der neuen ZIP-Datei unten befinden sich nun beide Bibliotheken und jeweils eine Beispieldatei, die die Funktionsweise zeigt. In Sachen Performance konnte ich keine signifikante Verschlechterung durch den Einsatz der neuen Bibliothek ogdbPLZnearby2.lib.php feststellen – Computer können halt einfach sehr gut und sehr schnell rechnen. Von daher gibt es eigentlich keinen Grund weiter die alte Bibliothek zu verwenden. Ich habe darauf geachtet, die Rückgabewerte der neuen Bibliothek identisch gegenüber der alten zu halten – bestehende Implementierungen können somit einfach durch Austausch der Bibliothek von dem neuen Verfahren profitieren.

Noch ein Hinweis: Da die Entfernungsbestimmung auf Basis eines Koordinatenpaares je Postleitzahl erfolgt und dabei nicht die tatsächliche Verkehrsführung berücksichtigt wird, kann die ermittelte Entfernung insbesondere im ländlichen Raum (große PLZ-Gebiete, geringe Straßendichte) durchaus größere Differenzen zur realen (Auto-)Entfernung aufweisen. In der Regel dürfte das hinnehmbar sein aber man sollte diese Tatsache immer im Hinterkopf behalten.

Download der PLZ-Umkreissuche

Aktuelle Version (13.06.2014)
ogdbPLZnearby-2014-06-13.zip

Alte Version (5.11.2013)
ogdbPLZnearby-2013-11-05.zip

Das Skript ist nach besten Wissen getestet und erfüllt in meinem Szenario die gewünschten Anforderungen. Dennoch erfolgt die Nutzung des auf eigene Gefahr!

35 Kommentare Schreibe einen Kommentar

  1. Hallo,
    GENAU DAS hab ich gesucht, vielen Dank dafür!
    Simpel in der Anwendung, die Performance ist völlig in Ordnung.

    Prima…

    Gruß
    Christian

    • Vermutlich nicht. Denn außer dem Zurückliefern von Postleitzahlen im Umkreis macht das Skript ja nix – in der Regel wird man das irgendwie in seine eigene Applikation „verwursten“ müssen. So stand-alone bringt das wenig.

      • Ich brauchte dieses Tool für ein WordPress-Projekt und kann den Code der Umkreissuche gerne demnächst aus dem Gesamtprojekt in ein einzelnes kleines Skript packen. Das wäre dann aber mehr ein Framework, als ein fertiges Plugin.

          • Ich bin gerade dran, habe aber viel um die Ohren, sodass ich nicht vor Ende diesen des Moants fertig werde. Es wird aber ein Minimal-Plugin, das alle grundlegenden Funktionen zur Verfügung stellt, die man für eine Umkreissuche in WordPress benötigt:
            1. Adressfelder für die Registrierung neuer Accounts und im Profil.
            2. Widget für die Integration einer Karte, auf der die nächsten Ergebnisse gezeigt werden, die anhand der HTML5-Standortbestimmung ermittelt werden.
            3. Ein Shortcode für die Integration einer entsprechenden Karte.
            4. Eine Umkreissuche, die anhand von PLZ und einem Suchstring, der nach passenden Benutzernamen sucht.

            API wird später auf meiner Seite veröffentlich und ein Bereich für Bug-Report und Änderungsvorschläge im Git eingerichtet.

  2. Kann man OpenGeoDB denn bedenkenlos in einem ernsten Projekt verbauen? Ich meine damit die Akualität der Daten?! Hast du diesbezüglich schon negative Erfahrungen sammeln können? Ich bin gerade an etwas dran und mir ist die Verwendung irgendwie zu heiß, da mir mein Auftraggeber den Kopf abhackt, wenn es da zu beschwerden kommt :-D

    • Hallo Sladjan,

      also ich habe das selbst schon verbaut und bin damit zufrieden (und der Auftraggeber auch). Aber da es, wie im Artikel beschrieben, technisch bedingt eine gewisse Unschärfe in der Genauigkeit gibt (Betrachtung der Luftlinie ohne tatsächliche Straßenführung, ganze Postleitzahlengebiete mit einer einzigen Koordinate adressiert), muss man halt abwägen ob das im jeweiligen Projekt akzeptabel ist oder nicht. Die Aktualität der Daten spielt hier vermutlich eine eher untergeordnete Rolle – so schnell ändern sich ja Postleitzahlengebiete nicht. Wenn Du bei Deinem Auftraggeber dahingehend kein gutes Gefühl hast, dann nutze lieber eine exaktere Datenbasis z.B. über die Google Maps API.

  3. Super Script – danke Dir.

    Müsste nun allerdings das gleiche mit der Schweiz realisieren.

    Kann ich dann die PLZ Liste austauschen? Woher bekomme ich die PLZ Daten samt Location Daten der Schweiz?

    Danke Euch
    Gruss
    Moritz

  4. Grüß Dich,
    tolles Script. Super.
    Ist es möglich das ich auch angeben kann das es mir
    nur im Rahmen von 10-20 km radius sucht?

    Wenn ich das Script einmal mit 10 und einmal mit 20 suchen lasse
    bekomme ich die 10 KM Suchergebnisse ebenfalls bei den 20KM.

    Gibt es dort eine Möglichkeit?

    Grüße Thomas

    • Hallo Thomas,

      hmm…. ich glaube die Art der Datenverarbeitung ist nicht geeignet, um das gewünschte Ergebnis in einem Durchgang zu ermitteln. Aber Du könntest das Skript intern zweimal ausführen, erst für 20 km Umkreis (Ergebnis dann in Array speichern) und dann für 10 km Umkreis (Ergebnis in einem zweiten Array speichern). Anschließend könntest Du die PHP-Funktion array_diff() verwenden, um beide Arrays zu vergleichen. Die Funktion liefert ein neues Array zurück, welches nur die Elemente enthält, die nicht doppelt vorkommen – also die 20km-Treffer minus die 10km-Treffer. Dann hättest Du das was Du brauchst und könntest damit dann weiter arbeiten.

  5. Guten Morgen André,

    das wäre eine Möglichkeit, jedoch ist die bei der zu erwartenden Menge an ergebnissen einfach zu umständlich.

    Dachte das man deine funktion abändern kann.

    Danke für deine Antwort.

    • Hallo Thomas,

      mir ist doch noch was eingefallen:
      In Zeile 76 der ogdbPLZnearby2.lib.php wird geprüft, ob die Entfernung kleiner/gleich der übergebenen Kilometerzahl ($distance) ist. Diese Prüfung könntest Du so erweitern, dass sie zusätzlich mit größer-als auf eine zweite Entfernung prüft.

  6. Hallo André!
    Echt genial. Klappt einwandfrei. Vielen Dank!
    Habe aber noch ne Anregung/Frage: Wie müsste man das Skript anpassen um die Distanz zwischen zwei PLZs zu erhalten? Das müsste doch auch irgendwie gehen.
    Also ich übergebe einer Funktion zwei PLZs und bekomme die Distanz in KM zurück.

    • Hallo nicmare,

      in meiner Lösung verwende ich ja intern das Skript „OgdbDistance“ von Manuel Hoppe (Link oben im Artikel). Dieses macht genau das was Du brauchst – nämlich Entfernungen zwischen zwei PLZ zu berechnen. Verwendet man dann einfach so, z.B.: $distance = ogdbDistance(47443,47058);. Du brauchst also nicht mein Skript umschreiben sondern verwendest einfach das OgdbDistance-Skript.

      • Also ich hab jetzt das Script von Manuel geladen und verwende jetzt quasi beide gleichzeitig! Deins und Manuels. Das klappt. Oder ist das zu umständlich? Deine Antwort war mir irgendwie zu schwammig.

        • Das kommt ganz darauf, was Du genau vorhast. Möchtest Du eine Umkreissuche (optional mit Angabe der Entfernung zum „Suchtreffer“) dann verwende mein Skript. Möchtest Du nur Entfernungen zwischen zwei PLZs bestimmen, nimm das von Manuel Hoppe. Wenn Du mein Skript verwendest, kannst Du durch setzten des vierten Wertes in der Function ogdbPLZnearby() auf true die Entfernung zu den jeweils ermittelten PLZ zurückgeben lassen. Ein Beispiel dazu findest Du im mitgelieferten Beispiel-Skript ganz unten.

          • Ja also es ist so: Ich habe in WP eine Liste mit CPTs (Golfplätze). Alle haben ein Custom Field für die PLZ. Nun habe ich ein Formular wo ich meine eigene PLZ eingebe und mein Script macht nen wp_query mit allen Plätzen (PLZs) in der Nähe. Klappt alles wunderbar. Jetzt will ich nur noch bei jedem Platz die Entfernung zur gesuchten PLZ angeben. Für dieses Gesamtvorhaben benutze ich zur Zeit zwei Skripte:
            require_once(„tools/ogdbPLZnearby2.lib.php“);
            require_once(„tools/ogdbDistance.lib.php“);
            Weil ich kann mir zwar mit ogdbPLZnearby2 die Distanzen zu den NachbarPLZ anzeigen lassen aber das nützt mir an der Stelle nichts. Weil ich brauch erstmal nur die PLZs für die Abfrage der WP-DB. Und einen WP-Query kann ich ja dann nur nach dem Feld „PLZ“ sortieren. Das ist leider zu ungenau. Daher jetzt die Notlösung eine zweite Abfrage nur für die Distanz zu starten.

          • Okay, verstehe. Ein Lösungsvorschlag wäre folgender:
            #1. Du nutzt ogdbPLZnearby() mit dem vierten Parameter auf true, damit die Entfernung zum Ziel mit in das Ausgabe-Array geschrieben wird

            #2. dort holst Du Dir PLZs für die Deine wp_query raus

            #3.zusätzlich speicherst Du noch die von ogdbPLZnearby() ermittelten Entfernungen in ein neues Array: $distance[{PLZ}] = {Entfernung}

            #4. bei der Ausgabe der passenden Golfplätze kannst Du dann die Entfernung z.B. via echo "Entfernung: ".$distance[{die PLZ aus dem Custom Field}]."km"; ausgeben

          • Gute Idee André. Werde ich so machen. Merci. Irgendwie war mir unwohl bei der bisherigen Lösung. Ist es ja quasi ne doppelte Distanzabfrage gewesen…

  7. Hallo,

    erstmal vielen Dank für die Mühen die du dir gemacht hast. Für eine Projektarbeit muss ich eine Menge an Daten auswerten und hierbei möchte ich diese gerne anhand der PLZ eingrenzen können. Meine Daten liegen in einer EXCEL-Tabelle vor. Bei meiner Recherche wie ich die Daten eingrenzen kann, bin ich auf opengeodb und darüber auf dieses Skript gestoßen.

    Ich bin nicht sehr vertraut mit php, aber da ich ansonsten keine andere Möglichkeit gefunden habe, will ich es mal versuchen. Ich habe die zip datei lokal gespeichert und entpackt und per xampp konnte ich auch die php dateien öffnen.

    Ich versuche im Prinzip eine, hoffentlich ganz einfache, Sache zu realisieren. Ich möchte eine PLZ eingeben, den Umkreis und dann sollen mir alle PLZ mit diesen Kriterien in einer Liste ausgegeben werden, damit ich diese in EXCEL einfügen und damit weiterarbeiten kann.

    Kann mir jemand einen Tipp geben wie ich dies tun kann oder kennt jmd eine Seite auf der dies erklärt wird?

    Über jede Hilfe wäre ich dankbar.

    MfG,
    Weezo

    • Hallo Weezo,

      das Skript macht genau das was Du suchst und wenn Du in der Lage warst einen XAMPP auf zu setzen, dürftest Du damit klar kommen. Ich gebe Dir gern Tipps dazu, bin aber jetzt und die nächsten Tage unterwegs, nur mit Smartphone als Kommunikator. Keine ideale Vorraussetzung für einen längeren Text. Wenn es noch was Zeit hat, schicke mir bitte als Gedächtnisstütze eine Nachricht über das Kontaktformular. Ich melde mich dann nächste Woche.

      • Hallo digitalbricks,

        danke für deine Antwort.

        Ein Freund hat mir gestern noch erklärt, dass ich nur in der php datei den Radius und die PLZ ändern muss um mein gewünschten Ergebnis zu bekommen.

        Das hat dann wunderbar geklappt und mein Problem ist gelöst.

        In jedem Fall: Danke für die schnelle Antwort und Hilfsbereitschaft. :)

        Gruß,
        Weezo

  8. Hallo Andre,

    ersteinmal danke für das tolle Script. Es funktioniert sehr gut und auch mit top Performance.

    Auf unserer Website möchten wir für unseren Newsletterversand nicht nur Deutschland, sondern auch Österreich und die Schweiz in die Umkreissuche mit einbeziehen.

    Soweit ich den Code richtig interpretiere, ist es vermutlich nicht damit getan, nur die PLZs der anderen Länder in die Datenbank aufzunehmen.

    Daher die Frage: hast Du oder vielleicht auch einer der anderen „Mitstreiter“ das eventuell schon irgendwie umgesetzt?

    Viele Grüße,
    Oliver

    • Hallo Oliver,

      ich selber habe das noch nicht umgesetzt, sollte aber prinzipiell so funktionieren: Die vom Skript genutzte PLZ.tab enthält ja nur die Daten für Deutschland. Entsprechende Daten für Österreich kannst Du jedoch hier beziehen: http://www.fa-technik.adfc.de/code/opengeodb/ (AT.tab). Diese Daten müsstest Du dann zunächst in eine Form bringen, analog der PLZ.tab. Um Konflikte mit den deutschen PLZs zu vermeiden müsste dann im Umkreissuche-Formular auf der Site eine Auswahl zwischen Deutschland und Österreich ermöglicht werden und im Skript dann eine Weiche, die – je nach Auswahl – die PLZ.tab (für Deutschland) oder die AT.tab (Österreich) verarbeitet.

  9. Hallo,
    super Script, genau danach habe ich gesucht :)
    Manuel Hoppes OgdbDistance-Skript mit der Datei DE.tab hat einen kleinen Test in meiner Umgebung leider nicht ganz bestanden, da ein paar Ergebnisse gefehlt haben. Dein Script funktioniert bisher super. Leicht verständlich, super Handhabung, super Performance und (bisher) alle Ergebnisse richtig.

    Vielen lieben Dank :)

  10. Hallo,
    ich habe mich sehr gefreut dieses Script gefunden zu haben. Leider bekomme ich es in WordPress nicht in mein Theme integriert. Mir wird immer wieder die Fehlermeldung „ABBRUCH: konnte Daten nicht laden (http://fa-technik.adfc.de/code/opengeodb/PLZ.tab)“ angezeigt.

    Hat jemand das Script schon mal unter WordPress zum laufen bekommen und kann mir sagen worauf ich achten muss?

    Vielen lieben Dank für die Hilfe.

    • Hallo Marcus,

      schau mal via phpinfo(), ob auf Deinem Server allow_url_fopen aktiviert ist. Das ist die Voraussetzung, das das Skript die Datei mit den Postleitzahlen aus dem Netz abrufen kann. Du kannst das aber auch einfach umgehen: Lade Dir dazu zunächst die PLZ.tab manuell von http://fa-technik.adfc.de/code/opengeodb/PLZ.tab herunter und lege diese im Root-Verzeichnis Deiner WordPress-Site ab. Also da, wo die wp-config.php liegt (alternativ kannst Du in der ogdbPLZnearby2.lib.php des Umkreissuche-Skripts auch durch Ändern der Konstante OGDB_LOCAL_DATA_FILE einen anderen Pfad angeben). Das Skript schaut nämlich immer zuerst nach, ob die Datei bereits lokal auf dem Server vorliegt und versucht erst wenn dem nicht so ist, die PLZ.tab aus dem Internet zu laden.

      Beste Grüße
      André

      • Hallo André,

        vielen lieben Dank für deine schnelle Antwort. Das ablegen der Datei ins Stammverzeichnis hat sehr gut funktioniert.

        Beste Grüße
        Marcus

Schreibe einen Kommentar