# PostGIS

## Hvad det er

PostGIS er en open source-tilføjelse til databaseserveren PostgreSQL, som gør
det muligt at gemme en form i en kolonne, for eksempel en adresse, et vandløb
eller et matrikelskel, og at spørge med almindelig SQL, hvordan formerne ligger
i forhold til hinanden. Det startede som et forskningsprojekt hos Refractions
Research, et konsulentfirma inden for GIS og databaser, og udvikles i dag som et
selvstændigt open source-projekt.

Udvidelsen installeres i én database ad gangen med `CREATE EXTENSION postgis;`,
og med den følger tre ting.

- **Rumlige typer.** `geometry` beskriver former på en flad plan i det
  koordinatsystem, dataene blev indsamlet i. `geography` beskriver dem på
  jordens krumme overflade og måler i meter.
- **Rumlige funktioner.** En stor familie af funktioner, hvis navne begynder med
  `ST_`: `ST_Intersects` og `ST_Contains` til relationer, `ST_Area` og
  `ST_Length` til målinger, `ST_Transform` til at flytte data mellem
  koordinatsystemer og `ST_AsGeoJSON` til at levere resultatet til et webkort.
- **Rumlige indeks.** PostGIS bygger et R-træ oven på PostgreSQL's GiST-ramme og
  understøtter desuden SP-GiST og blokintervalindeks til særlige slags data.

Formerne følger Simple Features-modellen fra Open Geospatial Consortium, som
også er udgivet som ISO 19125: punkter, linjer og polygoner, deres flerdelte
udgaver og samlinger af blandede typer. Hver værdi bærer et SRID, et tal, der
peger på en række i tabellen `spatial_ref_sys` og fortæller, hvad koordinaterne
betyder. SRID 4326 står for længde- og breddegrad i grader på WGS 84-datummet,
det system GPS regner i. Et projiceret system måler i stedet i meter mod øst og
nord fra et fast nulpunkt og er kun præcist inden for det område, det er lavet
til.

Rasterdata, topologi og visse 3D-operationer ligger i særskilte udvidelser,
`postgis_raster`, `postgis_topology` og `postgis_sfcgal`, så en database kun
bærer de dele, den rent faktisk bruger.

## Hvorfor det betyder noget

PostGIS tvinger et valg frem: Hvor skal de stedbundne data bo? Uden udvidelsen
ender koordinaterne gerne i to talkolonner, mens det egentlige rumlige arbejde
foregår et andet sted. Det kan være applikationskode, der løber alle rækker
igennem for at regne afstande ud, et desktop-GIS, som nogen eksporterer til hver
mandag, eller en separat geotjeneste med sin egen kopi af dataene. Kopierne
glider fra hinanden. Med PostGIS bliver »hvilke kunder bor inden for to
kilometer af et lager, der har varen på hylden« til én forespørgsel, der
forbinder lagerstatus, kundedata og former i samme transaktion, under samme
backup og samme rettigheder som resten af databasen.

Dermed får de rumlige data også de garantier, resten af skemaet allerede har. En
check-constraint kan afvise en ugyldig polygon, før den bliver gemt. Row-level
security kan begrænse et regionalt team til matriklerne i dets egen region. En
import, der går i stå halvvejs, bliver rullet tilbage i stedet for at efterlade
et halvt lag.

Det andet valg står mellem `geometry` og `geography`, og PostGIS-manualen siger
det ligeud. Data, der holder sig inden for en by eller et land, gemmes som regel
bedst som `geometry` i en projektion, der er valgt til netop det område, fordi
hele funktionssættet er til rådighed, og regningen går hurtigt. Data, der
spænder over kontinenter, eller hvor brugerne vil have afstande i meter mellem
vilkårlige punkter på kloden, er lettere at få rigtige som `geography`. Den type
klarer selv matematikken på den krumme overflade, til gengæld med færre
funktioner.

Somme tider er svaret, at PostGIS er mere, end opgaven kræver. En app, der blot
gemmer bredde- og længdegrad for hver butik og sætter nåle på et kort, klarer
sig fint med to talkolonner. PostGIS gør sig fortjent til pladsen, når
spørgsmålene handler om afstand, indeholdelse eller overlap, eller når dataene
rummer linjer og polygoner frem for enkelte punkter.

## Sådan ser det ud i praksis

To projekter her på siden viser PostGIS i de to roller, udvidelsen oftest får:
som grunden til en migrering og som arbejdshest under en oprydning.

En kortapplikation med rumlige forespørgsler i realtid for mange brugere kørte
på en Microsoft SQL Server-database uden indbygget geospatial indeksering, og
ventetiden gik ud over både kortet og tjenesterne bag det.
[Løsningen blev en flytning til PostgreSQL med PostGIS](/da/portfolio/improved-geographical-map-application-performance-by-10x-through-6/),
med et skema designet til rumlige data, GiST- og SP-GiST-indeks på geometri- og
geografikolonnerne og constraints på de rumlige koordinater. Over 50 millioner
rumlige poster gik gennem ETL-pipelines, der transformerede dem til EPSG:4326
undervejs. Den gennemsnitlige svartid faldt fra 2,5 sekunder til under 250
millisekunder, en forbedring på 10 gange, og CPU-belastningen i backend faldt
med 65 %.

Det andet projekt handlede om data frem for fart. Et stort geospatialt
analyseprojekt afslørede over 1.000 uoverensstemmelser i geografiske datasæt og
tidsseriedatasæt. GDAL, QGIS og ArcGIS blev brugt til at finde og rette de
rumlige fejl, mens
[PostGIS stod for de rumlige forespørgsler og indekseringen](/da/portfolio/resolved-1-000-issues-in-geographical-data-and-7/),
og procedurer i PL/pgSQL transformerede dataene inde i PostgreSQL, før det
rensede resultat blev vist i Mapbox. Køretiden for rumlige forespørgsler faldt
med 35 %.

## Hvor det går galt

**Afstande kommer tilbage som små decimaltal.** En forespørgsel spørger, hvor
langt der er mellem to byer, og får `0.08`. Kolonnen er `geometry` i SRID 4326,
så svaret er i grader, og en længdegrad skrumper fra omkring 111 kilometer ved
ækvator til ingenting ved polerne. Cast til `geography` for at få svaret i
meter, eller brug `ST_Transform` til at flytte dataene over i et projiceret
system, der passer til området, før der måles.

**En radiussøgning læser hver eneste række.** Tabellen har et rumligt indeks,
men planen viser en sekventiel scanning. Den typiske årsag er et filter skrevet
som `ST_Distance(a, b) < 500`. Manualens liste over funktioner, der selv tager
indekset i brug, rummer `ST_DWithin`, men `ST_Distance` står der ikke. At pakke
den indekserede kolonne ind i en cast eller en transformation har samme
virkning, for et indeks på `geom` siger intet om `geom::geography`.

```sql
-- en geography-kolonne, indekseret som den er gemt, målt i meter
CREATE INDEX depots_geog_idx ON depots USING GIST (geog);

SELECT id FROM depots
WHERE ST_DWithin(geog, ST_MakePoint(12.57, 55.68)::geography, 500);
```

Skal dataene blive i en `geometry`-kolonne i 4326, så tilføj en
`geography`-kolonne som denne og indeksér den, eller opret indekset på præcis
det udtryk, forespørgslen bruger. Kør `ANALYZE` efter en stor indlæsning, så
planlæggeren ved, at indekset er værd at tage.

**Punkterne havner i havet.** Mønsteret ser rigtigt ud, men det hele ligger
forkert, ofte på et andet kontinent. To fejl giver det billede. `ST_SetSRID`
sætter kun et nyt mærke på en geometri og lader tallene være, så hvis den
bruges, hvor `ST_Transform` var ment, bliver meter stemplet som grader. Eller
også er længde- og breddegrad byttet om et sted tidligere i kæden: PostGIS
forventer ligesom GeoJSON x før y, altså længdegrad før breddegrad. Brug
`ST_SetSRID` til at erklære, hvad tallene allerede er, `ST_Transform` til at
omregne dem, og test hver import med ét punkt, hvis placering man kender.

**Areal- og overlapsberegninger kaster fejl.** En import fra en shapefil går
igennem, og senere fejler `ST_Intersection` med en topologiundtagelse. PostGIS
gemmer med vilje ugyldig geometri, så den kan findes og repareres, og en
polygon, hvis rand krydser sig selv, er ugyldig. Tjek med `ST_IsValid`, reparér
med `ST_MakeValid`, der bevarer alle inputpunkter, og tilføj en
check-constraint, så en dårlig form bliver afvist ved døren i stedet for at
dukke op i en rapport.

## Læs mere

- [PostGIS Documentation](https://postgis.net/documentation/) — projektets eget
  indgangspunkt til manualen, FAQ'en og undervisningsmaterialet.
- [Data Management](https://postgis.net/docs/using_postgis_dbmanagement.html) —
  geometry og geography, SRID'er, gyldighedsregler og de tre indekstyper, som
  manualen selv beskriver dem.
- [Spatial Indexing](https://postgis.net/workshops/postgis-intro/indexing.html)
  — workshopkapitlet om afgrænsningsbokse og de funktioner, der bruger et indeks
  af sig selv.
- [ST_DWithin](https://postgis.net/docs/ST_DWithin.html) — referencesiden for
  afstandstesten med enhederne for hver type.
- [Simple Feature Access – Part 1](https://www.ogc.org/standards/sfa/) —
  OGC-standarden bag de geometrityper, PostGIS gemmer.

<https://engineer.company/da/glossary/postgis/>
