💻 Romlig kobling

💻 Romlig kobling#

Open In Colab

Romlige koblinger er operasjoner som kombinerer data fra to eller flere romlige datasett basert på deres geometriske relasjon. Vi har allerede fått kjennskap til to spesifikke tilfeller av romlige koblinger: Punkt-i-polygon spørringer og Intersect.

Romlige koblingsoperasjoner krever to inngangsparametere: predikatet, dvs. den geometriske betingelsen som må oppfylles mellom to geometrier, og koblings-typen: om bare rader med matchende geometrier beholdes, eller alle radene i en inputtabell, eller alle radene fra begge tabellene.

Geopandas (ved hjelp av shapely for å implementere geometriske forhold) støtter et standard sett med geometriske predikater, som ligner de som de fleste GIS analyseverktøy og progammer bruker:

  • intersects

  • contains

  • within

  • touches

  • crosses

  • overlaps

Geometriske predikater uttrykkes som verb, så de har en intuitiv betydning. Se shapely bruker manual for en detaljert beskrivelse av hvert geometrisk predikat.

Binære geometriske predikater

Shapely støtter flere binære geometriske predikater enn geopandas implementerer for romlige koblinger. Hva er de? Kan de uttrykkes ved å kombinere de som allerede er implementerte?

Når det gjelder koblings-typen, implementerer geopandas tre forskjellige alternativer:

  • left: behold alle poster av left data-rammen, fyll med tomme verdier hvis ingen match, behold left geometrikolonne

  • right: behold alle poster av left data-rammen, fyll med tomme verdier hvis ingen match, behold right geometrikolonne

  • inner: behold bare poster av matchende poster, behold left geometrikolonne

Romlige koblinger

Tip

PyGIS boken har en god oversikt over romlige predikater og koblings-typer med forklarende tegninger.


Last inn inngangsdata#

Som et praktisk eksempel, la oss finne befolkningstettheten på hver av adressene fra geokodingen vi har gjort tidligere, ved å kombinere datasettet med data fra et befolkningsnett.

Befolkningsnettdata er tilgjengelig fra SSB.

import pathlib 
NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_MAPPE = NOTEBOOK_PATH / "data"
import geopandas

adresser = geopandas.read_file(DATA_MAPPE / "oslo_adresser" / "adresser.geojson")

befolkningsnett = geopandas.read_file(DATA_MAPPE / "ssb_rutenett" / "befolkning_250m_2023_oslo.shp"
)
befolkningsnett.head()
fid ru250m pop_tot geometry
0 1.0 2.263751e+13 14 POLYGON ((264000 6643000, 263750 6643000, 2637...
1 2.0 2.264001e+13 177 POLYGON ((264250 6643000, 264000 6643000, 2640...
2 3.0 2.264251e+13 169 POLYGON ((264500 6643000, 264250 6643000, 2642...
3 4.0 2.264501e+13 261 POLYGON ((264750 6643000, 264500 6643000, 2645...
4 5.0 2.264751e+13 106 POLYGON ((265000 6643000, 264750 6643000, 2647...

Til slutt, beregn befolkningstettheten ved å dele antall innbyggere av hver rutenettcelle med arealet i km²:

befolkningsnett["pop_density"] = (
    befolkningsnett["pop_tot"]
    / (befolkningsnett.area / 1_000_000)
)
befolkningsnett.head()
fid ru250m pop_tot geometry pop_density
0 1.0 2.263751e+13 14 POLYGON ((264000 6643000, 263750 6643000, 2637... 224.000000
1 2.0 2.264001e+13 177 POLYGON ((264250 6643000, 264000 6643000, 2640... 2832.000001
2 3.0 2.264251e+13 169 POLYGON ((264500 6643000, 264250 6643000, 2642... 2704.000001
3 4.0 2.264501e+13 261 POLYGON ((264750 6643000, 264500 6643000, 2645... 4176.000002
4 5.0 2.264751e+13 106 POLYGON ((265000 6643000, 264750 6643000, 2647... 1696.000001

Kodestil: store tall, operatorer i flerlinjeuttrykk

Hvis du trenger å bruke veldig store tall, for eksempel, i eksempelet ovenfor, 1 million for å konvertere m² til km², kan du bruke understreker (_) som tusen-separatorer. Python vil behandle en sekvens av tall sammenvevd med understreker som en vanlig numerisk verdi. Du kan bruke samme syntaks for å gruppere tall etter en annen logikk, for eksempel, for å gruppere heksadesimale eller binære verdier i grupper av fire.

I tilfelle et uttrykk, for eksempel, en matematisk formel, sprer seg over flere linjer, anses det for å være god kodestil å plassere en operator i begynnelsen av en ny linje, i stedet for å la den være en hale i den forrige linjen. Dette anses som mer lesbart, som forklart i PEP-8 styling retningslinjer


Join inputlagene#

Nå er vi klare til å utføre den romlige joinen mellom de to lagene. Husk: målet er å finne befolkningstettheten rundt hver av adressepunktene. Vi ønsker å knytte befolkningstetthetsinformasjon fra befolkningsnett-polygonlaget til adresser-punktlaget, avhengig av om punktet er innenfor polygonet. Under denne operasjonen ønsker vi å beholde geometriene til punktlaget.

Før vi kan fortsette med join-operasjonen, må vi sørge for at de to lagene er i samme crs:

assert adresser.crs == befolkningsnett.crs, "CRS er ikke identiske"
---------------------------------------------------------------------------
AssertionError                            Traceback (most recent call last)
Cell In[5], line 1
----> 1 assert adresser.crs == befolkningsnett.crs, "CRS er ikke identiske"

AssertionError: CRS er ikke identiske

De deler ikke samme CRS, la oss reprojisere befolkningsnettet, siden vi ønsker å joine infomasjon fra befolkningsnettet inn i adresse-laget vårt:

befolkningsnett = befolkningsnett.to_crs(adresser.crs)

Nå er vi klare til å utføre den faktiske romlige joinen ved hjelp av geopandas.GeoDataFrame.sjoin() metoden. Husk, vi ønsker å bruke et within geometrisk predikat og beholde punktlagets geometrier (i eksempelet nedenfor er left dataframmen).

adresser_med_befolkningsdata = adresser.sjoin(
    befolkningsnett,
    how="left",
    predicate="within"
)
adresser_med_befolkningsdata.head()
fid_left address id adr geometry index_right fid_right ru250m pop_tot pop_density
0 1 25, Statsråd Mathiesens vei, Linderud, Bjerke,... 100 Statsråd Mathiesens vei 25, 0594 OSLO POINT (10.83648 59.94104) NaN NaN NaN NaN NaN
1 2 15, Slimeveien, Bjørnholt, Søndre Nordstrand, ... 101 Slimeveien 15, 1275 OSLO POINT (10.83432 59.83557) 562.0 563.0 2.266501e+13 6.0 96.000000
2 3 Sognsveien 80, Konvallveien, Sogn, Nordre Aker... 102 Sognsveien 80, 0855 OSLO POINT (10.72956 59.95011) 1805.0 1806.0 2.261501e+13 239.0 3824.000002
3 4 5, Ullevålsveien, Hammersborg, St. Hanshaugen,... 103 Ullevålsveien 5, 0165 OSLO POINT (10.74356 59.91863) 1879.0 1880.0 2.262001e+13 868.0 13888.000008
4 5 30B, Nydalsveien, Nydalen, Nordre Aker, Oslo, ... 104 Nydalsveien 30b, 0484 OSLO POINT (10.76402 59.9503) 2123.0 2124.0 2.263251e+13 366.0 5856.000003

Det ser flott ut! Vi har nå et adresse-datasett med befolkningstetthetsinformasjon knyttet til det.


Som en siste oppgave, la oss se på hvordan du kan plotte data ved hjelp av en gradert kartografisk visualiseringsstil.

geopandas.GeoDataFrame.plot() metoden kan variere kartfargene avhengig av en kolonnes verdier ved å sende inn navnet som et navngitt argument column. I tillegg til det, aksepterer metoden mange argumenter for å påvirke stilen på kartet. Blant dem er scheme og cmap som definerer kategoriseringsskjemaet, og fargekartet som brukes. Mange flere argumenter sendes gjennom til matplotlib, som markersize for å sette størrelsen på punktsymboler, og facecolor for å sette fargen på polygonområder. For å tegne en legend, sett legend til True, for å sette størrelsen på figuren, angi inn en tuple (med verdier i tommer) som figsize.

ax = adresser_med_befolkningsdata.plot(
    figsize=(10, 10),
    column="pop_density",
    cmap="Reds",
    scheme="quantiles",
    markersize=15,
    legend=True
)
ax.set_title("Befolkningstetthet rundt adressepunktene")
Text(0.5, 1.0, 'Befolkningstetthet rundt adressepunktene')
../../_images/9fe5c378070f944e4f728941406248787ca949205d2a9ba4fb779f224bd3a165.png

Vi kan bruke de samme argumentene for å plotte et befolkningstetthetskart ved hjelp av hele befolkningsnett datasettet:

ax = befolkningsnett.plot(
    figsize=(10, 10),
    column="pop_density",
    cmap="Reds",
    scheme="quantiles",
    legend=True
)
ax.set_title("Befolkningstetthet i Oslo")
Text(0.5, 1.0, 'Befolkningstetthet i Oslo')
../../_images/69675e6b4f84139db32a4b8d1fd7213f37741af9a7308f9202db022d82d36bac.png

Til slutt, kan vi lagre output-dataframmen til en fil:

adresser_med_befolkningsdata.to_file(
    DATA_MAPPE / "oslo_adresser" / "adresser_med_befolkningsdata.geojson",
)