💻 Overlay#

Open In Colab

Overlay-analyser er GIS-operasjoner der to eller flere vektorlag er kombinert for å produsere nye geometrier. Typiske overlay-operasjoner inkluderer union, intersection, og difference - navngitt etter resultatet av kombinasjonen av to lag. I denne notebooken (inspirert av Python for Geographic Data Analysis) ser vi nærmere på hvordan vi kan utføre overlay operasjoner med vektor data.

Den grunnleggende ideen med vektor-overlay-operasjoner er demonstrert i figuren under, hvor de grønne områdene representerer områdene som utgjør resultatet etter overlay-operasjonen. Det er viktig å huske at overlays opererer på GeoDataFrame-nivå, ikke på individuelle geometrier, og egenskapene fra begge beholdes (ofte, men ikke alltid). I praksis utføres denne operasjonen for hver form i den venstre GeoDataFrame mot hver annen form i den høyre GeoDataFrame.

Fire paneler som viser union, intersection, symmetric difference og difference av to geometrier.

Romlig overlay med to inputvektorlag (rektangel, sirkel). Det resulterende vektorlaget vises i grønt. Kilde: QGIS dokumentasjon

For å demonstrere hvordan disse overlay-operasjonene fungerer i praksis, vil vi utføre vektor-overlay-operasjoner mellom to polygon-datasett som representerer 1) bydelene i Oslo og 2) en 5000-meter buffer rundt Oslo S. La oss starte med å lese inn datasettene og forberede dem for analysen:

import geopandas
import pathlib 

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

bydeler = geopandas.read_file(DATA_MAPPE / "oslo_bydeler" / "oslo_bydeler_befolkning_2024.geojson")
oslo_s = geopandas.read_file(DATA_MAPPE / "oslo_bydeler" / "oslo_s.geojson")
bydeler.shape
(17, 6)
oslo_s.head()
navn geometry
0 Oslo S POINT (10.7504 59.9117)

Her kan vi se at bydelene inneholder MultiPolygon-geometrier som representerer til sammen 17 bydeler, mens oslo_s representerer et enkelt punkt for Oslo S. Siden vektor-overlay-operasjoner skjer mellom to geografiske datasett, er det nødvendig å sikre at de begge deler samme koordinatsystem og i vårt tilfelle et projisert koordinatsystem, siden vi skal bruke en metrisk buffersone. La oss derfor først sjekke crs-attributtene og gjøre nødvendige reprojiseringer:

print(bydeler.crs)
print(oslo_s.crs)
EPSG:4326
EPSG:4326

Begge datasettene våre er i EPSG:4326, la oss reprojisere til EPSG:25832 som egner seg for norske data:

# Reprojiser bydeler
bydeler = bydeler.to_crs("EPSG:25832")

# Reprojiser oslo_s
oslo_s = oslo_s.to_crs(bydeler.crs)

La oss for sikkerhetsskyld dobbeltsjekke at lagene har samme crs:

assert bydeler.crs == oslo_s.crs, "Ulike koordinatsystemer!"

Supert, koordinatsystemet (CRS) stemmer overens mellom lagene. Derfor kan vi fortsette og lage en 5 kilometer buffer rundt Oslo S som vi vil bruke i våre vektor-overlay-operasjoner:

oslo_s_buffer = oslo_s.copy()
oslo_s_buffer["geometry"] = oslo_s_buffer.buffer(5000)

Her har vi først lagd en kopi av oslo_s-laget vårt, og brukt .buffer()-metoden til å lage en buffer Polygon på 5000 meters radius rundt Oslo S. Vi kan nå lage et enkelt plot for å se hvordan lagene overlapper hverandre:

ax = bydeler.plot(facecolor="blue")
oslo_s_buffer.plot(ax=ax, facecolor="red")
<Axes: >
../../_images/1c146fc4ad91f0b59af67a8dbd93ec76e9ea25678b0d32da82d513653b34641d.png

Intersection#

Nå kan vi utføre en overlay-analyse mellom disse to lagene. Vi skal lage et nytt lag som intersect(“skjærer”) med bydelslaget. Vi bruker da metoden .overlay() i GeoPandas til å gjøre analysen mellom laget bydeler og oslo_s_buffer. Parameteren how i overlay()-metoden angir hvilken type overlay-analyse som utføres. De ulike metodene man kan velge mellom er intersection, union, symmetric_difference, difference og identity.

Vi begynner med å bruke intersection som overlay-metode:

# Intersection
intersection = bydeler.overlay(oslo_s_buffer, how="intersection")
intersection
fid bydelsnr bynavn bydel befolkning_2024 navn geometry
0 1 030101 Oslo Gamle Oslo 63721 Oslo S POLYGON ((594299.813 6640496.194, 595053.786 6...
1 2 030102 Oslo Grünerløkka 65532 Oslo S POLYGON ((598106.885 6643240.277, 598165.757 6...
2 3 030103 Oslo Sagene 47627 Oslo S POLYGON ((599000.023 6645042.516, 598989.131 6...
3 4 030104 Oslo St.Hanshaugen 41571 Oslo S POLYGON ((597929.403 6643709.551, 597936.146 6...
4 5 030105 Oslo Frogner 60727 Oslo S POLYGON ((593123.834 6643198.372, 593664.254 6...
5 6 030106 Oslo Ullern 35440 Oslo S POLYGON ((592894.411 6643050.452, 592909.701 6...
6 7 030107 Oslo Vestre Aker 52590 Oslo S POLYGON ((595764.88 6647391.522, 595759.932 66...
7 8 030108 Oslo Nordre Aker 55056 Oslo S POLYGON ((599513.141 6646064.846, 599498.522 6...
8 9 030109 Oslo Bjerke 36450 Oslo S POLYGON ((601049.467 6644203.445, 601046.649 6...
9 12 030112 Oslo Alna 50358 Oslo S POLYGON ((601433.698 6642738.788, 601426.332 6...
10 13 030113 Oslo Østensjø 51895 Oslo S POLYGON ((601656.376 6639614.618, 601620.452 6...
11 14 030114 Oslo Nordstrand 54027 Oslo S POLYGON ((596941.033 6638803.261, 596901.883 6...
12 16 030116 Oslo Sentrum 0 Oslo S POLYGON ((597652.231 6642995.353, 597659.927 6...

Vi kan nå se at vi har en GeoDataFrame med 13 rader for alle bydelene som intersecter med bufferlaget vårt. Vi kan også se at vi har med kolonner fra begge datasettene, og dermed at overaly-metoden fungerer litt likt sjoin() som vi brukte i en tidligere forelesning.

For å lettere illustrere hvordan de ulike overlay metodene fungerer, lager vi nå en funksjon som plotter et kart før og etter overlay-analysen ved siden av hverandre:

import matplotlib.pyplot as plt


def plot_overlay(gdf1, gdf2, result, title):
    """
    Creates two maps next to each other based on `gdf1`, `gdf2` and the
    `result` GeoDataFrames.

    Source: https://pythongis.org/part2/chapter-06/nb/08-overlay-analysis-with-vector-data.html#intersection
    """

    fig, (ax1, ax2) = plt.subplots(nrows=1, ncols=2, figsize=(8, 5))

    ax1 = gdf1.plot(ax=ax1)
    ax1 = gdf2.plot(ax=ax1, color="red", alpha=0.3)

    result.plot(ax=ax2)

    # Fetch bounds and apply to axis 2
    xmin, ymin, xmax, ymax = gdf1.total_bounds

    ax2.set_xlim(xmin, xmax)
    ax2.set_ylim(ymin, ymax)

    fig.suptitle(title, fontsize=16)
    # Add an arrow between the plots
    fig.text(0.49, 0.5, "⇨", fontsize=30, color="red")
    ax1.axis("off")
    ax2.axis("off")
    plt.tight_layout()
    return fig, ax1, ax2

Med denne funksjonen kan vi nå bruke intersection-laget vi nettopp lagde og plotte før- og etter-resultatet:

fig, ax1, ax2 = plot_overlay(
    gdf1=bydeler, gdf2=oslo_s_buffer, result=intersection, title="Intersection"
)
../../_images/c4619e8acfdfe2611a7f77e08e4d04f3d8b2788600bea6761ffbdc4cb5498c6c.png

Som vi kan se, beholder “intersection” operasjonen bydelene som krysser sirkelen og beholder alle disse geometriene i resultatet. En viktig ting å merke seg er at med overlay() vil den kryssende GeoDataFramen (oslo_s_buffer) også modifisere inputgeometriene ved å kutte dem i grenseområdene der de krysser. Dette er en av de viktigste forskjellene mellom .overlay() og sjoin() metodene, da sjoin() ikke vil modifisere inputgeometriene. Som nevnt tidligere, beholdes attributtdata fra begge GeoDataFramene for objektene som er en del av resultatet. I det følgende vil vi vise én etter én hvordan forskjellige overlay-operasjoner (dvs. union, difference, symmetric difference, identity) påvirker resultatene.

Union#

La oss bruke de samme inputdatasettene og utføre en union-overlay:

# Union
union = bydeler.overlay(oslo_s_buffer, how="union")

fig, ax1, ax2 = plot_overlay(
    gdf1=bydeler, gdf2=oslo_s_buffer, result=union, title="Union"
)
../../_images/9de19325263b2bafe7b12df92b68fb614265a4801960cea5cb172db8f1434237.png
union.shape
(27, 7)

Når man bruker "union" overlay-operasjonen, beholdes geometriene fra begge GeoDataFrames i resultatet. Som du kan se, har antall rader økt ganske betydelig fra 17 til 22 rader. Dette skjer fordi bydelene igjen blir modifisert av oslo_s_buffer i områdene der geometriene krysser hverandre: bydelene blir delt i to i områder der buffergeometrien krysser bydeler-geometriene. Dette vil derfor øke antall rader i det endelige resultatet.

Difference og symmetric difference#

Noen ganger kan det være nyttig å fokusere på å trekke ut geometrier som ligger utenfor et gitt lag. Dette kan oppnås ved å bruke .overlay() med “difference” operatoren:

# Difference
difference = bydeler.overlay(oslo_s_buffer, how="difference")

fig, ax1, ax2 = plot_overlay(
    gdf1=bydeler, gdf2=oslo_s_buffer, result=difference, title="Difference"
)
../../_images/e23005e8a02e58a709002982c0258eef3766da216497f132a46f6b9e01cb0bb7.png
difference.shape
(13, 6)

Symmetric differance-overlay-operasjonen er en interessant en. Den vil beholde geometriene og attributtene utenfor oslo_s_buffer-laget, samt opprette en geometri innenfor oslo_s_buffer som inkluderer områder som er innenfor oslo_s_buffer-ringen, men utenfor bydeler-GeoDataFramen. Det vil si, i vårt tilfelle inneholder den et lite stykke av Oslofjorden, som vist nedenfor:

# Symmetric Difference
symmetric_difference = bydeler.overlay(oslo_s_buffer, how="symmetric_difference")

fig, ax1, ax2 = plot_overlay(
    gdf1=bydeler,
    gdf2=oslo_s_buffer,
    result=symmetric_difference,
    title="Symmetric Difference",
)
../../_images/b55bede9da62fc37801865d98d2cd00a187d7a19310d69146cd9c9bc487bbd01.png
symmetric_difference.shape
(14, 7)
symmetric_difference.tail()
fid bydelsnr bynavn bydel befolkning_2024 navn geometry
9 13.0 030113 Oslo Østensjø 51895.0 NaN POLYGON ((603544.866 6638682.313, 603551.763 6...
10 14.0 030114 Oslo Nordstrand 54027.0 NaN POLYGON ((600631.693 6635249.786, 600624.152 6...
11 15.0 030115 Oslo Søndre Nordstrand 39124.0 NaN MULTIPOLYGON (((603567.643 6634836.359, 603565...
12 17.0 030117 Oslo Marka 0.0 NaN MULTIPOLYGON (((603567.643 6634836.359, 603567...
13 NaN NaN NaN NaN NaN Oslo S MULTIPOLYGON (((596434.202 6638086.898, 595972...

Som vi kan se i tabellen, så inneholder tabellen attributter fra bydelene og en rad med data fra oslo_s_buffer i den siste raden.

Overlay med befolkningsdata#

I denne notebooken vil vi utføre en overlay-analyse for å velge de polygon-cellene i et grid-datasett som ligger innenfor Oslo. I denne øvelsen, bruker vi to input-datasett: et grid av statistiske polygoner med befolkningen i Oslo og et polygon-datasett av norske kommuner (kommuner.geojson), hvor vi skal velge bare Oslo kommune.

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

rutenett = geopandas.read_file(DATA_MAPPE / "ssb_rutenett" / "befolkning_250m_2023_akershus.shp")

kommuner = geopandas.read_file(
    DATA_MAPPE / "kommuner" / "kommuner.geojson"
)

display(kommuner.head())
gml_id lokalId navnerom versjonId oppdateringsdato gyldigFra datauttaksdato navn språk kommunenummer kommunenavn samiskForvaltningsområde geometry
0 kommune.1 0466e7c3-6a23-4b70-97d4-cf81f949490b https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 17:47:39 2024-01-01 2024-03-04 12:27:57 Ibestad nor 5514 Ibestad False MULTIPOLYGON (((589669.263 7630312.979, 589669...
1 kommune.2 0f821a2d-a307-41ec-9b64-f01c80e39357 https://data.geonorge.no/sosi/inndelinger/innd... 4 2023-11-07 23:33:28 2023-11-07 2024-03-04 12:27:57 Rendalen nor 3424 Rendalen False MULTIPOLYGON (((327391.89 6869419.57, 327363.6...
2 kommune.3 251a3fdd-ac96-4ef2-badb-a9b75e6ab2e6 https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 15:42:05 2024-01-01 2024-03-04 12:27:57 Rakkestad nor 3120 Rakkestad False MULTIPOLYGON (((284560.99 6591094.55, 284575.8...
3 kommune.4 2758a7b8-edba-4d6a-936e-4754ce03ca8f https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 15:56:10 2024-01-01 2024-03-04 12:27:57 Nordre Follo nor 3207 Nordre Follo False MULTIPOLYGON (((269615.27 6638265.47, 269615.2...
4 kommune.5 340fff7f-b153-4a87-814d-12ca4f2d1682 https://data.geonorge.no/sosi/inndelinger/innd... 4 2023-11-07 23:33:28 2023-11-07 2024-03-04 12:27:57 Overhalla nor 5047 Overhalla False MULTIPOLYGON (((359415.1 7152572.93, 359412.81...

Vi kan nå velge bare Ås kommune fra kommune-datasettet.

aas = kommuner.loc[kommuner["navn"] == "Ås"]
aas.head()
gml_id lokalId navnerom versjonId oppdateringsdato gyldigFra datauttaksdato navn språk kommunenummer kommunenavn samiskForvaltningsområde geometry
27 kommune.28 d285e9c4-8da0-4dfc-b927-31ba7ee17e6a https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 16:00:22 2024-01-01 2024-03-04 12:27:57 Ås nor 3218 Ås False MULTIPOLYGON (((266871.58 6614231.5, 266871.14...

Før vi fortsetter er det en god ide å sørge for at begge lagene har samme koordinatsystem:

# Sjekke at lagene har samme crs
rutenett.crs == aas.crs
True

La oss gjøre en rask overleggsvisualisering av de to lagene:

# Plott lagene
ax = rutenett.plot(color="gray")
aas.plot(ax=ax, facecolor="None", edgecolor="blue")
<Axes: >
../../_images/03d949f3185826edf820f55ce651e99510c1dff5d9f35ad141d51007a41cdb2e.png

Her er alle de grå rutene gridceller med befolkningsdata for Akershus fylke. Datasettet inneholder 17376 ruter. Det blå omrisset representerer Ås kommune. Vårt mål er nå å utføre en overlay-analyse og velge de geometriene fra rutenettet som krysser polygonet for Ås kommune.

Vi vil lage et nytt lag basert på rutenett-polygoner som intersect-er med Ås-laget vårt. Vi kan bruke metoden overlay() med en GeoDataFrame for å utføre overlay-analysen som tar som en input 1) andre GeoDataFrames, og 2) parameteren how som kan brukes til å kontrollere hvordan overlay-analysen blir utført (mulige verdier er 'intersection', 'union','symmetric_difference', 'difference', og 'identity'):

intersection = rutenett.overlay(aas, how="intersection")

La oss plotte dataene våre og se hva vi har:

intersection.plot(color="b")
<Axes: >
../../_images/e6172737a86c85505b714626d10aaca441eac3110fad1d5687a51c74c5b1b447.png

Som et resultat har vi nå bare de rutenett cellene som krysser med Helsingfors grenser. Hvis du ser nøye etter, kan du også observere at rutenett cellene er klippet basert på grensen.

  • Hva med dataattributter? La oss se hva vi har:

intersection.head()
ru250m pop_tot gml_id lokalId navnerom versjonId oppdateringsdato gyldigFra datauttaksdato navn språk kommunenummer kommunenavn samiskForvaltningsområde geometry
0 22605006625250 10 kommune.28 d285e9c4-8da0-4dfc-b927-31ba7ee17e6a https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 16:00:22 2024-01-01 2024-03-04 12:27:57 Ås nor 3218 Ås False POLYGON ((260500 6625250, 260500 6625500, 2607...
1 22617506625250 3 kommune.28 d285e9c4-8da0-4dfc-b927-31ba7ee17e6a https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 16:00:22 2024-01-01 2024-03-04 12:27:57 Ås nor 3218 Ås False POLYGON ((261750 6625250, 261750 6625500, 2620...
2 22620006625250 23 kommune.28 d285e9c4-8da0-4dfc-b927-31ba7ee17e6a https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 16:00:22 2024-01-01 2024-03-04 12:27:57 Ås nor 3218 Ås False POLYGON ((262000 6625250, 262000 6625500, 2622...
3 22622506625250 10 kommune.28 d285e9c4-8da0-4dfc-b927-31ba7ee17e6a https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 16:00:22 2024-01-01 2024-03-04 12:27:57 Ås nor 3218 Ås False POLYGON ((262250 6625250, 262250 6625500, 2625...
4 22625006625250 14 kommune.28 d285e9c4-8da0-4dfc-b927-31ba7ee17e6a https://data.geonorge.no/sosi/inndelinger/innd... 3 2023-11-28 16:00:22 2024-01-01 2024-03-04 12:27:57 Ås nor 3218 Ås False POLYGON ((262500 6625250, 262500 6625500, 2627...

Som vi kan se, på grunn av overleggsanalysen, inneholder datasettet attributtene fra begge input lagene.

La oss lagre resultatrutenettet vårt som en GeoPackage.

intersection.to_file(DATA_MAPPE / "intersection.geojson")

Det er mange flere eksempler på forskjellige typer overleggsanalyse i Geopandas dokumentasjon hvor du kan gå og lære mer.