Geometry eller Geography til geografiske data?

I projekt QuestGame arbejder vi med geografiske data, fordi spillets Lokationer skal placeres på et kort. En spiller skal blandt andet kunne se Lokationer i nærheden og senere kunne få vist de LokationsElementer, der befinder sig inden for en bestemt afstand.

Til dette bruger vi PostgreSQL med PostGIS. PostGIS udvider PostgreSQL med understøttelse af geografiske data og funktioner til blandt andet afstandsberegninger.

Under arbejdet med databasedesignet stødte jeg på et valg, som kan få betydning på flere parametre: Skal en Lokation gemmes som datatypen geometry eller geography?

Udgangspunktet: koordinater fra telefonens GPS

I backenden repræsenterer vi en Lokation med et Point fra NetTopologySuite.

Et forsimplet eksempel kunne være:

public class Location
{
    public int Id { get; set; }

    public Point Coordinates { get; set; } = null!;
}Code language: JavaScript (javascript)

Telefonens GPS leverer bredde- og længdegrader. Disse koordinater forbindes typisk med WGS84, som har SRID 4326.

Et punkt kan eksempelvis bestå af:

  • Longitude: 10.3883
  • Latitude: 55.3959

Det fungerer fint til at fortælle, hvor noget befinder sig. Udfordringen opstår, når jeg vil beregne afstande.

Geometry

PostGIS-datatypen geometry arbejder med koordinater ud fra det koordinatsystem, der er valgt.

Hvis jeg eksempelvis gemmer et punkt som:

geometry(Point, 4326)

gemmer jeg stadig almindelige longitude- og latitude-koordinater.

Problemet er, at EPSG:4326 bruger grader som koordinatenhed. Hvis jeg derfor laver en afstandsberegning, kan jeg ikke uden videre antage, at værdien 1000 betyder 1000 meter.

Det betyder ikke, at geometry er et dårligt valg. Jeg kan læse mig til at det er meget almindeligt at bruge geometry sammen med et projekteret koordinatsystem, hvor koordinaterne eksempelvis måles i meter.

Det kræver dog, at man aktivt tager stilling til projektion og eventuelt transformerer GPS-koordinaterne.

For QuestGame ville det derfor betyde lidt mere GIS-logik i systemet. Et felt, jeg ikke har arbejdet med før nu, og som derfor vil øge kompleksiteten.

Geography

PostGIS har også datatypen geography. Den er beregnet til koordinater på jordens overflade.

En Lokation kan eksempelvis gemmes som:

geography(Point, 4326)

Her kan jeg stadig bruge koordinaterne direkte fra telefonens GPS, men PostGIS behandler dem som geografiske koordinater på jorden.

En vigtig fordel for QuestGame er, at afstande kan arbejdes med i meter.

Hvis jeg eksempelvis vil finde alle Lokationer inden for én kilometer, kan en forespørgsel konceptuelt se sådan ud:

SELECT *
FROM "Locations"
WHERE ST_DWithin(
    "Coordinates",
    :playerPosition,
    1000
);Code language: JavaScript (javascript)

Når kolonnen er geography, betyder 1000 her 1000 meter.

Det passer meget direkte til den type spørgsmål, vores backend skal kunne besvare. F.eks.:

  • Er spilleren inden for 50 meter af Lokationen?
  • Hvilke Lokationer ligger inden for 1 kilometer?
  • Hvor langt er der til næste Lokation?

Det gør modellen lettere for mig at forstå, fordi de værdier jeg arbejder med i kode og database svarer til virkelige afstande.

Spatial indexes

En anden vigtig del af arbejdet med geografiske data er performance.

Det ville være en dårlig løsning at hente alle Lokationer fra databasen og derefter beregne afstandene én efter én i C#.

PostGIS kan i stedet udføre forespørgslen direkte i databasen.

Funktioner som ST_DWithin kan samtidig gøre brug af spatial indexes. Med PostGIS har man en række funktioner, som findes i PostGIS Geography Support Functions.

Konceptuelt bliver flowet derfor:

Spillerens position
        ↓
ASP.NET Core
        ↓
EF Core / Npgsql
        ↓
PostgreSQL + PostGIS
        ↓
Find Lokationer inden for radius

Databasen udfører altså den geografiske beregning, som den er specialiseret til.

Hvorfor bruger man ikke altid geography?

geography er ikke automatisk den bedste datatype til alle systemer.

Hvis et system kun arbejder inden for et begrænset område, kan det være oplagt at vælge et passende projekteret koordinatsystem og bruge geometry.

Det kan blandt andet give større fleksibilitet og være en mere klassisk GIS-løsning.

QuestGame arbejder netop forholdsvis lokalt. En Quest kan eksempelvis foregå inden for et område på én eller to kilometer.

Derfor kunne geometry med et passende koordinatsystem også være en fornuftig løsning.

Det ville dog betyde, at vi samtidig skulle tage stilling til transformation af GPS-koordinater og valg af projektion.

Mit valg til QuestGame

Til QuestGame hælder jeg derfor til:

geography(Point, 4326)

Valget handler primært om enkelhed.

Vi modtager WGS84-koordinater fra telefonernes GPS, og vores vigtigste geografiske operationer bliver afstands- og radiusberegninger.

Ved at bruge geography kan vi arbejde direkte med disse koordinater og bruge meter som afstandsenhed.

Alternativet med geometry og et projekteret koordinatsystem kan muligvis være mere optimalt i bestemte situationer, men det introducerer også mere kompleksitet.

I et projekt med begrænset udviklingstid giver det ikke nødvendigvis mening at optimere en geografisk løsning, før vi ved, om performance overhovedet bliver et problem.

Hvad jeg tager med videre

Før arbejdet med PostGIS tænkte jeg mest på geografiske data som latitude og longitude.

Jeg har efterfølgende fundet ud af, at selve koordinaterne kun er en del af problemet.

Jeg skal også tage stilling til:

  • Hvordan fortolkes koordinaterne?
  • Hvilket koordinatsystem bruges?
  • Hvilken enhed returnerer afstandsberegninger?
  • Hvor skal beregningerne udføres?

For QuestGame giver følgende model derfor bedst mening for mig lige nu:

GPS
 ↓
WGS84-koordinater
 ↓
PostGIS
 ↓
geography(Point, 4326)
 ↓
spatial index
 ↓
ST_DWithin og afstandsberegninger

Det giver en forholdsvis direkte sammenhæng mellem de koordinater, mobiltelefonen leverer, og de geografiske forespørgsler backenden skal udføre.

Kilder