💻 Geokoding i geopandas#
Geopandas støtter geokoding via et bibliotek kalt
geopy, som må være installert for å bruke
geopandas ‘ geopandas.tools.geocode()
funksjon.
geocode() forventer en liste eller pandas.Series av adresser (strenger) og
returnerer en GeoDataFrame med løste adresser og punktgeometrier.
La oss prøve dette ut.
Vi vil geokode adresser lagret i en semikolon-separert tekstfil kalt
adresser.txt. Disse adressene ligger i alle i Oslo.
import pathlib
NOTEBOOK_PATH = pathlib.Path().resolve()
DATA_MAPPE = NOTEBOOK_PATH / "data"
import pandas
adresser = pandas.read_csv(
    DATA_MAPPE / "oslo_adresser" / "adresser.txt",
    sep=";"
)
adresser.head()
| id | adr | |
|---|---|---|
| 0 | 100 | Statsråd Mathiesens vei 25, 0594 OSLO | 
| 1 | 101 | Slimeveien 15, 1275 OSLO | 
| 2 | 102 | Sognsveien 80, 0855 OSLO | 
| 3 | 103 | Ullevålsveien 5, 0165 OSLO | 
| 4 | 104 | Nydalsveien 30b, 0484 OSLO | 
Vi har en id for hver rad og en adresse i adr kolonnen.
Geokode adresser ved hjelp av Nominatim#
I vårt eksempel vil vi bruke Nominatim som en geokodingstilbyder. Nominatim er et bibliotek og en tjeneste som bruker OpenStreetMap-data, og drives av OpenStreetMap Foundation. Geopandas ‘
geocode()
funksjon støtter den innebygd.
God bruk
Nominatims brukervilkår krever at brukere av tjenesten sørger for at de ikke sender mer hyppige forespørsler enn en per sekund, og at en tilpasset bruker-agent streng er knyttet til hver forespørsel.
Geopandas’ implementering lar oss spesifisere en user_agent; biblioteket tar også
hånd om å respektere hastighetsbegrensningen til Nominatim.
Å slå opp en adresse er en ganske kostbar databaseoperasjon. Derfor er det,
noen ganger, den offentlige og gratise Nominatim-serveren bruker litt lenger tid å på
svare. I dette eksempelet legger vi til en parameter timeout=10 for å vente opptil 10
sekunder for et svar.
import geopandas
geokodede_adresser = geopandas.tools.geocode(
    adresser["adr"],
    provider="nominatim",
    user_agent="gmgi221",
    timeout=10
)
geokodede_adresser.head()
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
Cell In[3], line 3
      1 import geopandas
----> 3 geokodede_adresser = geopandas.tools.geocode(
      4     adresser["adr"],
      5     provider="nominatim",
      6     user_agent="gmgi221",
      7     timeout=10
      8 )
      9 geokodede_adresser.head()
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/geopandas/tools/geocoding.py:66, in geocode(strings, provider, **kwargs)
     63     provider = "photon"
     64 throttle_time = _get_throttle_time(provider)
---> 66 return _query(strings, True, provider, throttle_time, **kwargs)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/geopandas/tools/geocoding.py:139, in _query(data, forward, provider, throttle_time, **kwargs)
    137 try:
    138     if forward:
--> 139         results[i] = coder.geocode(s)
    140     else:
    141         results[i] = coder.reverse((s.y, s.x), exactly_one=True)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/geopy/geocoders/nominatim.py:297, in Nominatim.geocode(self, query, exactly_one, timeout, limit, addressdetails, language, geometry, extratags, country_codes, viewbox, bounded, featuretype, namedetails)
    295 logger.debug("%s.geocode: %s", self.__class__.__name__, url)
    296 callback = partial(self._parse_json, exactly_one=exactly_one)
--> 297 return self._call_geocoder(url, callback, timeout=timeout)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/geopy/geocoders/base.py:368, in Geocoder._call_geocoder(self, url, callback, timeout, is_json, headers)
    366 try:
    367     if is_json:
--> 368         result = self.adapter.get_json(url, timeout=timeout, headers=req_headers)
    369     else:
    370         result = self.adapter.get_text(url, timeout=timeout, headers=req_headers)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/geopy/adapters.py:472, in RequestsAdapter.get_json(self, url, timeout, headers)
    471 def get_json(self, url, *, timeout, headers):
--> 472     resp = self._request(url, timeout=timeout, headers=headers)
    473     try:
    474         return resp.json()
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/geopy/adapters.py:482, in RequestsAdapter._request(self, url, timeout, headers)
    480 def _request(self, url, *, timeout, headers):
    481     try:
--> 482         resp = self.session.get(url, timeout=timeout, headers=headers)
    483     except Exception as error:
    484         message = str(error)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/requests/sessions.py:602, in Session.get(self, url, **kwargs)
    594 r"""Sends a GET request. Returns :class:`Response` object.
    595 
    596 :param url: URL for the new :class:`Request` object.
    597 :param \*\*kwargs: Optional arguments that ``request`` takes.
    598 :rtype: requests.Response
    599 """
    601 kwargs.setdefault("allow_redirects", True)
--> 602 return self.request("GET", url, **kwargs)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/requests/sessions.py:589, in Session.request(self, method, url, params, data, headers, cookies, files, auth, timeout, allow_redirects, proxies, hooks, stream, verify, cert, json)
    584 send_kwargs = {
    585     "timeout": timeout,
    586     "allow_redirects": allow_redirects,
    587 }
    588 send_kwargs.update(settings)
--> 589 resp = self.send(prep, **send_kwargs)
    591 return resp
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/requests/sessions.py:703, in Session.send(self, request, **kwargs)
    700 start = preferred_clock()
    702 # Send the request
--> 703 r = adapter.send(request, **kwargs)
    705 # Total elapsed time of the request (approximately)
    706 elapsed = preferred_clock() - start
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/requests/adapters.py:644, in HTTPAdapter.send(self, request, stream, timeout, verify, cert, proxies)
    641     timeout = TimeoutSauce(connect=timeout, read=timeout)
    643 try:
--> 644     resp = conn.urlopen(
    645         method=request.method,
    646         url=url,
    647         body=request.body,
    648         headers=request.headers,
    649         redirect=False,
    650         assert_same_host=False,
    651         preload_content=False,
    652         decode_content=False,
    653         retries=self.max_retries,
    654         timeout=timeout,
    655         chunked=chunked,
    656     )
    658 except (ProtocolError, OSError) as err:
    659     raise ConnectionError(err, request=request)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/urllib3/connectionpool.py:787, in HTTPConnectionPool.urlopen(self, method, url, body, headers, retries, redirect, assert_same_host, timeout, pool_timeout, release_conn, chunked, body_pos, preload_content, decode_content, **response_kw)
    784 response_conn = conn if not release_conn else None
    786 # Make the request on the HTTPConnection object
--> 787 response = self._make_request(
    788     conn,
    789     method,
    790     url,
    791     timeout=timeout_obj,
    792     body=body,
    793     headers=headers,
    794     chunked=chunked,
    795     retries=retries,
    796     response_conn=response_conn,
    797     preload_content=preload_content,
    798     decode_content=decode_content,
    799     **response_kw,
    800 )
    802 # Everything went great!
    803 clean_exit = True
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/urllib3/connectionpool.py:534, in HTTPConnectionPool._make_request(self, conn, method, url, body, headers, retries, timeout, chunked, response_conn, preload_content, decode_content, enforce_content_length)
    532 # Receive the response from the server
    533 try:
--> 534     response = conn.getresponse()
    535 except (BaseSSLError, OSError) as e:
    536     self._raise_timeout(err=e, url=url, timeout_value=read_timeout)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/site-packages/urllib3/connection.py:565, in HTTPConnection.getresponse(self)
    562 _shutdown = getattr(self.sock, "shutdown", None)
    564 # Get the response from http.client.HTTPConnection
--> 565 httplib_response = super().getresponse()
    567 try:
    568     assert_header_parsing(httplib_response.msg)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/http/client.py:1395, in HTTPConnection.getresponse(self)
   1393 try:
   1394     try:
-> 1395         response.begin()
   1396     except ConnectionError:
   1397         self.close()
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/http/client.py:325, in HTTPResponse.begin(self)
    323 # read until we get a non-100 response
    324 while True:
--> 325     version, status, reason = self._read_status()
    326     if status != CONTINUE:
    327         break
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/http/client.py:286, in HTTPResponse._read_status(self)
    285 def _read_status(self):
--> 286     line = str(self.fp.readline(_MAXLINE + 1), "iso-8859-1")
    287     if len(line) > _MAXLINE:
    288         raise LineTooLong("status line")
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/socket.py:718, in SocketIO.readinto(self, b)
    716 while True:
    717     try:
--> 718         return self._sock.recv_into(b)
    719     except timeout:
    720         self._timeout_occurred = True
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/ssl.py:1314, in SSLSocket.recv_into(self, buffer, nbytes, flags)
   1310     if flags != 0:
   1311         raise ValueError(
   1312           "non-zero flags not allowed in calls to recv_into() on %s" %
   1313           self.__class__)
-> 1314     return self.read(nbytes, buffer)
   1315 else:
   1316     return super().recv_into(buffer, nbytes, flags)
File /opt/hostedtoolcache/Python/3.11.13/x64/lib/python3.11/ssl.py:1166, in SSLSocket.read(self, len, buffer)
   1164 try:
   1165     if buffer is not None:
-> 1166         return self._sslobj.read(len, buffer)
   1167     else:
   1168         return self._sslobj.read(len)
KeyboardInterrupt: 
Et voilà! Som et resultat fikk vi tilbake en GeoDataFrame som inneholder en analysert
versjon av våre originale adresser og en geometry kolonne med
shapely.geometry.Points som vi kan bruke, for eksempel, til å eksportere dataene til
et romlig dataformat.
Imidlertid ble id-kolonnen forkastet i prosessen. For å kombinere inputdatasettet med resultatsettet vårt, kan vi bruke pandas’ join
operasjoner.
Koble sammen dataframes#
Koble sammen datasett ved hjelp av pandas
For en omfattende oversikt over forskjellige måter å kombinere dataframes og Series, ta en titt på pandas dokumentasjon om merge, join og concatenate.
Å koble data fra to eller flere dataframes eller tabeller er en vanlig oppgave i mange
(romlige) dataanalysearbeidsflyter. Som du kanskje husker fra våre tidligere
timer, kan kombinering av data fra forskjellige tabeller basert på en felles nøkkel-attributt
gjøres enkelt i pandas/geopandas ved hjelp av merge()
funksjonen.
Men, noen ganger er det nyttig å koble to dataframes sammen basert på deres indeks. Dataframes må ha samme antall rader og dele den samme indeksen (enkelt forklart, de skal ha samme rekkefølge av rader).
Vi kan bruke denne tilnærmingen, for å koble informasjon fra den originale dataframen adresser til de geokodede adressene geokodede_adresser, rad for rad.
join()-funksjonen, som standard, kobler to dataframes basert på indeksen deres.
Dette fungerer for eksemplet vårt, da rekkefølgen på de to dataframesene er identiske.
geokodede_adresser_med_id = geokodede_adresser.join(adresser)
geokodede_adresser_med_id
| geometry | address | id | adr | |
|---|---|---|---|---|
| 0 | POINT (10.83648 59.94104) | 25, Statsråd Mathiesens vei, Linderud, Bjerke,... | 100 | Statsråd Mathiesens vei 25, 0594 OSLO | 
| 1 | POINT (10.83432 59.83557) | 15, Slimeveien, Bjørnholt, Søndre Nordstrand, ... | 101 | Slimeveien 15, 1275 OSLO | 
| 2 | POINT (10.72956 59.95011) | Sognsveien 80, Konvallveien, Sogn, Nordre Aker... | 102 | Sognsveien 80, 0855 OSLO | 
| 3 | POINT (10.74356 59.91863) | 5, Ullevålsveien, Hammersborg, St. Hanshaugen,... | 103 | Ullevålsveien 5, 0165 OSLO | 
| 4 | POINT (10.76402 59.9503) | 30B, Nydalsveien, Nydalen, Nordre Aker, Oslo, ... | 104 | Nydalsveien 30b, 0484 OSLO | 
| 5 | POINT (10.75292 59.919) | 3, Vestre Elvebakke, Fredensborg, Grünerløkka,... | 105 | Vestre Elvebakke 3, 0182 OSLO | 
| 6 | POINT (10.79645 59.90968) | 5, Etterstadsletta, Gamle Oslo, Oslo, 0660, Norge | 106 | Etterstadsletta 5, 0660 OSLO | 
| 7 | POINT (10.75544 59.92704) | 20, Steenstrups gate, Grünerløkka, Oslo, 0554,... | 107 | Steenstrups gate 20, 0554 OSLO | 
| 8 | POINT (10.79432 59.91526) | 21, Fyrstikkalléen, Lilleberg, Gamle Oslo, Osl... | 108 | Fyrstikkalleen 21, 0661 OSLO | 
| 9 | POINT (10.71724 59.91854) | Niels Juels gate, Uranienborg, Frogner, Oslo, ... | 109 | Niels Juels gate 56, 0259 OSLO | 
| 10 | POINT (10.84019 59.91439) | 6, Wilhelm Stenersens vei, Tveita, Alna, Oslo,... | 110 | Wilhelm Stenersens vei 6, 0671 OSLO | 
| 11 | POINT (10.76403 59.91812) | 20B, Herslebs gate, Grønland, Gamle Oslo, Oslo... | 111 | Herslebs gate 20B, 0561 OSLO | 
| 12 | POINT (10.78564 59.88545) | 124, Ekebergveien, Holtet, Nordstrand, Oslo, 1... | 112 | Ekebergveien 124, 1178 OSLO | 
| 13 | POINT (10.74013 59.93902) | Ullevål sykehus, Gäbleins vei, Ullevål hageby,... | 113 | Ullevål sykehus, 0450 OSLO | 
| 14 | POINT (10.75824 59.89739) | 30, Kongsveien, Grønlia, Gamle Oslo, Oslo, 019... | 114 | Kongsveien 30, 0193 OSLO | 
| 15 | POINT (10.7903 59.91467) | Gladengveien 3B, Gladengveien, Ensjø, Gamle Os... | 115 | Gladengveien 3B, 0661 OSLO | 
| 16 | POINT (10.81631 59.92734) | 10, Kabelgata, Mellom-Hovin, Bjerke, Oslo, 058... | 116 | Kabelgata 10, 0580 OSLO | 
| 17 | POINT (10.81041 59.87888) | 6, Cecilie Thoresens vei, Karlsrud, Nordstrand... | 117 | Cecilie Thoresens vei 6, 1153 OSLO | 
| 18 | POINT (10.71752 59.95709) | 67, Sognsvannsveien, Rabben, Nordre Aker, Oslo... | 118 | Sognsvannsveien 67, 0372 OSLO | 
| 19 | POINT (10.76286 59.95056) | 30C, Nydalsveien, Nydalen, Nordre Aker, Oslo, ... | 119 | Nydalsveien 30c, 0484 OSLO | 
| 20 | POINT (10.7206 59.91399) | 65, Parkveien, Ruseløkka, Frogner, Oslo, 0254,... | 120 | Parkveien 65, 0254 OSLO | 
| 21 | POINT (10.7416 59.92115) | 31, Ullevålsveien, Hammersborg, St. Hanshaugen... | 121 | Ullevålsveien 31, 0131 OSLO | 
| 22 | POINT (10.65787 59.94535) | 1, Gamle Hovsetervei, Nordre Huseby, Vestre Ak... | 122 | Gamle Hovsetervei 1, 0768 OSLO | 
| 23 | POINT (10.92678 59.9605) | 25, Karl Fossums vei, Fossum, Stovner, Oslo, 0... | 123 | Karl Fossums vei 25, 0913 OSLO | 
| 24 | POINT (10.71712 59.94804) | 11, Sognsvannsveien, Gaustad, Nordre Aker, Osl... | 124 | Sognsvannsveien 11, 0372 OSLO | 
| 25 | POINT (10.66345 59.93113) | 66, Ullernchausséen, Montebello, Ullern, Oslo,... | 125 | Ullernchaussèen 66, 0379 OSLO | 
| 26 | POINT (10.85068 59.88579) | 5, Tor Jonssons veg, Myrvoll, Østensjø, Oslo, ... | 126 | Tor Jonssons veg 5, 0688 OSLO | 
| 27 | POINT (10.80587 59.91724) | 16G, Innspurten, Gullhaug, Gamle Oslo, Oslo, 0... | 127 | Innspurten 16, 0663 OSLO | 
| 28 | POINT (10.72413 59.91304) | 30, Cort Adelers gate, Ruseløkka, Frogner, Osl... | 128 | Cort Adelers gate 30, 0254 OSLO | 
| 29 | POINT (10.78756 59.93246) | Lørenveien 11, Lørenveien, Sinsen, Grünerløkka... | 129 | Lørenveien 11, 0585 OSLO | 
Utdataen fra join() er en ny geopandas.GeoDataFrame:
type(geokodede_adresser_med_id)
geopandas.geodataframe.GeoDataFrame
Den nye data rammen har alle originale kolonner pluss nye kolonner for geometry
og for en analysert adresse som kan brukes til å spot-sjekke resultatene.
Note
Hvis du skulle gjøre join den andre veien, dvs. adresser.join(geokodede_adresser), ville utdata være en pandas.DataFrame, ikke en geopandas.GeoDataFrame.
Det er nå enkelt å lagre det nye datasettet som en geospatial fil, for eksempel, i GeoPackage format:
geokodede_adresser_med_id.to_file(DATA_MAPPE / "oslo_adresser" / "adresser.gpkg")