PostgreSQL kann Geo-Daten — auf zwei Ebenen. Built-in gibt es ein paar einfache Geometrie-Typen wie point und polygon (gut für „ist Punkt X im Rechteck Y?”). Für ernsthafte Geo-Anwendungen ist aber PostGIS die de-facto Standardlösung. Dieser Artikel zeigt das eingebaute Minimum und worauf man bei PostGIS achten sollte.

Eingebaute Geometrie-Typen

Postgres bringt von Haus aus diese Typen mit:

TypBeschreibungBeispiel
point2D-Punkt'(13.40, 52.52)'
lineunendliche Linie'{1, 1, 0}' (Form Ax+By+C=0)
lsegLiniensegment (zwei Punkte)'[(0,0), (1,1)]'
boxAchsen-paralleler Rechteck'((0,0), (1,1))'
pathoffene oder geschlossene Polylinie'[(0,0),(1,1),(2,0)]'
polygonPolygon'((0,0),(1,0),(1,1),(0,1))'
circleKreis (Mittelpunkt + Radius)'<(0,0), 1>'
SQL Built-in geometry
CREATE TABLE locations (
    id   bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name text NOT NULL,
    pos  point NOT NULL              -- (Längengrad, Breitengrad)
);

INSERT INTO locations (name, pos) VALUES
    ('Berlin Mitte',   point(13.40, 52.52)),
    ('München Marienplatz', point(11.58, 48.14)),
    ('Hamburg Rathaus', point(9.99, 53.55));

Built-in-Operatoren

SQL Distanz und Containment
myapp=> SELECT
          name,
          pos <-> point(13.40, 52.52) AS dist_to_berlin
        FROM locations
        ORDER BY dist_to_berlin;
         name         | dist_to_berlin
----------------------+-----------------
 Berlin Mitte         | 0
 Hamburg Rathaus      | 4.092…
 München Marienplatz  | 4.778…

<-> ist der „Distanz”-Operator. Aber Achtung: dieser rechnet euklidisch — als wäre die Erde flach. Für kleine Bereiche ist das eine Näherung, für größere total daneben. Die obigen Werte sind nicht „Kilometer”, sondern Grad — und nicht mal das ist sinnvoll, weil Längen- und Breitengrad-Distanzen unterschiedlich groß sind.

Für ernsthafte Distanzen → PostGIS.

Trotzdem — für lokale Anwendungen kann’s reichen:

SQL Punkt im Rechteck
-- Welche Locations liegen in Deutschland (vereinfachte Bounding-Box)?
SELECT name, pos
FROM locations
WHERE pos <@ box(point(5.87, 47.27), point(15.04, 55.06));

<@ heißt „enthalten in”. Auf einem Polygon-Typ funktioniert das genauso für „Punkt im Polygon”-Tests.

Wann reicht das Built-in?

Die Built-in-Typen sind okay für:

  • Sehr kleine Bereiche (eine Stadt, ein Gebäude-Grundriss), wo die euklidische Näherung kein großer Fehler ist.
  • Spielerische Anwendungen: 2D-Grafik, Layout-Engines, Zeichenprogramme.
  • Containment-Checks ohne metrische Distanzen: „Ist dieser Punkt im Rechteck?” ohne Aussage über Längeneinheiten.

Sie sind nicht geeignet für:

  • Reale Geo-Anwendungen (Karten, Routing, Geo-Suche).
  • Distanzen über mehr als ein paar Kilometer.
  • Korrekte Erddarstellung (Kugel statt Ebene).
  • Geo-Indexe — die Built-in-Typen unterstützen GIST-Indexe, aber ohne PostGIS’ Optimierungen.

Für all das gibt’s PostGIS.

PostGIS — die richtige Geo-Lösung

PostGIS ist eine Extension, die PostgreSQL um echte Geo-Funktionalität erweitert: Erd-Kugel-bewusste Distanzen, Routing, Karten-Tile-Generierung, Topologie. Wenn du irgendwas mit Karten, GPS-Daten oder Geo-Suche machst — PostGIS.

SQL Installation
CREATE EXTENSION postgis;

Damit bekommst du zwei neue Typen: geometry (planares 2D) und geography (Erd-Kugel).

SQL Tabelle mit PostGIS-Spalte
CREATE TABLE locations (
    id        bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name      text NOT NULL,
    location  geography(POINT, 4326)            -- Erd-Punkt, WGS84
);

INSERT INTO locations (name, location) VALUES
    ('Berlin', ST_MakePoint(13.40, 52.52)),
    ('Paris',  ST_MakePoint(2.35, 48.86)),
    ('Rom',    ST_MakePoint(12.50, 41.90));

SRID 4326 ist WGS84 — das Koordinatensystem, das GPS und Google Maps verwenden. ST_MakePoint(longitude, latitude) ist der Konstruktor; die Reihenfolge Longitude, Latitude überrascht oft (mathematisch ist’s (x, y) = (lng, lat)).

Distanz auf der Erdkugel

SQL
myapp=> SELECT
          name,
          ST_Distance(
            location,
            ST_MakePoint(13.40, 52.52)::geography
          )::int AS distance_meters
        FROM locations
        ORDER BY distance_meters;
  name  | distance_meters
--------+-----------------
 Berlin |               0
 Paris  |          878477
 Rom    |         1183587

Mit geography rechnet PostGIS auf der Erdkugel — die Werte sind echte Meter. 878 km von Berlin nach Paris, 1184 km nach Rom. Korrekt.

„Locations im Umkreis von X” — der Klassiker

SQL Geo-Suche im Radius
-- Alle Locations innerhalb 1000 km von Berlin
SELECT name, ST_Distance(location, berlin)::int AS m
FROM locations,
     (SELECT ST_MakePoint(13.40, 52.52)::geography AS berlin) b
WHERE ST_DWithin(location, berlin, 1000000)  -- 1 Mio. Meter = 1000 km
ORDER BY location <-> berlin;                 -- nach Distanz sortieren

ST_DWithin ist die Magie — es kann Indexe nutzen. Ein simples WHERE ST_Distance(...) < 1000000 müsste die Distanz für jede Zeile ausrechnen (Sequential Scan). ST_DWithin mit einem GIST-Index auf location springt direkt zu den infrage kommenden Zeilen.

SQL GIST-Index für Geo-Suche
CREATE INDEX locations_geo_idx ON locations USING GIST (location);

Mit Index ist eine Umkreis-Suche in Millionen-Zeilen-Tabellen oft unter 100 ms.

geometry vs. geography

PostGIS hat zwei Haupt-Typen:

TypWelt-ModellDistanzeinheitPerformanceWann nehmen
geometryflache EbeneKoordinaten-Einheitensehr schnellLokale Karten, ein Land/Region, eigene Koordinatensysteme
geographyErdkugelMeterlangsamer (komplexere Berechnung)Globale Anwendungen, GPS-Daten, „echte Distanzen”

Für globale Apps mit weltweit verteilten Koordinaten: geography. Für lokale Karten in einem Ländchen: geometry mit einem passenden lokalen SRID (z. B. ETRS89/UTM für Mitteleuropa) — schneller und genauer im Lokalmaßstab.

Polygone und komplexere Shapes

PostGIS unterstützt das volle OGC-Geometrie-Modell: Linien, Polygone (mit Löchern), Multi-Polygone, Sammlungen.

SQL Punkt-in-Polygon
CREATE TABLE districts (
    id    bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
    name  text NOT NULL,
    shape geometry(POLYGON, 4326)
);

-- WKT-Notation (Well-Known Text):
INSERT INTO districts (name, shape) VALUES
    ('Mitte', ST_GeomFromText(
        'POLYGON((13.39 52.50, 13.42 52.50, 13.42 52.53, 13.39 52.53, 13.39 52.50))',
        4326
    ));

-- Welcher Bezirk enthält diesen Punkt?
SELECT name FROM districts
WHERE ST_Contains(shape, ST_GeomFromText('POINT(13.40 52.52)', 4326));

ST_Contains, ST_Intersects, ST_Within etc. sind die Operatoren für Geometrie-Vergleiche. Mit GIST-Index sehr schnell, auch auf Millionen-Polygon-Datensätzen.

Interessantes

point() ist (x, y) — also (Lng, Lat).

Postgres’ point(a, b) und PostGIS’ ST_MakePoint(a, b) erwarten beide (Longitude, Latitude), also Längengrad zuerst. Das überrascht Leute, die Adressen wie „52.52, 13.40” lesen (Lat zuerst). Klassischer Bug-Auslöser. Im Zweifel: ST_MakePoint(LON, LAT) mit Variablen-Namen.

SRID 4326 ist GPS/WGS84.

Die Standard-Wahl für globale Daten. Andere SRIDs gibt’s für lokale Projektionen (EPSG.io listet Tausende). Wer nicht weiß, was SRID nehmen — 4326 ist fast immer richtig, solange du mit Lat/Lng-Daten arbeitest.

Cloud-Postgres und PostGIS.

AWS RDS, Cloud SQL, Azure Database und Heroku bieten PostGIS standardmäßig an — CREATE EXTENSION postgis reicht. Bei kleineren Anbietern erst prüfen. Spezielle PostGIS-Cloud-Anbieter (Crunchy Bridge) bieten besser optimierte Setups für Geo-Workloads.

Karten-Tiles direkt aus PostGIS.

Mit ST_AsMVT() (Mapbox Vector Tiles) liefert PostGIS Vector-Tiles direkt aus der DB — keine Extra-Tile-Server-Software nötig. Tools wie pg_tileserv und Martin sind dünne Wrapper darum.

Built-in vs. PostGIS — keine Migrations-Brücke.

Wer mit Built-in-point startet und später nach PostGIS migrieren will, muss alle Spalten umstellen. Empfehlung: bei jedem Geo-Projekt direkt PostGIS — auch wenn man am Anfang nur 100 Locations hat. Spätere Migration ist Mehrarbeit.

GIST-Index ist Pflicht für Geo-Performance.

Ohne GIST-Index laufen Geo-Queries als Sequential Scan — bei großen Tabellen unbenutzbar. CREATE INDEX … USING GIST (location) muss bei jeder Tabelle mit Geo-Spalte sein. Bei sehr großen Daten lohnt sich auch SP-GIST oder das experimentelle postgis_tiger_geocoder-Pattern.

Weiterführende Ressourcen

Externe Quellen

/ Weiter

Zurück zu Datentypen

Zur Übersicht