Skip to content

Commit

Permalink
Change type of attribute bpolys to Feature
Browse files Browse the repository at this point in the history
  • Loading branch information
matthiasschaub committed Jun 29, 2021
1 parent ffffe88 commit 787f4c5
Show file tree
Hide file tree
Showing 32 changed files with 203 additions and 226 deletions.
7 changes: 4 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@
- Return a GeoJSON when computing an indicator from the CLI using a dataset and FID ([#57])
- Update MapAction layers and POC report ([#56])
- Simplify CLI option handling by only allowing one option at a time to be added ([#54])
- Redefine OQT regions ([26])
- Change type of attribute bpolys to be of Polygon or MultyPolygon ([63])
- Redefine OQT regions ([#26])
- Change type of attribute bpolys to be GeoJSON.Feature ([#69] [#9])

[#28]: https://github.com/GIScience/ohsome-quality-analyst/pull/28
[#40]: https://github.com/GIScience/ohsome-quality-analyst/pull/40
Expand All @@ -34,7 +34,8 @@
[#56]: https://github.com/GIScience/ohsome-quality-analyst/pull/56
[#54]: https://github.com/GIScience/ohsome-quality-analyst/pull/54
[#26]: https://github.com/GIScience/ohsome-quality-analyst/issues/26
[#63]: https://github.com/GIScience/ohsome-quality-analyst/pull/63
[#69]: https://github.com/GIScience/ohsome-quality-analyst/pull/69
[#9]: https://github.com/GIScience/ohsome-quality-analyst/issues/9


## 0.3.1
Expand Down
35 changes: 21 additions & 14 deletions workers/ohsome_quality_analyst/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,29 +63,32 @@ def int_or_str_param_type(value: str) -> Union[int, str]:
raise ValueError("Given parameter is not a valid integer or string")


async def load_bpolys(bpolys: str) -> Union[Polygon, MultiPolygon]:
async def load_bpolys(bpolys: str) -> Feature:
"""Load as GeoJSON object, validate and check size of bpolys"""
# TODO: Return API response with error message

async def check_geom_size(geom):
if await db_client.get_area_of_bpolys(geom) > GEOM_SIZE_LIMIT:
raise ValueError(
"Input GeoJSON Object is too big. "
+ "The area should be less than {0} sqkm.".format(GEOM_SIZE_LIMIT)
)

bpolys = geojson.loads(bpolys)

if bpolys.is_valid is False:
raise ValueError("Input geometry is not valid")

if isinstance(bpolys, Feature):
bpolys = bpolys["geometry"]

if isinstance(bpolys, (Polygon, MultiPolygon)) is False:
elif isinstance(bpolys, Feature):
await check_geom_size(bpolys.geometry)
return bpolys
elif isinstance(bpolys, (Polygon, MultiPolygon)):
await check_geom_size(bpolys)
return Feature(geometry=bpolys)
else:
raise ValueError(
"Input GeoJSON Objects have to be of type Feature, Polygon or MultiPolygon"
)

if await db_client.get_area_of_bpolys(bpolys) > GEOM_SIZE_LIMIT:
raise ValueError(
"Input GeoJSON Object is too big. "
+ "The area should be less than {0} sqkm.".format(GEOM_SIZE_LIMIT)
)
return bpolys


@app.get("/indicator/{name}")
async def get_indicator(
Expand Down Expand Up @@ -184,7 +187,11 @@ async def _fetch_report(
feature_id = int_or_str_param_type(feature_id)

report = await oqt.create_report(
name, bpolys=bpolys, dataset=dataset, feature_id=feature_id, fid_field=fid_field
name,
feature=bpolys,
dataset=dataset,
feature_id=feature_id,
fid_field=fid_field,
)

response = empty_api_response()
Expand Down
9 changes: 4 additions & 5 deletions workers/ohsome_quality_analyst/base/indicator.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from dataclasses import dataclass
from datetime import datetime
from io import StringIO
from typing import Dict, Literal, Optional, Union
from typing import Dict, Literal, Optional

import matplotlib.pyplot as plt
from dacite import from_dict
from geojson import MultiPolygon, Polygon
from geojson import Feature

from ohsome_quality_analyst.utils.definitions import (
INDICATOR_LAYER,
Expand Down Expand Up @@ -63,11 +63,10 @@ class BaseIndicator(metaclass=ABCMeta):
def __init__(
self,
layer_name: str,
bpolys: Union[Polygon, MultiPolygon],
feature: Feature,
data: Optional[dict] = None,
) -> None:

self.bpolys = bpolys
self.feature = feature
self.data = data

# setattr(object, key, value) could be used instead of relying on from_dict.
Expand Down
8 changes: 4 additions & 4 deletions workers/ohsome_quality_analyst/base/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
from abc import ABCMeta, abstractmethod
from dataclasses import dataclass
from statistics import mean
from typing import Dict, List, Literal, NamedTuple, Optional, Tuple, Union
from typing import Dict, List, Literal, NamedTuple, Optional, Tuple

from dacite import from_dict
from geojson import MultiPolygon, Polygon
from geojson import Feature

from ohsome_quality_analyst.base.indicator import BaseIndicator
from ohsome_quality_analyst.utils.definitions import get_metadata
Expand Down Expand Up @@ -39,15 +39,15 @@ class BaseReport(metaclass=ABCMeta):

def __init__(
self,
bpolys: Union[Polygon, MultiPolygon, None] = None,
feature: Feature = None,
dataset: Optional[str] = None,
feature_id: Optional[int] = None,
fid_field: Optional[str] = None,
):
self.dataset = dataset
self.feature_id = feature_id
self.fid_field = fid_field
self.bpolys = bpolys
self.feature = feature

# Definies indicator+layer combinations
self.indicator_layer: Tuple[IndicatorLayer] = []
Expand Down
26 changes: 10 additions & 16 deletions workers/ohsome_quality_analyst/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def create_indicator(
oqt.create_indicator(
indicator_name,
layer_name,
bpolys=feature.geometry,
feature=feature,
feature_id=feature_id,
dataset=dataset_name,
fid_field=fid_field,
Expand All @@ -143,7 +143,7 @@ def create_indicator(
oqt.create_indicator(
indicator_name,
layer_name,
bpolys=None,
feature=None,
feature_id=feature_id,
dataset=dataset_name,
fid_field=fid_field,
Expand All @@ -153,12 +153,9 @@ def create_indicator(
if outfile:
if fid_field is None:
fid_field = DATASETS[dataset_name]["default"]
feature_collection = asyncio.run(
db_client.get_bpolys_from_db(dataset_name, feature_id, fid_field)
)
for feature in feature_collection.features:
feature = update_features_indicator(feature, indicator)
write_geojson(outfile, feature_collection)
feature = asyncio.run(db_client.get_region_from_db(feature_id, fid_field))
feature = update_features_indicator(feature, indicator)
write_geojson(outfile, feature)
else:
click.echo(indicator.metadata)
click.echo(indicator.result)
Expand Down Expand Up @@ -190,7 +187,7 @@ def create_report(
report = asyncio.run(
oqt.create_report(
report_name,
bpolys=feature.geometry,
feature=feature,
dataset=dataset_name,
feature_id=feature_id,
fid_field=fid_field,
Expand All @@ -208,7 +205,7 @@ def create_report(
report = asyncio.run(
oqt.create_report(
report_name,
bpolys=None,
feature=None,
dataset=dataset_name,
feature_id=feature_id,
fid_field=fid_field,
Expand All @@ -218,12 +215,9 @@ def create_report(
if outfile:
if fid_field is None:
fid_field = DATASETS[dataset_name]["default"]
feature_collection = asyncio.run(
db_client.get_bpolys_from_db(dataset_name, feature_id, fid_field)
)
for feature in feature_collection.features:
feature = update_features_report(feature, report)
write_geojson(outfile, feature_collection)
feature = asyncio.run(db_client.get_region_from_db(feature_id, fid_field))
feature = update_features_report(feature, report)
write_geojson(outfile, feature)
else:
click.echo(report.metadata)
click.echo(report.result)
Expand Down
19 changes: 9 additions & 10 deletions workers/ohsome_quality_analyst/geodatabase/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

import asyncpg
import geojson
from geojson import FeatureCollection, MultiPolygon, Polygon
from geojson import Feature, FeatureCollection, MultiPolygon, Polygon

from ohsome_quality_analyst.utils.definitions import DATASETS

Expand Down Expand Up @@ -148,22 +148,21 @@ async def get_area_of_bpolys(bpolys: Union[Polygon, MultiPolygon]):
return result["area_sqkm"]


# TODO: Return GeoJSON object of type Polygon or MultiPolygon not FeatureCollection
async def get_bpolys_from_db(
dataset: str, feature_id: Union[int, str], fid_field: str
) -> Union[Polygon, MultiPolygon]:
"""Get bounding polygon from the geodatabase as a GeoJSON FeatureCollection."""
logging.info("(dataset, fid_field, id): " + str((dataset, fid_field, feature_id)))
async def get_region_from_db(feature_id: Union[int, str], fid_field: str) -> Feature:
"""Get regions from geodatabase as a GeoJSON Feature object"""
logging.info("(fid_field, id): " + str((fid_field, feature_id)))

# Safe against SQL injection because of predefined values
# (See oqt.py and definitions.py)
query = "SELECT ST_AsGeoJSON(geom) FROM {dataset} WHERE {fid_field} = $1".format(
fid_field=fid_field, dataset=dataset
query = (
"SELECT ST_AsGeoJSON(geom) "
+ "FROM regions "
+ "WHERE {0} = $1".format(fid_field)
)

async with get_connection() as conn:
result = await conn.fetchrow(query, feature_id)
return geojson.loads(result[0])
return Feature(geometry=geojson.loads(result[0]))


async def get_available_regions() -> FeatureCollection:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import logging
from io import StringIO
from string import Template
from typing import Union

import matplotlib.pyplot as plt
import numpy as np
from asyncpg import Record
from geojson import MultiPolygon, Polygon
from geojson import Feature

from ohsome_quality_analyst.base.indicator import BaseIndicator
from ohsome_quality_analyst.geodatabase import client as db_client
Expand All @@ -19,11 +18,11 @@ class GhsPopComparisonBuildings(BaseIndicator):
def __init__(
self,
layer_name: str,
bpolys: Union[Polygon, MultiPolygon],
feature: Feature,
) -> None:
super().__init__(
layer_name=layer_name,
bpolys=bpolys,
feature=feature,
)
# Those attributes will be set during lifecycle of the object.
self.pop_count = None
Expand All @@ -45,14 +44,16 @@ def yellow_threshold_function(self, pop_per_sqkm) -> float:
return 0.75 * np.sqrt(pop_per_sqkm)

async def preprocess(self) -> bool:
pop_count, area = await self.get_zonal_stats_population(bpolys=self.bpolys)
pop_count, area = await self.get_zonal_stats_population()

if pop_count is None:
pop_count = 0
self.area = area
self.pop_count = pop_count

query_results = await ohsome_client.query(layer=self.layer, bpolys=self.bpolys)
query_results = await ohsome_client.query(
layer=self.layer, bpolys=self.feature.geometry
)
if query_results is None:
return False
self.feature_count = query_results["result"][0]["value"]
Expand Down Expand Up @@ -172,7 +173,7 @@ def create_figure(self) -> bool:
plt.close("all")
return True

async def get_zonal_stats_population(self, bpolys: dict) -> Record:
async def get_zonal_stats_population(self) -> Record:
"""Derive zonal population stats for given GeoJSON geometry.
This is based on the Global Human Settlement Layer Population.
Expand All @@ -198,6 +199,6 @@ async def get_zonal_stats_population(self, bpolys: dict) -> Record:
st_setsrid(public.ST_GeomFromGeoJSON($3), 4326)
)
"""
data = tuple(map(str, [bpolys] * 3))
data = tuple(map(str, [self.feature.geometry] * 3))
async with db_client.get_connection() as conn:
return await conn.fetchrow(query, *data)
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import logging
from io import StringIO
from string import Template
from typing import Union

import matplotlib.pyplot as plt
import numpy as np
from asyncpg import Record
from geojson import MultiPolygon, Polygon
from geojson import Feature

from ohsome_quality_analyst.base.indicator import BaseIndicator
from ohsome_quality_analyst.geodatabase import client as db_client
Expand All @@ -19,12 +18,9 @@ class GhsPopComparisonRoads(BaseIndicator):
def __init__(
self,
layer_name: str,
bpolys: Union[Polygon, MultiPolygon],
feature: Feature,
) -> None:
super().__init__(
layer_name=layer_name,
bpolys=bpolys,
)
super().__init__(layer_name=layer_name, feature=feature)
# Those attributes will be set during lifecycle of the object.
self.pop_count = None
self.area = None
Expand All @@ -47,14 +43,16 @@ def yellow_threshold_function(self, pop_per_sqkm) -> float:
return 5

async def preprocess(self) -> bool:
pop_count, area = await self.get_zonal_stats_population(bpolys=self.bpolys)
pop_count, area = await self.get_zonal_stats_population()

if pop_count is None:
pop_count = 0
self.area = area
self.pop_count = pop_count

query_results = await ohsome_client.query(layer=self.layer, bpolys=self.bpolys)
query_results = await ohsome_client.query(
layer=self.layer, bpolys=self.feature.geometry
)
if query_results is None:
return False
# results in meter, we need km
Expand Down Expand Up @@ -169,7 +167,7 @@ def create_figure(self) -> bool:
plt.close("all")
return True

async def get_zonal_stats_population(self, bpolys: dict) -> Record:
async def get_zonal_stats_population(self) -> Record:
"""Derive zonal population stats for given GeoJSON geometry.
This is based on the Global Human Settlement Layer Population.
Expand All @@ -195,6 +193,6 @@ async def get_zonal_stats_population(self, bpolys: dict) -> Record:
st_setsrid(public.ST_GeomFromGeoJSON($3), 4326)
)
"""
data = tuple(map(str, [bpolys] * 3))
data = tuple(map(str, [self.feature.geometry] * 3))
async with db_client.get_connection() as conn:
return await conn.fetchrow(query, *data)
Loading

0 comments on commit 787f4c5

Please sign in to comment.