Skip to content

Commit

Permalink
Read space data as geopandas dataframe
Browse files Browse the repository at this point in the history
Signed-off-by: Kharude, Sachin <sachin.kharude@here.com>
  • Loading branch information
Kharude, Sachin authored and sackh committed Sep 7, 2020
1 parent 7aac30c commit 4a1926d
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 23 deletions.
34 changes: 28 additions & 6 deletions tests/space/test_space_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,10 @@ def test_space_search(space_object):
assert feats[0]["type"] == "Feature"
assert len(feats) > 0

feats = list(space_object.search(limit=10))
assert feats[0]["type"] == "Feature"
assert len(feats) <= 10
feats = space_object.search(limit=10, geo_dataframe=True)
gdf = next(feats)
assert isinstance(gdf, gpd.GeoDataFrame)
assert gdf["name"][0] == "Afghanistan"

feats = list(space_object.search(tags=["non-existing"]))
assert len(feats) == 0
Expand Down Expand Up @@ -168,6 +169,10 @@ def test_space_feature_operations(space_object):
@pytest.mark.skipif(not XYZ_TOKEN, reason="No token found.")
def test_space_features_operations(space_object):
"""Test for get, add, update and delete features operations."""
gdf = space_object.get_features(
feature_ids=["DEU", "ITA"], geo_dataframe=True
)
assert isinstance(gdf, gpd.GeoDataFrame)
# get two features
data = space_object.get_features(feature_ids=["DEU", "ITA"])
assert isinstance(data, GeoJSON)
Expand All @@ -191,9 +196,10 @@ def test_space_features_search_operations(space_object):
assert len(bbox) == 15
assert bbox[0]["type"] == "Feature"

tile = list(space_object.features_in_tile(tile_type="here", tile_id="12"))
assert len(tile) == 97
assert tile[0]["type"] == "Feature"
gdf = next(
space_object.features_in_bbox(bbox=[0, 0, 20, 20], geo_dataframe=True)
)
assert gdf.shape == (15, 3)

spatial_search = list(
space_object.spatial_search(
Expand All @@ -203,14 +209,30 @@ def test_space_features_search_operations(space_object):
assert spatial_search[0]["type"] == "Feature"
assert spatial_search[0]["id"] == "AFG"

ss_gdf = next(
space_object.spatial_search(
lat=37.377228699000057, lon=74.512691691000043, geo_dataframe=True
)
)
assert ss_gdf.shape == (1, 3)

data1 = {"type": "Point", "coordinates": [72.8557, 19.1526]}
spatial_search_geom = list(
space_object.spatial_search_geometry(data=data1)
)
assert spatial_search_geom[0]["type"] == "Feature"
assert spatial_search_geom[0]["id"] == "IND"
ss_gdf = next(
space_object.spatial_search_geometry(data=data1, geo_dataframe=True)
)
assert ss_gdf.shape == (1, 3)
with pytest.raises(ValueError):
list(space_object.features_in_tile(tile_type="dummy", tile_id="12"))
res = space_object.features_in_tile(
tile_type="here", tile_id="12", limit=10, geo_dataframe=True
)
gdf = next(res)
assert gdf.shape == (10, 3)


@pytest.mark.skipif(not XYZ_TOKEN, reason="No token found.")
Expand Down
83 changes: 66 additions & 17 deletions xyzspaces/spaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ def search(
params: Optional[dict] = None,
selection: Optional[List[str]] = None,
skip_cache: Optional[bool] = None,
geo_dataframe: Optional[bool] = None,
) -> Generator[Feature, None, None]:
"""
Search features for this space object.
Expand Down Expand Up @@ -305,7 +306,10 @@ def search(
result list.
:param skip_cache: If set to ``True`` the response is not returned from cache.
Default is ``False``.
:yields: A Feature object.
:param geo_dataframe: A boolean if set to ``True`` searched features will be
yield as single Geopandas Dataframe.
:yields: A Feature object by default. If param ``geo_dataframe`` is True then
yields single Geopandas Dataframe.
"""
features = self.api.get_space_search(
space_id=self._info["id"],
Expand All @@ -315,6 +319,8 @@ def search(
selection=selection,
skip_cache=skip_cache,
)
if geo_dataframe is not None:
yield gpd.GeoDataFrame.from_features(features=features["features"])
for f in features["features"]:
yield f

Expand Down Expand Up @@ -408,17 +414,24 @@ def delete_feature(self, feature_id: str):
space_id=self._info["id"], feature_id=feature_id
)

def get_features(self, feature_ids: List[str]) -> GeoJSON:
def get_features(
self, feature_ids: List[str], geo_dataframe: Optional[bool] = None
) -> Union[GeoJSON, gpd.GeoDataFrame]:
"""
Retrieve one GeoJSON feature with given ID from this space.
:param feature_ids: A list of feature_ids.
:param geo_dataframe: A boolean if set to ``True`` features will be
returned as single Geopandas Dataframe.
:return: A feature collection with all features inside the specified
space.
space. If param ``geo_dataframe`` is set to ``True`` then return features
in single Geopandas Dataframe.
"""
res = self.api.get_space_features(
space_id=self._info["id"], feature_ids=feature_ids
)
if geo_dataframe is not None:
return gpd.GeoDataFrame.from_features(features=res["features"])
return GeoJSON(res)

def add_features(
Expand Down Expand Up @@ -611,6 +624,7 @@ def features_in_bbox(
skip_cache: Optional[bool] = None,
clustering: Optional[str] = None,
clustering_params: Optional[dict] = None,
geo_dataframe: Optional[bool] = None,
) -> Generator[Feature, None, None]:
"""
Get features inside some given bounding box.
Expand Down Expand Up @@ -651,7 +665,10 @@ def features_in_bbox(
Default is ``False``.
:param clustering: ...
:param clustering_params: ...
:yields: A Feature object.
:param geo_dataframe: A boolean if set to ``True`` searched features will be
yield as single Geopandas Dataframe.
:yields: A Feature object by default. If param ``geo_dataframe`` is True then
yields single Geopandas Dataframe.
"""
features = self.api.get_space_bbox(
space_id=self._info["id"],
Expand All @@ -665,8 +682,12 @@ def features_in_bbox(
clustering=clustering,
clusteringParams=clustering_params,
)
for f in features["features"]:
yield f

if geo_dataframe is not None:
yield gpd.GeoDataFrame.from_features(features=features["features"])
else:
for f in features["features"]:
yield f

def features_in_tile(
self,
Expand All @@ -681,6 +702,7 @@ def features_in_tile(
clustering_params: Optional[dict] = None,
margin: Optional[int] = None,
limit: Optional[int] = None,
geo_dataframe: Optional[bool] = None,
) -> Generator[Feature, None, None]:
"""
Get features in tile.
Expand Down Expand Up @@ -724,7 +746,10 @@ def features_in_tile(
:param clustering_params: ...
:param margin: ...
:param limit: A max. number of features to return in the result.
:yields: A Feature object.
:param geo_dataframe: A boolean if set to ``True`` searched features will be
yield as single Geopandas Dataframe.
:yields: A Feature object by default. If param ``geo_dataframe`` is True then
yields single Geopandas Dataframe.
:raises ValueError: If `tile_type` is invalid, valid tile_types are
`quadkeys`, `web`, `tms` and `here`.
"""
Expand All @@ -743,8 +768,13 @@ def features_in_tile(
margin=margin,
limit=limit,
)
for f in features["features"]:
yield f
if geo_dataframe is not None:
yield gpd.GeoDataFrame.from_features(
features=features["features"]
)
else:
for f in features["features"]:
yield f
else:
raise ValueError("Invalid value for parameter tile_type")

Expand All @@ -760,6 +790,7 @@ def spatial_search(
params: Optional[dict] = None,
selection: Optional[List[str]] = None,
skip_cache: Optional[bool] = None,
geo_dataframe: Optional[bool] = None,
) -> Generator[Feature, None, None]:
"""
Get features with radius search.
Expand Down Expand Up @@ -804,7 +835,10 @@ def spatial_search(
:param selection: A list of strings holding properties values.
:param skip_cache: A Boolean if set to ``True`` the response is not returned from
cache.
:yields: A Feature object.
:param geo_dataframe: A boolean if set to ``True`` searched features will be
yield as single Geopandas Dataframe.
:yields: A Feature object by default. If param ``geo_dataframe`` is True then
yields single Geopandas Dataframe.
"""
features = self.api.get_space_spatial(
space_id=self._info["id"],
Expand All @@ -819,8 +853,11 @@ def spatial_search(
selection=selection,
skip_cache=skip_cache,
)
for f in features["features"]:
yield f
if geo_dataframe is not None:
yield gpd.GeoDataFrame.from_features(features=features["features"])
else:
for f in features["features"]:
yield f

def spatial_search_geometry(
self,
Expand All @@ -835,6 +872,7 @@ def spatial_search_geometry(
cell_width: Optional[float] = None,
units: Optional[str] = "m",
chunk_size: int = 1,
geo_dataframe: Optional[bool] = None,
) -> Generator[Feature, None, None]:
"""
Search features which intersect the provided geometry.
Expand Down Expand Up @@ -878,7 +916,10 @@ def spatial_search_geometry(
:param chunk_size: Number of chunks each process to handle. The default value is
1, for a large number of features please use `chunk_size` greater than 1
to get better results in terms of performance.
:yields: A Feature object.
:param geo_dataframe: A boolean if set to ``True`` searched features will be
yield as single Geopandas Dataframe.
:yields: A Feature object by default. If param ``geo_dataframe`` is True then
yields as single Geopandas Dataframe.
"""
if not divide:
features = self.api.post_space_spatial(
Expand All @@ -891,8 +932,13 @@ def spatial_search_geometry(
selection=selection,
skip_cache=skip_cache,
)
for f in features["features"]:
yield f
if geo_dataframe is not None:
yield gpd.GeoDataFrame.from_features(
features=features["features"]
)
else:
for f in features["features"]:
yield f
else:
divide_features = divide_bbox(
Feature(geometry=data), cell_width, units
Expand Down Expand Up @@ -924,8 +970,11 @@ def spatial_search_geometry(
each["id"]: each for each in feature_list
}.values()

for f in unique_features:
yield f
if geo_dataframe is not None:
yield gpd.GeoDataFrame.from_features(features=unique_features)
else:
for f in unique_features:
yield f

def _spatial_search_geometry(
self,
Expand Down

0 comments on commit 4a1926d

Please sign in to comment.