Skip to content

Commit

Permalink
feat: allow tag dict column
Browse files Browse the repository at this point in the history
closes #130
  • Loading branch information
SlowMo24 authored Nov 20, 2023
1 parent 28a1f35 commit bcf2ad7
Show file tree
Hide file tree
Showing 4 changed files with 153 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
- accept shapely Polygon and MultiPolygon for `bpolys` input parameter
- if a request fails a bash script containing the respective `curl` command is logged (if possible). This allows for easier debugging and sharing of failed requests.

### Changed

- breaking: geodataframes now contain a `@other_tags` colum containing all OSM tags. This behaviour can be adapted using the `explode_tags` parameter that allows to specify tags that should be in a separate column or to disable the feature completely. The latter will result in a potentially wide but sparse data frame.

### Removed

- support for python < 3.10
Expand Down
32 changes: 29 additions & 3 deletions ohsome/response.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"""Class for ohsome API response"""

import json
from typing import Optional

import geopandas as gpd
import pandas as pd
Expand All @@ -22,17 +23,22 @@ def __init__(self, response=None, url=None, params=None):
self.parameters = params
self.data = response.json()

def as_dataframe(self, multi_index=True):
def as_dataframe(
self, multi_index: Optional[bool] = True, explode_tags: Optional[tuple] = ()
):
"""
Converts the ohsome response to a pandas.DataFrame or a geopandas.GeoDataFrame if the
response contains geometries
:param multi_index: If true returns the dataframe with a multi index
:param explode_tags: By default, tags of extracted features are stored in a single dict-column. You can specify
a tuple of tags that should be popped from this column. To disable it completely, pass None. Yet, be aware that
you may get a large but sparse data frame.
:return: pandas.DataFrame or geopandas.GeoDataFrame
"""
if "features" not in self.data.keys():
return self._as_dataframe(multi_index)
else:
return self._as_geodataframe(multi_index)
return self._as_geodataframe(multi_index, explode_tags)

def _as_dataframe(self, multi_index=True):
"""
Expand Down Expand Up @@ -67,7 +73,9 @@ def _as_dataframe(self, multi_index=True):

return result_df.sort_index()

def _as_geodataframe(self, multi_index=True):
def _as_geodataframe(
self, multi_index: Optional[bool] = True, explode_tags: Optional[tuple] = ()
):
"""
Converts the ohsome response to a geopandas.GeoDataFrame
:param multi_index: If true returns the dataframe with a multi index
Expand All @@ -78,7 +86,25 @@ def _as_geodataframe(self, multi_index=True):
return gpd.GeoDataFrame(crs="epsg:4326", columns=["@osmId", "geometry"])

try:
if explode_tags is not None:
for feature in self.data["features"]:
properties = feature["properties"]
tags = {}
new_properties = {}
for k in properties.keys():
if (
(k.startswith("@"))
or (k == "timestamp")
or (k in explode_tags)
):
new_properties[k] = properties.get(k)
else:
tags[k] = properties.get(k)
new_properties["@other_tags"] = tags
feature["properties"] = new_properties

features = gpd.GeoDataFrame().from_features(self.data, crs="epsg:4326")

except TypeError:
raise TypeError(
"This result type cannot be converted to a GeoPandas GeoDataFrame object."
Expand Down
98 changes: 98 additions & 0 deletions ohsome/test/cassettes/test_response/test_extra_tags_argument.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
interactions:
- request:
body: bboxes=8.7137%2C49.4096%2C8.717%2C49.4119&time=2016-01-01&filter=name%3DKrautturm+and+type%3Away&properties=tags%2Cmetadata
headers:
Accept:
- '*/*'
Accept-Encoding:
- gzip, deflate
Connection:
- keep-alive
Content-Length:
- '123'
Content-Type:
- application/x-www-form-urlencoded
user-agent:
- ohsome-py/0.2.0
method: POST
uri: https://api.ohsome.org/v1/elements/geometry
response:
body:
string: "{\n \"attribution\" : {\n \"url\" : \"https://ohsome.org/copyrights\",\n
\ \"text\" : \"\xA9 OpenStreetMap contributors\"\n },\n \"apiVersion\"
: \"1.10.1\",\n \"type\" : \"FeatureCollection\",\n \"features\" : [{\n
\ \"type\" : \"Feature\",\n \"geometry\" : {\n \"type\" : \"Polygon\",\n
\ \"coordinates\" : [\n [\n [\n 8.7160632,\n
\ 49.4102899\n ],\n [\n 8.7160749,\n
\ 49.4103121\n ],\n [\n 8.7160827,\n
\ 49.4103235\n ],\n [\n 8.7160963,\n
\ 49.4103374\n ],\n [\n 8.716121,\n
\ 49.4103549\n ],\n [\n 8.7161392,\n
\ 49.4103691\n ],\n [\n 8.7161626,\n
\ 49.4103819\n ],\n [\n 8.716186,\n
\ 49.4103894\n ],\n [\n 8.716225,\n
\ 49.4103952\n ],\n [\n 8.7162679,\n
\ 49.4103926\n ],\n [\n 8.7162893,\n
\ 49.4103882\n ],\n [\n 8.7163176,\n
\ 49.410378\n ],\n [\n 8.7163507,\n
\ 49.4103615\n ],\n [\n 8.7163829,\n
\ 49.4103317\n ],\n [\n 8.7163975,\n
\ 49.4103057\n ],\n [\n 8.7164024,\n
\ 49.4102791\n ],\n [\n 8.7164004,\n
\ 49.4102506\n ],\n [\n 8.7163868,\n
\ 49.4102277\n ],\n [\n 8.7162815,\n
\ 49.4102258\n ],\n [\n 8.7162659,\n
\ 49.4102189\n ],\n [\n 8.7162503,\n
\ 49.4102144\n ],\n [\n 8.7162289,\n
\ 49.4102106\n ],\n [\n 8.7162113,\n
\ 49.4102093\n ],\n [\n 8.7161987,\n
\ 49.4101992\n ],\n [\n 8.7161665,\n
\ 49.4101992\n ],\n [\n 8.7161519,\n
\ 49.4101954\n ],\n [\n 8.7161343,\n
\ 49.4101846\n ],\n [\n 8.7161119,\n
\ 49.4101967\n ],\n [\n 8.7160934,\n
\ 49.4102119\n ],\n [\n 8.7160807,\n
\ 49.4102277\n ],\n [\n 8.7160719,\n
\ 49.4102449\n ],\n [\n 8.7160671,\n
\ 49.4102594\n ],\n [\n 8.7160641,\n
\ 49.4102753\n ],\n [\n 8.7160632,\n
\ 49.4102899\n ]\n ]\n ]\n },\n \"properties\"
: {\n \"@changesetId\" : 30687511,\n \"@lastEdit\" : \"2015-05-01T10:33:22Z\",\n
\ \"@osmId\" : \"way/24885641\",\n \"@osmType\" : \"way\",\n \"@snapshotTimestamp\"
: \"2016-01-01T00:00:00Z\",\n \"@version\" : 4,\n \"building\" :
\"yes\",\n \"name\" : \"Krautturm\"\n }\n }]\n}\n"
headers:
Access-Control-Allow-Credentials:
- 'true'
Access-Control-Allow-Headers:
- Origin,Accept,X-Requested-With,Content-Type,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization
Access-Control-Allow-Methods:
- POST, GET
Access-Control-Allow-Origin:
- '*'
Access-Control-Max-Age:
- '3600'
Connection:
- Keep-Alive
Content-Encoding:
- gzip
Content-Type:
- application/geo+json;charset=utf-8
Content-disposition:
- attachment;filename=ohsome.geojson
Date:
- Fri, 17 Nov 2023 14:22:26 GMT
Keep-Alive:
- timeout=5, max=100
Server:
- Apache
Strict-Transport-Security:
- max-age=63072000; includeSubdomains;
Transfer-Encoding:
- chunked
vary:
- accept-encoding
status:
code: 200
message: ''
version: 1
22 changes: 22 additions & 0 deletions ohsome/test/test_response.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,28 @@ def test_elements_geometry(base_client):
assert len(result) == 1


@pytest.mark.vcr
def test_extra_tags_argument(base_client):
"""
Tests whether the result of elements.geometry is converted to a geopandas.GeoDataFrame
:return:
"""
bboxes = "8.7137,49.4096,8.717,49.4119"
time = "2016-01-01"
flter = "name=Krautturm and type:way"

response = base_client.elements.geometry.post(
bboxes=bboxes, time=time, filter=flter, properties="tags,metadata"
)
result = response.as_dataframe()

assert "@other_tags" in result.columns
assert "@version" in result.columns

assert result["@other_tags"].to_list() == [{"building": "yes", "name": "Krautturm"}]
assert result["@version"].to_list() == [4]


@pytest.mark.vcr
def test_elementsFullHistory_geometry(base_client):
"""
Expand Down

0 comments on commit bcf2ad7

Please sign in to comment.