Tel +49 (30) 814504070

Detlef Hüttemann
08.02.2006 14:24 Uhr

Postleitzahlen zur Distanzsuche verwenden

Tags:

Location Based Services sind ohne Frage auf dem Vormarsch - was liegt also näher, als auch auf der eigenen Website Geo-Services anzubieten. Im einfachsten Fall geht es darum, die Entfernung zwischen zwei Orten / Personen zu messen, um Trefferlisten nach Nähe zu sortieren.

Genau dieses Problem hatten wir - in diesem Artikel zeige ich, wie man es löst.

Die Aufgabe

Bei unserer Aufgabenstellung geht es wie oben beschrieben darum, Trefferlisten zu sortieren: Die dem Webuser nächsten Personen sollen zuerst erscheinen. Eine hausnaummern-genaue Bestimmung der Geo-Koordinaten ist für uns nicht relevant; uns reicht die Postleitzahl. Dies hat zudem den Vorteil, das man auch vom Webuser nicht alle Daten erfragen muss - die Angabe der Postleitzahl genügt.

Die Idee besteht nun darin, zu allen Postleitzahlengebieten die geometrischen Mittelpunkte zu bestimmen. Anhand dieser Koordinaten lässt sich dann recht einfach die Distanz zwischen zwei Postleitzahl-Gebieten errechnen.

Bei meiner Recherche nach passenden Daten und Frameworks bin ich zunächst auf OpenGeoDB gestossen. In OpenGeoDB liegen nicht nur Postleitzahlen vor, sondern auch die Zuordnungen der Postleitzahlen zu den Verwaltungsstrukturen (Gemeinden, Bundesländern etc.). Leider sind jedoch die Ortsbezüge(Koordinaten) den Städten und nicht den Postleitzahlen zugeordnet - alle Bewohner Berlins haben die gleiche Koordinate. Das hilft natürlich wenig!

Freie Daten

Bei der weiteren Recherche bin ich dann auf MapBender gestossen. MapBender stellt Geo-daten übersichtlich in Maps zusammen, die zudem navigierbar sind. Für uns relevant: MapBender hat unter SourceForge die Geo-Daten der Postleitzahlen freigegeben (Im Bereich Data/PLZ nachschauen!): zu jeder Postleitzahl findet sich dort der Umriss des Gebietes als Polygonzug. Die Daten liegen als Postgres/PostGIS Daten vor (PostGIS erweitert postgres um GIS-Funktionen. PostGIS ist OpenGIS-konform).

Die Datenbank

Also: Postgres Installieren, PostGIS installieren, Daten einspielen! Mit den debian-postgres Paket kamen wir nicht weit - wir scheiterten immer an dem

undefiend symbol: nth

Als wir aber postgres aus den Quellen kompilierten, funktionierte alles fehlerfrei. Nach dem Einspielen der Postleitzahlen in PostGres liegen die Daten dann in der Tabellle post_code_areas:

postgis=# \d post_code_areas
                                TABLE "public.post_code_areas"
  COLUMN  |       Type        |                           Modifiers                           
----------+-------------------+---------------------------------------------------------------
 gid      | integer           | NOT NULL DEFAULT NEXTVAL('post_code_areas_gid_seq'::regclass)
 plz99    | character varying | 
 plz99_n  | integer           | 
 plzort99 | character varying | 
 the_geom | geometry          | 
Indexes:
    "post_code_areas_pkey" PRIMARY KEY, btree (gid)
CHECK constraints:
    "$1" CHECK (srid(the_geom) = 4326)
    "$2" CHECK (geometrytype(the_geom) = 'MULTIPOLYGON'::text OR the_geom IS NULL)
postgis=#

Aus Fläche mach Punkte

Mit den Polygonzügen der Postleitzahlen wollen wir nicht arbeiten; wir erlauben uns eine weitere Vereinfachnung: Wir reduzieren die Gebiete auf den geometrischen Mittelpunkt. Mit PostGIS ist dies ziemlich einfach; die Funktion centroid macht genau das gewünschte:

postgis=# select (point(centroid(the_geom))) from post_code_areas limit 20;
                point                
-------------------------------------
 (13.724898815155,51.05735206604)
 (13.7359957695007,51.0390377044678)
 (13.7440705299377,51.0672779083252)
 (13.8562636375427,51.0991764068604)
 (13.7630171775818,51.128568649292)
 (13.7324242591858,51.0800037384033)
 (13.7086491584778,51.09889793396)
 (13.6885514259338,51.0847797393799)
 (13.6662459373474,51.0664196014404)
 (13.7031207084656,51.0407161712646)
 (13.6616768836975,51.0388126373291)
 (13.7001214027405,51.0265789031982)
 (13.6995062828064,51.0130062103271)
 (13.7365202903748,51.014440536499)
 (13.7652487754822,51.0151195526123)
 (13.8000407218933,51.0164356231689)
 (13.7834916114807,51.0029048919678)
 (13.8136086463928,50.9896984100342)
 (13.8462166786194,50.9910869598389)
 (13.8025555610657,51.0345325469971)
(20 rows)
postgis=#

Zum bequemeren Arbeiten haben wir uns eine Tabelle gebaut, die nur noch Postleitzahlen und Geo-Koordinaten besitzt; Die Umrechnung der Geokoordinaten in Kugelkoordinaten ist bei der Berechnung der Entfernung hilfreich und wird gleich mit erledigt:

postgis=# CREATE TABLE plz_coord (
postgis(# breite float,
postgis(# laenge float,
postgis(# plz character varying
postgis(# );
CREATE TABLE
postgis=# insert into plz_coord select (point(centroid(the_geom)))[1]*pi()/180.0,(point(centroid(the_geom)))[0]*pi()/180.0, plz99 from post_code_areas;
INSERT 0 8270
postgis=# select * from plz_coord limit 20;                                                                                         
       breite      |      laenge       |  plz  
-------------------+-------------------+-------
 0.239544669741332 | 0.891118885701478 | 01067
 0.239738348128304 | 0.890799239797094 | 01069
 0.239879279281836 | 0.891292124327433 | 01097
 0.241837418374185 | 0.891848858143817 | 01099
 0.240209960659799 | 0.892361849540043 | 01109
 0.239676013515103 | 0.891514231958557 | 01127
 0.239261059734833 | 0.891843997874911 | 01129
  0.23891028813574 | 0.891597588899254 | 01139
 0.238520983925282 | 0.891277144046557 | 01157
 0.239164570081235 | 0.890828534568585 | 01159
 0.238441238896751 | 0.890795311634553 | 01169
 0.239112222321951 | 0.890581792697798 | 01187
 0.239101486453989 | 0.890344904522877 | 01189
 0.239747502744395 | 0.890369938236696 | 01217
 0.240248909390077 | 0.890381789303345 | 01219
  0.24085614339777 | 0.890404759067355 | 01237
 0.240567306937722 | 0.890168603261721 | 01239
 0.241092948348909 | 0.889938106673306 | 01257
 0.241662065864151 | 0.889962341438813 | 01259
 0.240900035620738 | 0.890720609967251 | 01277
(20 rows)
postgis=#

Entfernungen messen

Nun benötigen wir nur noch eine Formel zur Berechnung der Entfernung von zwei Kugelkoordinaten:

  dist = 6378.137 * ARCCOS( SIN(a.breite)*SIN(b.breite) + COS(a.breite)*COS(b.breite)*COS(b.laenge-a.laenge) )

Hat man zwei Postleitzahlen, so kann man dies direkt in SQL ausdrücken

postgis=# select 6378.137 * ACOS( SIN(a.breite)*SIN(b.breite) + COS(a.breite)*COS(b.breite)*COS(b.laenge-a.laenge) ) as dist from plz_coord a, plz_coord b where a.plz=10405 and b.plz=10551 ;
       dist       
------------------
 5.84523133900308
(1 row)

Prima: Das sind die Kilometer, die ich mit dem Rad von Moabit nach Prenzlberg radel!

Bookmark and Share

Kommentare

Daniel Knappe
2006/02/16 00:43

Hallo, klasse Artikel, ich suche momentan jemanden der das gegen Bezahlung in unsere Website integriert. Wäre toll wenn Du mir kurzes Feedback an dk@stagehands.de geben könntest. Grüße aus Kreuzberg, Daniel.

Dirk
2006/03/08 19:44

Hallo! Vielen Dank! Nach 2 Tagen Suche endlich der richtige Tipp, wie man kostenfrei an Geodaten kommt! Genial! Danke! Gruß Dirk

Stefan
2006/08/11 16:21

sehr interessanter Beitrag ! Der Umweg über die Polygon-Daten die Koordinaten der PLZs zu berechnen ist zwar etwas umständlich ;) aber kostet ja nichts (ausser Zeit). Das haben wir schon vor Jahren für eine Marktforschungs-Anwendung gemacht. Jetzt wollen wir aus den PLZ-Polygons die Verwaltungsgrenzen darstellen ( z.B . PLZ-Bereich 71xxx). Hast du vielleicht dazu auch einen Idee?? Gruss, Stefan.

Detlef Hüttemann
2006/08/23 13:04

Wieso ist die Berechnung umständlich - wie gehts denn einfacher? Die Herleitung ist doch naheliegend… Ich weiss ja nicht, was Du unter „Verwaltungsgrenzen“ verstehst - die Vereinigungen der Polygone sollten sich jedenfalls in postgis lösen lassen (GeomUnion oder ConvexHull) Gruss, Detlef

Sebastian Müller
2006/08/30 11:52

Danke für die prägnante, gute Beschreibung. Ich würde die Daten gerne in MySQL verwenden - dafür muss ich aber einen Umweg über Postgree machen, verstehe ich das richtig? Wohl allein schon wegen der Konvertierung von Flächen zu Punkten…dann ist ein Export zu MySQL doch aber kein Problem? Das Ganze enthält nur die PLZ, nicht jedoch dazugehörige Ortsnamen, richtig? Vielen Dank, Sebastian

Kai Behncke
2007/08/01 22:49

Hoch interessanter Artikel, sehr schön. Werd ich mal ausprobieren. Danke!

Kommentar erstellen




Wenn Sie die Buchstaben auf dem Bild nicht lesen können, laden Sie diese .wav Datei herunter, um sie vorgelesen zu bekommen.

Über CosmoCode

CosmoCode ist ein Berliner IT-Dienstleister mit starkem Fokus auf Webapplikationen. Zu unseren Schwerpunkten gehören dabei Content Management Systeme, Wikis und Individuallösungen.

Abonnieren

Abonnieren Sie mögen unser Blog? Bleiben Sie informiert mit RSS.