CosmoCode ist eine Internetagentur aus Berlin mit Schwerpunkt CMS, Wiki, Web2.0
Mail info@cosmocode.deTel +49 (30) 814504070
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!
Hallo! Vielen Dank! Nach 2 Tagen Suche endlich der richtige Tipp, wie man kostenfrei an Geodaten kommt! Genial! Danke! Gruß Dirk
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.
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
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
Hoch interessanter Artikel, sehr schön. Werd ich mal ausprobieren. Danke!
Über CosmoCode
Abonnieren
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.