diff --git a/CHANGELOG.md b/CHANGELOG.md index ea5d0364f..343695f47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## Current Main + +### New Features + +- building-comparison: support comparison with multiple datasets ([#768]) + +[#768]: https://github.com/GIScience/ohsome-quality-api/pull/768 + ## Release 1.2.0 ### New Features diff --git a/ohsome_quality_api/definitions.py b/ohsome_quality_api/definitions.py index 58d32a2cf..59e78d528 100644 --- a/ohsome_quality_api/definitions.py +++ b/ohsome_quality_api/definitions.py @@ -22,6 +22,7 @@ "VNL": "Earth Observation Group Nighttime Light Data", "EUBUCCO": "European building stock characteristics in a common and open " + "database", + "Microsoft Buildings": "Microsoft Building Footprints (ODbL)", } ) @@ -106,6 +107,6 @@ def get_project_keys() -> Iterable[str]: def get_attribution(data_keys: list) -> str: """Return attribution text. Individual attributions are separated by semicolons.""" - assert set(data_keys) <= {"OSM", "GHSL", "VNL", "EUBUCCO"} + assert set(data_keys) <= {"OSM", "GHSL", "VNL", "EUBUCCO", "Microsoft Buildings"} filtered = dict(filter(lambda d: d[0] in data_keys, ATTRIBUTION_TEXTS.items())) return "; ".join([str(v) for v in filtered.values()]) diff --git a/ohsome_quality_api/geodatabase/client.py b/ohsome_quality_api/geodatabase/client.py index db936c1dc..ac08362a0 100644 --- a/ohsome_quality_api/geodatabase/client.py +++ b/ohsome_quality_api/geodatabase/client.py @@ -19,7 +19,7 @@ import asyncpg import geojson from asyncpg import Record -from geojson import Feature, FeatureCollection +from geojson import Feature, FeatureCollection, MultiPolygon from ohsome_quality_api.config import get_config_value @@ -69,50 +69,44 @@ async def get_shdi(bpoly: Feature | FeatureCollection) -> list[Record]: return await conn.fetch(query, geom) -async def get_building_area(bpoly: Feature) -> list[Record]: - """Get area of building footprints for a bounding polygon.""" - file_path = os.path.join(WORKING_DIR, "select_building_area.sql") +# TODO: Check calls of the function +async def get_reference_coverage(table_name: str) -> Feature: + """Get reference coverage for a bounding polygon.""" + file_path = os.path.join(WORKING_DIR, "select_coverage.sql") with open(file_path, "r") as file: query = file.read() - geom = str(bpoly.geometry) async with get_connection() as conn: - return await conn.fetch(query, geom) + result = await conn.fetch(query.format(table_name=table_name)) + return Feature(geometry=geojson.loads(result[0]["geom"])) -async def get_eubucco_coverage(inverse: bool) -> list[Record]: - file_path = os.path.join(WORKING_DIR, "select_eubucco_coverage.sql") - with open(file_path, "r") as file: - query = file.read() - if inverse: - table_name = "eubucco_v0_1_coverage_inversed" - else: - table_name = "eubucco_v0_1_coverage_simple" - query = query.format(table_name=table_name) - async with get_connection() as conn: - return await conn.fetch(query) - - -async def get_eubucco_coverage_intersection_area(bpoly: Feature) -> list[Record]: +async def get_intersection_area(bpoly: Feature, table_name: str) -> float: """Get ratio of AOI area to intersection area of AOI and coverage geometry. The result is the ratio of area within coverage (between 0-1) or an empty list if AOI lies outside of coverage geometry. """ - file_path = os.path.join(WORKING_DIR, "select_check_eubucco_coverage.sql") + file_path = os.path.join(WORKING_DIR, "select_intersection.sql") with open(file_path, "r") as file: query = file.read() geom = str(bpoly.geometry) async with get_connection() as conn: - return await conn.fetch(query, geom) + result = await conn.fetch(query.format(table_name=table_name), geom) + if result: + return result[0]["area_ratio"] + else: + return 0.0 -async def get_eubucco_coverage_intersection(bpoly: Feature) -> Feature: +async def get_intersection_geom(bpoly: Feature, table_name: str) -> Feature: """Get intersection geometry of AoI and coverage geometry.""" - file_path = os.path.join(WORKING_DIR, "get_coverage_intersection.sql") + file_path = os.path.join(WORKING_DIR, "select_intersection.sql") with open(file_path, "r") as file: query = file.read() geom = str(bpoly.geometry) async with get_connection() as conn: - result = await conn.fetch(query, geom) - bpoly["geometry"] = geojson.loads(result[0]["geom"]) - return bpoly + result = await conn.fetch(query.format(table_name=table_name), geom) + if result: + return Feature(geometry=geojson.loads(result[0]["geom"])) + else: + return Feature(geometry=MultiPolygon(coordinates=[])) diff --git a/ohsome_quality_api/geodatabase/get_coverage_intersection.sql b/ohsome_quality_api/geodatabase/get_coverage_intersection.sql deleted file mode 100644 index d2f49edac..000000000 --- a/ohsome_quality_api/geodatabase/get_coverage_intersection.sql +++ /dev/null @@ -1,11 +0,0 @@ -WITH bpoly AS ( - SELECT - ST_Setsrid (ST_GeomFromGeoJSON ($1), 4326) AS geom -) -SELECT - ST_AsGeoJSON (ST_Intersection (bpoly.geom, coverage.geom)) AS geom -FROM - bpoly, - eubucco_v0_1_coverage_simple coverage -WHERE - ST_Intersects (bpoly.geom, coverage.geom) diff --git a/ohsome_quality_api/geodatabase/select_building_area.sql b/ohsome_quality_api/geodatabase/select_building_area.sql index eb7c26d3e..a4b34bb73 100644 --- a/ohsome_quality_api/geodatabase/select_building_area.sql +++ b/ohsome_quality_api/geodatabase/select_building_area.sql @@ -3,9 +3,8 @@ WITH bpoly AS ( ST_Setsrid (ST_GeomFromGeoJSON (%s), 4326) AS geom ) SELECT - SUM(eubucco.area) as area -FROM - eubucco, + SUM({table_name}.area) as area +FROM {table_name}, bpoly WHERE - ST_Intersects (eubucco.centroid, bpoly.geom); + ST_Intersects ({table_name}.centroid, bpoly.geom); diff --git a/ohsome_quality_api/geodatabase/select_eubucco_coverage.sql b/ohsome_quality_api/geodatabase/select_coverage.sql similarity index 100% rename from ohsome_quality_api/geodatabase/select_eubucco_coverage.sql rename to ohsome_quality_api/geodatabase/select_coverage.sql diff --git a/ohsome_quality_api/geodatabase/select_check_eubucco_coverage.sql b/ohsome_quality_api/geodatabase/select_intersection.sql similarity index 70% rename from ohsome_quality_api/geodatabase/select_check_eubucco_coverage.sql rename to ohsome_quality_api/geodatabase/select_intersection.sql index 8e37f0447..cca64f558 100644 --- a/ohsome_quality_api/geodatabase/select_check_eubucco_coverage.sql +++ b/ohsome_quality_api/geodatabase/select_intersection.sql @@ -4,9 +4,10 @@ WITH bpoly AS ( ) SELECT -- ratio of area within coverage (empty if outside, between 0-1 if intersection) - ST_Area (ST_Intersection (bpoly.geom, coverage.geom)) / ST_Area (bpoly.geom) as area_ratio + ST_Area (ST_Intersection (bpoly.geom, coverage.geom)) / ST_Area (bpoly.geom) as area_ratio, + ST_AsGeoJSON (ST_Intersection (bpoly.geom, coverage.geom)) AS geom FROM bpoly, - eubucco_v0_1_coverage_simple coverage + {table_name} coverage WHERE ST_Intersects (bpoly.geom, coverage.geom) diff --git a/ohsome_quality_api/indicators/base.py b/ohsome_quality_api/indicators/base.py index 3416198d8..ee0b02f82 100644 --- a/ohsome_quality_api/indicators/base.py +++ b/ohsome_quality_api/indicators/base.py @@ -2,7 +2,7 @@ from abc import ABCMeta, abstractmethod import plotly.graph_objects as go -from geojson import Feature, MultiPolygon, Polygon +from geojson import Feature, Polygon from ohsome_quality_api.definitions import get_attribution, get_metadata from ohsome_quality_api.indicators.models import IndicatorMetadata, Result @@ -102,22 +102,26 @@ def attribution(cls) -> str: return get_attribution(["OSM"]) @classmethod - async def coverage(cls, inverse=False) -> Polygon | MultiPolygon: - """Return coverage geometry. Default is global coverage.""" + async def coverage(cls, inverse=False) -> list[Feature]: + """Return coverage geometries. Default is global coverage.""" if inverse is False: - return Polygon( - coordinates=[ - [ - (-180, 90), - (-180, -90), - (180, -90), - (180, 90), - (-180, 90), - ] - ] - ) + return [ + Feature( + geometry=Polygon( + coordinates=[ + [ + (-180, 90), + (-180, -90), + (180, -90), + (180, 90), + (-180, 90), + ] + ] + ) + ) + ] else: - return Polygon(coordinates=[]) + return [Feature(Polygon(coordinates=[]))] @abstractmethod async def preprocess(self) -> None: diff --git a/ohsome_quality_api/indicators/building_comparison/datasets.yaml b/ohsome_quality_api/indicators/building_comparison/datasets.yaml new file mode 100644 index 000000000..d39c57acd --- /dev/null +++ b/ohsome_quality_api/indicators/building_comparison/datasets.yaml @@ -0,0 +1,23 @@ +EUBUCCO: + name: EUBUCCO + link: https://docs.eubucco.com/ + date: Nov 3, 2022 + description: >- + EUBUCCO is a dataset of building footprints for Europe. + It is derived from administrative datasets. + color: PURPLE + coverage: + simple: eubucco_v0_1_coverage_simple + inversed: eubucco_v0_1_coverage_inversed + +Microsoft Buildings: + name: Microsoft Building Footprints + link: https://planetarycomputer.microsoft.com/dataset/ms-buildings + date: July 5, 2022 + description: >- + Microsoft Building Footprints is a dataset of building footprints for the world. + It is derived from satellite imagery. + color: ORANGE + coverage: + simple: microsoft_buildings_coverage_simple + inversed: microsoft_buildings_coverage_inversed diff --git a/ohsome_quality_api/indicators/building_comparison/indicator.py b/ohsome_quality_api/indicators/building_comparison/indicator.py index 30a6295fd..f08cc2e01 100644 --- a/ohsome_quality_api/indicators/building_comparison/indicator.py +++ b/ohsome_quality_api/indicators/building_comparison/indicator.py @@ -1,5 +1,6 @@ import logging import os +from functools import cache from string import Template import geojson @@ -8,7 +9,7 @@ import yaml from async_lru import alru_cache from dateutil import parser -from geojson import Feature, MultiPolygon, Polygon +from geojson import Feature from numpy import mean from ohsome_quality_api.config import get_config_value @@ -29,173 +30,261 @@ def __init__( topic=topic, feature=feature, ) - self.area_osm: float | None = None - self.area_references: dict = {} # The result is the ratio of area within coverage (between 0-1) or an empty list - self.coverage: dict = {} - + # # TODO: Evaluate thresholds self.th_high = 0.85 # Above or equal to this value label should be green self.th_low = 0.50 # Above or equal to this value label should be yellow self.above_one_th = 1.30 + self.data_ref: dict[str, dict] = {} + self.area_osm: dict[str, float | None] = {} + self.area_ref: dict[str, float | None] = {} + self.area_cov: dict[str, float | None] = {} + self.ratio: dict[str, float | None] = {} + # self.data_ref: list = load_reference_datasets() # reference datasets + for key, val in load_datasets_metadata().items(): + self.data_ref[key] = val + self.area_osm[key] = None # osm building area + self.area_ref[key] = None # reference building area [sqkm] + self.area_cov[key] = None # covered area [%] + self.ratio[key] = None + @classmethod - async def coverage(cls, inverse=False) -> Polygon | MultiPolygon: - result = await db_client.get_eubucco_coverage(inverse) - return geojson.loads(result[0]["geom"]) + async def coverage(cls, inverse=False) -> list[Feature]: + # TODO: could also return a Feature Collection + features = [] + datasets = load_datasets_metadata() + for val in datasets.values(): + if inverse: + table = val["coverage"]["inversed"] + else: + table = val["coverage"]["simple"] + feature_str = await db_client.get_reference_coverage(table) + geojson_dict = geojson.loads(feature_str) + feature = Feature(geometry=geojson_dict, properties={}) + feature.properties.update({"refernce_dataset": val["name"]}) + features.append(feature) + return features @classmethod def attribution(cls) -> str: - return get_attribution(["OSM", "EUBUCCO"]) + return get_attribution(["OSM", "EUBUCCO", "Microsoft Buildings"]) async def preprocess(self) -> None: - result = await db_client.get_eubucco_coverage_intersection_area(self.feature) - if result: - self.coverage["EUBUCCO"] = result[0]["area_ratio"] - else: - self.coverage["EUBUCCO"] = None - return + for key, val in self.data_ref.items(): + # get coverage [%] + self.area_cov[key] = await db_client.get_intersection_area( + self.feature, + val["coverage"]["simple"], + ) - edge_case = self.check_major_edge_cases() - if edge_case: - self.result.description = edge_case - return + if self.check_major_edge_cases(key): + continue - self.feature = await db_client.get_eubucco_coverage_intersection(self.feature) - db_query_result = await get_eubucco_building_area(geojson.dumps(self.feature)) - self.area_references["EUBUCCO"] = db_query_result / (1000 * 1000) - osm_query_result = await ohsome_client.query( - self.topic, - self.feature, - ) - raw = osm_query_result["result"][0]["value"] or 0 # if None - self.area_osm = raw / (1000 * 1000) - self.result.timestamp_osm = parser.isoparse( - osm_query_result["result"][0]["timestamp"] - ) + # clip input geom with coverage of reference dataset + feature = await db_client.get_intersection_geom( + self.feature, + val["coverage"]["simple"], + ) + + # get reference building area + result = await get_reference_building_area(feature, key) + self.area_ref[key] = result / (1000 * 1000) + + # get osm building area + result = await ohsome_client.query(self.topic, feature) + value = result["result"][0]["value"] or 0.0 # if None + self.area_osm[key] = value / (1000 * 1000) + timestamp = result["result"][0]["timestamp"] + self.result.timestamp_osm = parser.isoparse(timestamp) def calculate(self) -> None: # TODO: put checks into check_corner_cases. Let result be undefined. - if self.result.label == "undefined" and self.check_major_edge_cases(): + edge_cases = [self.check_major_edge_cases(k) for k in self.data_ref.keys()] + if all(edge_cases): + self.result.description += ( + " None of the reference datasets covers the area-of-interest." + ) return - if self.check_minor_edge_cases(): - self.result.description = self.check_minor_edge_cases() - else: - self.result.description = "" + self.result.description = "".join(edge_cases) - if all(v == 0 for v in self.area_references.values()): - self.result.description += "Warning: No reference data in this area. " - pass - else: - self.result.value = float( - mean( - [self.area_osm / v for v in self.area_references.values() if v != 0] - ) + for key in self.data_ref.keys(): + # if None in (self.ratio[key], self.area_cov[key], self.data_ref[key]): + if self.check_major_edge_cases(key): + continue + + self.result.description += self.check_minor_edge_cases(key) + # TODO: check for None explicitly? + # TODO: add warning for user, that no buildings are present? + try: + self.ratio[key] = self.area_osm[key] / self.area_ref[key] + except ZeroDivisionError: + self.ratio[key] = 0.0 + + template = Template(self.metadata.result_description) + self.result.description += template.substitute( + ratio=round(self.ratio[key] * 100, 2), + coverage=round(self.area_cov[key] * 100, 2), + dataset=self.data_ref[key]["name"], ) - if self.result.value is None: - return - elif self.above_one_th >= self.result.value >= self.th_high: - self.result.class_ = 5 - elif self.th_high > self.result.value >= self.th_low: - self.result.class_ = 3 - elif self.th_low > self.result.value >= 0: - self.result.class_ = 1 - elif self.result.value > self.above_one_th: - # TODO: move this to edge_case functions + ratios = [v for v in self.ratio.values() if v is not None] + ratios = [v for v in ratios if v <= self.above_one_th] + if ratios: + self.result.value = float(mean(ratios)) + else: self.result.description += ( - "Warning: Because of a big difference between OSM and the reference " - + "data no quality estimation has been made. " - + "It could be that the reference data is outdated. " + "Warning: OSM has substantivly more buildings mapped than the Reference" + + " datasets. No quality estimation has been made." ) - template = Template(self.metadata.result_description) - self.result.description += template.substitute( - ratio=round(self.result.value * 100, 2), - coverage=round(self.coverage["EUBUCCO"] * 100, 2), - ) + if self.result.value is not None: + if self.above_one_th >= self.result.value >= self.th_high: + self.result.class_ = 5 + elif self.th_high > self.result.value >= self.th_low: + self.result.class_ = 3 + elif self.th_low > self.result.value >= 0: + self.result.class_ = 1 + label_description = self.metadata.label_description[self.result.label] self.result.description += "\n" + label_description def create_figure(self) -> None: - if self.result.label == "undefined" and self.check_major_edge_cases(): + edge_cases = [self.check_major_edge_cases(k) for k in self.data_ref.keys()] + if self.result.label == "undefined" and all(edge_cases): logging.info( - "Result is undefined and major edge case is present. " - + "Skipping figure creation." + "Result is undefined and major edge case is present." + " Skipping figure creation." ) return - fig = pgo.Figure() - fig.add_trace( - pgo.Bar( - name="OSM", - x=["OSM" + f" ({self.result.timestamp_osm:%b %d, %Y})"], - y=[round(self.area_osm, 2)], - marker_color=Color.GREEN.value, - ) - ) - for name, area in self.area_references.items(): - fig.add_trace( + + ref_data = [] + ref_x = [] + ref_y = [] + osm_x = [] + osm_y = [] + ref_hover = [] + osm_hover = [] + ref_color = [] + osm_area = [] + ref_area = [] + for key, dataset in self.data_ref.items(): + if None in (self.area_ref[key], self.area_osm[key]): + continue + ref_x.append(dataset["name"]) + ref_y.append(round(self.area_ref[key], 2)) + ref_data.append(dataset) + osm_x.append(dataset["name"]) + osm_y.append(round(self.area_osm[key], 2)) + ref_hover.append(f"{dataset['name']} ({dataset['date']})") + osm_hover.append(f"OSM ({self.result.timestamp_osm:%b %d, %Y})") + ref_color.append(Color[dataset["color"]].value) + osm_area.append(round(self.area_osm[key], 2)) + ref_area.append(round(self.area_ref[key], 2)) + + fig = pgo.Figure( + data=[ pgo.Bar( - name=name, - x=[ - f"{name} ({load_source_data(name)['date']})" - if load_source_data(name)["date"] is not None - else name - ], - y=[round(area, 2)], - marker_color=Color.PURPLE.value, - ) + name="OSM building area" + + " (" + + "km², ".join(map(str, osm_area)) + + "km²)", + x=osm_x, + y=osm_y, + marker_color=Color.GREY.value, + hovertext=osm_hover, + hoverinfo="text", + ), + pgo.Bar( + name=ref_x[0] + f" ({ref_area[0]} km²)", + x=ref_x, + y=ref_y, + marker_color=ref_color, + hovertext=ref_hover, + hoverinfo="text", + legendgroup="Reference", + ), + ] + ) + + # Put every reference dataset to legend by adding transparent shapes + for i, dataset in enumerate(list(self.data_ref.values())[1:]): + fig.add_shape( + name=dataset["name"] + f" ({ref_area[i+1]} km²)", + legendgroup="Reference", + showlegend=True, + type="rect", + layer="below", + line=dict(width=0), + fillcolor=Color[dataset["color"]].value, + x0=0, + y0=0, + x1=0, + y1=0, ) - fig.update_layout(title_text=("Building Comparison"), showlegend=True) - fig.update_yaxes(title_text="Building Area [km²]") - fig.update_xaxes( - title_text="Reference Datasets (" - + get_sources(self.area_references.keys()) - + ")" - ) + layout = { + "title_text": "Building Comparison", + "showlegend": True, + "barmode": "group", + "yaxis_title": "Building Area [km²]", + "xaxis_title": f"Reference Datasets ({self.format_sources()})", + } + fig.update_layout(**layout) + raw = fig.to_dict() raw["layout"].pop("template") # remove boilerplate self.result.figure = raw - def check_major_edge_cases(self) -> str: + def check_major_edge_cases(self, dataset: str) -> str: """If edge case is present return description if not return empty string.""" - coverage = self.coverage["EUBUCCO"] + coverage = self.area_cov[dataset] if coverage is None or coverage == 0.00: - return "Reference dataset does not cover area-of-interest." + return f"Reference dataset {dataset} does not cover area-of-interest. " elif coverage < 0.10: return ( "Only {:.2f}% of the area-of-interest is covered ".format( coverage * 100 ) - + "by the reference dataset (EUBUCCO). " - + "No quality estimation is possible." + + f"by the reference dataset ({dataset}). " + + f"No quality estimation with reference {dataset} is possible." ) else: return "" - def check_minor_edge_cases(self) -> str: + def check_minor_edge_cases(self, dataset: str) -> str: """If edge case is present return description if not return empty string.""" - coverage = self.coverage["EUBUCCO"] + coverage = self.area_cov[dataset] if coverage < 0.95: return ( - "Warning: Reference data does not cover the whole input geometry. " - + "Input geometry is clipped to the coverage. Result is only calculated" + f"Warning: Reference data {dataset} does " + f"not cover the whole input geometry. " + + "Input geometry is clipped to the coverage." + " Result is only calculated" " for the intersection area. " ) else: return "" + def format_sources(self): + sources = [] + for dataset in self.data_ref.values(): + if dataset["link"] is not None: + sources.append(f"" f"{dataset['name']}") + else: + sources.append(f"{dataset}") + result = ", ".join(sources) + return result + @alru_cache -async def get_eubucco_building_area(bpoly: str) -> float: +async def get_reference_building_area(feature: Feature, table_name: str) -> float: """Get the building area for a AoI from the EUBUCCO dataset.""" # TODO: https://github.com/GIScience/ohsome-quality-api/issues/746 - bpoly = geojson.loads(bpoly) file_path = os.path.join(db_client.WORKING_DIR, "select_building_area.sql") with open(file_path, "r") as file: query = file.read() - geom = str(bpoly.geometry) dns = "postgres://{user}:{password}@{host}:{port}/{database}".format( host=get_config_value("postgres_host"), port=get_config_value("postgres_port"), @@ -203,31 +292,17 @@ async def get_eubucco_building_area(bpoly: str) -> float: user=get_config_value("postgres_user"), password=get_config_value("postgres_password"), ) + table_name = table_name.replace(" ", "_") + geom = geojson.dumps(feature.geometry) async with await psycopg.AsyncConnection.connect(dns) as con: async with con.cursor() as cur: - await cur.execute(query, (geom,)) + await cur.execute(query.format(table_name=table_name), (geom,)) res = await cur.fetchone() return res[0] or 0.0 -def get_sources(reference_datasets): - sources = "" - for dataset in reference_datasets: - source_metadata = load_source_data(dataset) - if source_metadata["link"] is not None: - sources += f"{dataset}" - else: - sources += f"{dataset}" - return sources - - -def load_source_data(reference_dataset) -> dict: - file_path = os.path.join(os.path.dirname(__file__), "sources.yaml") - +@cache +def load_datasets_metadata() -> dict: + file_path = os.path.join(os.path.dirname(__file__), "datasets.yaml") with open(file_path, "r") as f: - raw = yaml.safe_load(f) - - link = raw.get(reference_dataset, {}).get("link") - date = raw.get(reference_dataset, {}).get("date") - - return {"link": link, "date": date} + return yaml.safe_load(f) diff --git a/ohsome_quality_api/indicators/building_comparison/metadata.yaml b/ohsome_quality_api/indicators/building_comparison/metadata.yaml index 4ca475214..0b2a03de1 100644 --- a/ohsome_quality_api/indicators/building_comparison/metadata.yaml +++ b/ohsome_quality_api/indicators/building_comparison/metadata.yaml @@ -18,5 +18,5 @@ building-comparison: undefined: >- Comparison could not be made. result_description: >- - $coverage% of the area-of-interest is covered by the reference dataset. - The building area of OSM is $ratio% of that of the reference data. + $coverage% of the area-of-interest is covered by the reference dataset $dataset. + The building area of OSM is $ratio% of that reference data. diff --git a/ohsome_quality_api/indicators/building_comparison/sources.yaml b/ohsome_quality_api/indicators/building_comparison/sources.yaml deleted file mode 100644 index 7d7c873b4..000000000 --- a/ohsome_quality_api/indicators/building_comparison/sources.yaml +++ /dev/null @@ -1,5 +0,0 @@ ---- -# set source link and publishing date of compare datasets -EUBUCCO: - link: https://docs.eubucco.com - date: Nov 3, 2022 \ No newline at end of file diff --git a/ohsome_quality_api/indicators/definitions.py b/ohsome_quality_api/indicators/definitions.py index 7b5679805..3856f81a7 100644 --- a/ohsome_quality_api/indicators/definitions.py +++ b/ohsome_quality_api/indicators/definitions.py @@ -1,6 +1,6 @@ from enum import Enum -from geojson import Feature, FeatureCollection +from geojson import FeatureCollection from ohsome_quality_api.definitions import load_metadata from ohsome_quality_api.indicators.models import IndicatorMetadata @@ -29,9 +29,8 @@ def get_valid_indicators(topic_key: str) -> tuple: async def get_coverage(indicator_key: str, inverse: bool = False) -> FeatureCollection: indicator_class = get_class_from_key(class_type="indicator", key=indicator_key) - geometry = await indicator_class.coverage(inverse) - feature = Feature(geometry=geometry, properties={}) - return FeatureCollection(features=[feature]) + features = await indicator_class.coverage(inverse) + return FeatureCollection(features=features) IndicatorEnum = Enum("IndicatorEnum", {name: name for name in get_indicator_keys()}) diff --git a/pyproject.toml b/pyproject.toml index 849e7d8ac..63835bc4c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,3 +81,4 @@ select = [ [tool.pytest.ini_options] testpaths = ["tests"] +filterwarnings = ["ignore::DeprecationWarning"] diff --git a/tests/integrationtests/fixtures/vcr_cassettes/indicators/test_building_comparison.yaml b/tests/integrationtests/fixtures/vcr_cassettes/indicators/test_building_comparison.yaml index a7a18e284..57d264a6e 100644 --- a/tests/integrationtests/fixtures/vcr_cassettes/indicators/test_building_comparison.yaml +++ b/tests/integrationtests/fixtures/vcr_cassettes/indicators/test_building_comparison.yaml @@ -1,61 +1,4 @@ interactions: -- request: - body: filter=building%3D%2A+and+building%21%3Dno+and+geometry%3Apolygon&bpolys=%7B%22type%22%3A+%22FeatureCollection%22%2C+%22features%22%3A+%5B%7B%22type%22%3A+%22Feature%22%2C+%22geometry%22%3A+%7B%22type%22%3A+%22Polygon%22%2C+%22coordinates%22%3A+%5B%5B%5B8.573179%2C+49.4236%5D%2C+%5B8.573244%2C+49.423266%5D%2C+%5B8.573517%2C+49.421746%5D%2C+%5B8.573602%2C+49.421029%5D%2C+%5B8.57363%2C+49.420766%5D%2C+%5B8.573618%2C+49.420478%5D%2C+%5B8.573575%2C+49.420208%5D%2C+%5B8.573487%2C+49.419852%5D%2C+%5B8.573253%2C+49.419103%5D%2C+%5B8.57328%2C+49.418634%5D%2C+%5B8.573262%2C+49.41846%5D%2C+%5B8.573244%2C+49.418239%5D%2C+%5B8.573233%2C+49.418026%5D%2C+%5B8.573211%2C+49.417562%5D%2C+%5B8.573206%2C+49.417217%5D%2C+%5B8.573223%2C+49.416902%5D%2C+%5B8.573288%2C+49.416518%5D%2C+%5B8.57343%2C+49.415763%5D%2C+%5B8.573622%2C+49.415734%5D%2C+%5B8.57398%2C+49.414723%5D%2C+%5B8.574098%2C+49.414422%5D%2C+%5B8.574905%2C+49.412659%5D%2C+%5B8.575126%2C+49.41262%5D%2C+%5B8.575952%2C+49.412479%5D%2C+%5B8.576355%2C+49.412403%5D%2C+%5B8.576806%2C+49.412309%5D%2C+%5B8.577418%2C+49.412162%5D%2C+%5B8.578871%2C+49.411816%5D%2C+%5B8.580327%2C+49.411477%5D%2C+%5B8.580548%2C+49.411425%5D%2C+%5B8.581021%2C+49.411314%5D%2C+%5B8.581467%2C+49.411198%5D%2C+%5B8.582126%2C+49.411021%5D%2C+%5B8.58281%2C+49.410831%5D%2C+%5B8.583621%2C+49.410604%5D%2C+%5B8.584256%2C+49.41041%5D%2C+%5B8.584461%2C+49.410598%5D%2C+%5B8.584601%2C+49.41074%5D%2C+%5B8.58466%2C+49.410809%5D%2C+%5B8.584729%2C+49.410896%5D%2C+%5B8.584841%2C+49.411043%5D%2C+%5B8.584893%2C+49.411041%5D%2C+%5B8.590664%2C+49.410551%5D%2C+%5B8.592504%2C+49.410392%5D%2C+%5B8.59249%2C+49.410325%5D%2C+%5B8.592557%2C+49.410316%5D%2C+%5B8.592992%2C+49.410285%5D%2C+%5B8.594295%2C+49.410194%5D%2C+%5B8.595027%2C+49.410131%5D%2C+%5B8.595718%2C+49.41008%5D%2C+%5B8.596457%2C+49.410013%5D%2C+%5B8.597109%2C+49.409948%5D%2C+%5B8.597817%2C+49.409869%5D%2C+%5B8.598479%2C+49.409799%5D%2C+%5B8.59864%2C+49.409784%5D%2C+%5B8.600522%2C+49.409526%5D%2C+%5B8.603393%2C+49.409127%5D%2C+%5B8.60354%2C+49.409104%5D%2C+%5B8.603638%2C+49.409079%5D%2C+%5B8.6052%2C+49.408456%5D%2C+%5B8.608794%2C+49.407045%5D%2C+%5B8.610673%2C+49.405949%5D%2C+%5B8.611461%2C+49.405467%5D%2C+%5B8.612437%2C+49.406203%5D%2C+%5B8.616606%2C+49.407383%5D%2C+%5B8.622553%2C+49.409046%5D%2C+%5B8.622627%2C+49.40901%5D%2C+%5B8.622648%2C+49.408953%5D%2C+%5B8.62272%2C+49.408975%5D%2C+%5B8.622722%2C+49.408976%5D%2C+%5B8.622791%2C+49.409015%5D%2C+%5B8.622851%2C+49.409049%5D%2C+%5B8.623189%2C+49.409236%5D%2C+%5B8.624498%2C+49.409957%5D%2C+%5B8.62459%2C+49.410009%5D%2C+%5B8.625101%2C+49.410294%5D%2C+%5B8.625378%2C+49.410448%5D%2C+%5B8.625911%2C+49.410745%5D%2C+%5B8.626324%2C+49.410975%5D%2C+%5B8.626979%2C+49.411341%5D%2C+%5B8.628263%2C+49.412032%5D%2C+%5B8.62859%2C+49.412213%5D%2C+%5B8.629072%2C+49.412486%5D%2C+%5B8.630034%2C+49.413176%5D%2C+%5B8.630959%2C+49.413833%5D%2C+%5B8.632078%2C+49.414583%5D%2C+%5B8.632707%2C+49.415007%5D%2C+%5B8.632748%2C+49.415035%5D%2C+%5B8.632938%2C+49.414961%5D%2C+%5B8.633025%2C+49.414928%5D%2C+%5B8.633075%2C+49.414807%5D%2C+%5B8.63406%2C+49.412449%5D%2C+%5B8.634446%2C+49.411487%5D%2C+%5B8.634737%2C+49.410763%5D%2C+%5B8.635407%2C+49.409092%5D%2C+%5B8.635873%2C+49.407932%5D%2C+%5B8.636033%2C+49.40753%5D%2C+%5B8.636136%2C+49.407242%5D%2C+%5B8.636215%2C+49.406957%5D%2C+%5B8.636379%2C+49.406391%5D%2C+%5B8.636465%2C+49.406041%5D%2C+%5B8.636591%2C+49.405566%5D%2C+%5B8.636667%2C+49.405073%5D%2C+%5B8.636706%2C+49.404806%5D%2C+%5B8.636743%2C+49.404552%5D%2C+%5B8.636827%2C+49.403969%5D%2C+%5B8.6369%2C+49.403462%5D%2C+%5B8.636926%2C+49.403268%5D%2C+%5B8.637017%2C+49.402596%5D%2C+%5B8.637222%2C+49.401141%5D%2C+%5B8.63755%2C+49.3988%5D%2C+%5B8.638008%2C+49.395648%5D%2C+%5B8.638186%2C+49.393813%5D%2C+%5B8.638295%2C+49.39272%5D%2C+%5B8.638521%2C+49.392746%5D%2C+%5B8.640722%2C+49.393021%5D%2C+%5B8.64343%2C+49.393381%5D%2C+%5B8.643512%2C+49.393194%5D%2C+%5B8.643527%2C+49.39316%5D%2C+%5B8.645535%2C+49.390884%5D%2C+%5B8.64636%2C+49.389866%5D%2C+%5B8.6475%2C+49.388537%5D%2C+%5B8.645357%2C+49.388044%5D%2C+%5B8.643807%2C+49.387609%5D%2C+%5B8.642091%2C+49.387149%5D%2C+%5B8.641038%2C+49.386841%5D%2C+%5B8.639916%2C+49.386498%5D%2C+%5B8.639667%2C+49.386434%5D%2C+%5B8.639387%2C+49.386413%5D%2C+%5B8.638662%2C+49.386411%5D%2C+%5B8.638086%2C+49.386573%5D%2C+%5B8.636604%2C+49.38704%5D%2C+%5B8.634926%2C+49.387313%5D%2C+%5B8.633683%2C+49.387185%5D%2C+%5B8.633598%2C+49.387174%5D%2C+%5B8.633542%2C+49.386897%5D%2C+%5B8.633434%2C+49.386227%5D%2C+%5B8.633401%2C+49.385899%5D%2C+%5B8.633366%2C+49.385891%5D%2C+%5B8.630199%2C+49.385201%5D%2C+%5B8.629858%2C+49.385107%5D%2C+%5B8.629436%2C+49.384945%5D%2C+%5B8.628851%2C+49.384706%5D%2C+%5B8.628366%2C+49.384445%5D%2C+%5B8.627801%2C+49.38413%5D%2C+%5B8.627122%2C+49.383729%5D%2C+%5B8.62646%2C+49.38335%5D%2C+%5B8.625986%2C+49.383068%5D%2C+%5B8.625383%2C+49.382699%5D%2C+%5B8.624878%2C+49.382387%5D%2C+%5B8.624241%2C+49.38199%5D%2C+%5B8.623785%2C+49.381668%5D%2C+%5B8.624382%2C+49.38067%5D%2C+%5B8.621112%2C+49.380011%5D%2C+%5B8.620378%2C+49.379843%5D%2C+%5B8.617932%2C+49.379283%5D%2C+%5B8.616155%2C+49.379158%5D%2C+%5B8.615294%2C+49.378918%5D%2C+%5B8.614545%2C+49.378758%5D%2C+%5B8.613751%2C+49.378528%5D%2C+%5B8.612581%2C+49.378202%5D%2C+%5B8.610866%2C+49.378017%5D%2C+%5B8.610186%2C+49.377919%5D%2C+%5B8.609757%2C+49.37785%5D%2C+%5B8.609403%2C+49.377756%5D%2C+%5B8.609066%2C+49.377649%5D%2C+%5B8.608678%2C+49.377505%5D%2C+%5B8.607916%2C+49.37721%5D%2C+%5B8.607342%2C+49.376972%5D%2C+%5B8.606882%2C+49.376751%5D%2C+%5B8.60681%2C+49.376163%5D%2C+%5B8.607222%2C+49.374986%5D%2C+%5B8.607484%2C+49.37429%5D%2C+%5B8.607333%2C+49.373851%5D%2C+%5B8.606536%2C+49.371886%5D%2C+%5B8.605798%2C+49.370097%5D%2C+%5B8.605168%2C+49.370272%5D%2C+%5B8.60321%2C+49.368377%5D%2C+%5B8.601004%2C+49.366203%5D%2C+%5B8.604393%2C+49.365577%5D%2C+%5B8.604307%2C+49.365419%5D%2C+%5B8.605479%2C+49.365214%5D%2C+%5B8.605885%2C+49.365159%5D%2C+%5B8.607155%2C+49.365004%5D%2C+%5B8.607562%2C+49.364962%5D%2C+%5B8.607787%2C+49.364932%5D%2C+%5B8.608004%2C+49.364857%5D%2C+%5B8.60823%2C+49.364779%5D%2C+%5B8.608521%2C+49.364676%5D%2C+%5B8.608763%2C+49.364589%5D%2C+%5B8.608975%2C+49.364526%5D%2C+%5B8.60924%2C+49.364461%5D%2C+%5B8.609518%2C+49.364418%5D%2C+%5B8.609797%2C+49.364361%5D%2C+%5B8.610128%2C+49.3643%5D%2C+%5B8.610354%2C+49.364251%5D%2C+%5B8.610605%2C+49.364179%5D%2C+%5B8.610995%2C+49.364053%5D%2C+%5B8.611555%2C+49.363849%5D%2C+%5B8.612129%2C+49.363662%5D%2C+%5B8.613637%2C+49.363103%5D%2C+%5B8.615444%2C+49.362414%5D%2C+%5B8.61635%2C+49.362058%5D%2C+%5B8.617043%2C+49.361767%5D%2C+%5B8.617591%2C+49.361529%5D%2C+%5B8.618237%2C+49.361201%5D%2C+%5B8.618957%2C+49.360849%5D%2C+%5B8.619443%2C+49.360653%5D%2C+%5B8.619975%2C+49.360461%5D%2C+%5B8.620625%2C+49.360206%5D%2C+%5B8.621086%2C+49.360044%5D%2C+%5B8.621364%2C+49.359916%5D%2C+%5B8.621823%2C+49.359713%5D%2C+%5B8.622089%2C+49.35959%5D%2C+%5B8.622436%2C+49.35947%5D%2C+%5B8.623627%2C+49.359191%5D%2C+%5B8.624466%2C+49.359063%5D%2C+%5B8.623282%2C+49.35746%5D%2C+%5B8.623019%2C+49.357581%5D%2C+%5B8.621335%2C+49.355497%5D%2C+%5B8.620215%2C+49.354073%5D%2C+%5B8.620484%2C+49.354038%5D%2C+%5B8.620686%2C+49.354007%5D%2C+%5B8.621085%2C+49.353877%5D%2C+%5B8.623323%2C+49.35288%5D%2C+%5B8.625019%2C+49.352119%5D%2C+%5B8.62524%2C+49.35203%5D%2C+%5B8.625625%2C+49.352003%5D%2C+%5B8.627988%2C+49.352082%5D%2C+%5B8.628566%2C+49.352102%5D%2C+%5B8.629102%2C+49.352103%5D%2C+%5B8.629641%2C+49.352094%5D%2C+%5B8.630762%2C+49.352065%5D%2C+%5B8.630787%2C+49.352734%5D%2C+%5B8.631513%2C+49.352774%5D%2C+%5B8.631909%2C+49.352796%5D%2C+%5B8.632002%2C+49.352809%5D%2C+%5B8.632227%2C+49.352816%5D%2C+%5B8.632605%2C+49.35284%5D%2C+%5B8.632903%2C+49.352846%5D%2C+%5B8.633112%2C+49.352836%5D%2C+%5B8.633234%2C+49.352838%5D%2C+%5B8.633289%2C+49.353939%5D%2C+%5B8.633355%2C+49.355012%5D%2C+%5B8.633367%2C+49.355489%5D%2C+%5B8.63355%2C+49.355781%5D%2C+%5B8.633757%2C+49.3561%5D%2C+%5B8.633974%2C+49.356482%5D%2C+%5B8.634167%2C+49.356808%5D%2C+%5B8.634287%2C+49.357126%5D%2C+%5B8.634421%2C+49.357445%5D%2C+%5B8.634503%2C+49.357686%5D%2C+%5B8.634859%2C+49.358095%5D%2C+%5B8.635396%2C+49.358765%5D%2C+%5B8.636103%2C+49.359671%5D%2C+%5B8.636657%2C+49.360357%5D%2C+%5B8.636737%2C+49.360538%5D%2C+%5B8.636888%2C+49.361037%5D%2C+%5B8.637136%2C+49.36199%5D%2C+%5B8.637264%2C+49.362635%5D%2C+%5B8.637304%2C+49.362804%5D%2C+%5B8.637443%2C+49.363191%5D%2C+%5B8.637664%2C+49.36361%5D%2C+%5B8.637986%2C+49.364357%5D%2C+%5B8.638362%2C+49.365323%5D%2C+%5B8.638797%2C+49.36638%5D%2C+%5B8.639105%2C+49.3671%5D%2C+%5B8.639672%2C+49.368535%5D%2C+%5B8.640274%2C+49.369977%5D%2C+%5B8.64095%2C+49.36962%5D%2C+%5B8.642835%2C+49.368655%5D%2C+%5B8.644035%2C+49.369071%5D%2C+%5B8.644716%2C+49.369324%5D%2C+%5B8.644987%2C+49.369459%5D%2C+%5B8.645246%2C+49.369621%5D%2C+%5B8.645609%2C+49.369857%5D%2C+%5B8.645945%2C+49.370086%5D%2C+%5B8.646206%2C+49.370303%5D%2C+%5B8.646365%2C+49.370464%5D%2C+%5B8.647363%2C+49.369711%5D%2C+%5B8.647775%2C+49.369395%5D%2C+%5B8.64813%2C+49.369156%5D%2C+%5B8.648616%2C+49.368856%5D%2C+%5B8.649147%2C+49.368515%5D%2C+%5B8.649775%2C+49.368118%5D%2C+%5B8.650491%2C+49.367685%5D%2C+%5B8.650841%2C+49.367478%5D%2C+%5B8.650529%2C+49.366984%5D%2C+%5B8.650281%2C+49.366573%5D%2C+%5B8.650115%2C+49.36627%5D%2C+%5B8.649942%2C+49.365978%5D%2C+%5B8.651691%2C+49.364976%5D%2C+%5B8.651847%2C+49.365094%5D%2C+%5B8.652197%2C+49.364902%5D%2C+%5B8.652915%2C+49.364549%5D%2C+%5B8.653519%2C+49.364273%5D%2C+%5B8.654112%2C+49.36402%5D%2C+%5B8.654768%2C+49.363737%5D%2C+%5B8.655407%2C+49.363473%5D%2C+%5B8.656377%2C+49.363115%5D%2C+%5B8.657142%2C+49.362813%5D%2C+%5B8.657764%2C+49.363277%5D%2C+%5B8.658185%2C+49.363054%5D%2C+%5B8.658651%2C+49.362837%5D%2C+%5B8.659164%2C+49.362571%5D%2C+%5B8.658051%2C+49.361776%5D%2C+%5B8.657009%2C+49.360179%5D%2C+%5B8.656454%2C+49.35929%5D%2C+%5B8.655814%2C+49.358138%5D%2C+%5B8.655354%2C+49.357298%5D%2C+%5B8.654879%2C+49.356558%5D%2C+%5B8.654787%2C+49.35643%5D%2C+%5B8.654561%2C+49.356112%5D%2C+%5B8.653362%2C+49.356263%5D%2C+%5B8.652987%2C+49.35577%5D%2C+%5B8.652544%2C+49.355287%5D%2C+%5B8.652104%2C+49.35458%5D%2C+%5B8.651277%2C+49.353254%5D%2C+%5B8.651454%2C+49.353204%5D%2C+%5B8.651636%2C+49.353188%5D%2C+%5B8.65183%2C+49.353191%5D%2C+%5B8.652046%2C+49.353203%5D%2C+%5B8.652348%2C+49.353246%5D%2C+%5B8.652615%2C+49.353273%5D%2C+%5B8.652844%2C+49.353279%5D%2C+%5B8.653124%2C+49.353277%5D%2C+%5B8.653474%2C+49.353258%5D%2C+%5B8.653742%2C+49.353245%5D%2C+%5B8.654053%2C+49.353215%5D%2C+%5B8.654541%2C+49.353146%5D%2C+%5B8.655319%2C+49.353021%5D%2C+%5B8.656788%2C+49.352777%5D%2C+%5B8.657594%2C+49.352635%5D%2C+%5B8.657702%2C+49.352622%5D%2C+%5B8.657803%2C+49.352609%5D%2C+%5B8.658004%2C+49.352578%5D%2C+%5B8.658147%2C+49.352565%5D%2C+%5B8.657938%2C+49.35276%5D%2C+%5B8.657306%2C+49.353303%5D%2C+%5B8.657404%2C+49.353355%5D%2C+%5B8.656818%2C+49.353732%5D%2C+%5B8.656362%2C+49.354017%5D%2C+%5B8.656689%2C+49.35436%5D%2C+%5B8.657243%2C+49.355195%5D%2C+%5B8.658874%2C+49.354938%5D%2C+%5B8.660099%2C+49.354864%5D%2C+%5B8.660662%2C+49.354831%5D%2C+%5B8.660554%2C+49.35385%5D%2C+%5B8.661186%2C+49.353988%5D%2C+%5B8.661529%2C+49.354066%5D%2C+%5B8.662499%2C+49.354314%5D%2C+%5B8.663203%2C+49.354396%5D%2C+%5B8.663578%2C+49.353585%5D%2C+%5B8.664184%2C+49.353663%5D%2C+%5B8.665018%2C+49.353769%5D%2C+%5B8.664862%2C+49.354592%5D%2C+%5B8.664516%2C+49.356424%5D%2C+%5B8.664343%2C+49.35714%5D%2C+%5B8.664257%2C+49.357496%5D%2C+%5B8.664523%2C+49.357548%5D%2C+%5B8.664876%2C+49.357593%5D%2C+%5B8.667029%2C+49.35798%5D%2C+%5B8.66801%2C+49.358065%5D%2C+%5B8.668543%2C+49.358111%5D%2C+%5B8.668764%2C+49.358127%5D%2C+%5B8.669807%2C+49.358003%5D%2C+%5B8.671465%2C+49.357805%5D%2C+%5B8.67168%2C+49.357818%5D%2C+%5B8.671905%2C+49.359187%5D%2C+%5B8.672189%2C+49.360595%5D%2C+%5B8.672443%2C+49.361521%5D%2C+%5B8.6742%2C+49.361218%5D%2C+%5B8.675834%2C+49.360668%5D%2C+%5B8.675955%2C+49.360221%5D%2C+%5B8.677248%2C+49.360356%5D%2C+%5B8.678227%2C+49.360428%5D%2C+%5B8.67875%2C+49.360442%5D%2C+%5B8.679817%2C+49.360427%5D%2C+%5B8.679921%2C+49.360425%5D%2C+%5B8.682084%2C+49.360389%5D%2C+%5B8.682014%2C+49.359907%5D%2C+%5B8.684247%2C+49.359817%5D%2C+%5B8.684378%2C+49.359781%5D%2C+%5B8.68706%2C+49.359824%5D%2C+%5B8.687067%2C+49.35976%5D%2C+%5B8.687186%2C+49.359138%5D%2C+%5B8.687237%2C+49.35886%5D%2C+%5B8.689029%2C+49.3589%5D%2C+%5B8.689213%2C+49.358942%5D%2C+%5B8.690288%2C+49.358929%5D%2C+%5B8.690684%2C+49.358899%5D%2C+%5B8.691469%2C+49.358787%5D%2C+%5B8.691399%2C+49.358492%5D%2C+%5B8.692384%2C+49.358415%5D%2C+%5B8.692438%2C+49.358513%5D%2C+%5B8.692875%2C+49.358386%5D%2C+%5B8.692904%2C+49.358507%5D%2C+%5B8.693627%2C+49.358371%5D%2C+%5B8.693654%2C+49.35848%5D%2C+%5B8.694136%2C+49.358357%5D%2C+%5B8.694369%2C+49.358679%5D%2C+%5B8.69466%2C+49.358603%5D%2C+%5B8.694621%2C+49.358537%5D%2C+%5B8.694444%2C+49.358239%5D%2C+%5B8.6964%2C+49.357749%5D%2C+%5B8.696686%2C+49.357789%5D%2C+%5B8.696953%2C+49.358039%5D%2C+%5B8.698234%2C+49.357421%5D%2C+%5B8.699042%2C+49.357005%5D%2C+%5B8.699133%2C+49.357005%5D%2C+%5B8.699224%2C+49.357003%5D%2C+%5B8.699468%2C+49.356867%5D%2C+%5B8.70005%2C+49.356571%5D%2C+%5B8.700092%2C+49.35655%5D%2C+%5B8.700541%2C+49.356327%5D%2C+%5B8.701206%2C+49.356063%5D%2C+%5B8.701769%2C+49.355846%5D%2C+%5B8.701847%2C+49.355815%5D%2C+%5B8.70249%2C+49.355716%5D%2C+%5B8.703321%2C+49.355722%5D%2C+%5B8.703979%2C+49.355745%5D%2C+%5B8.704667%2C+49.355786%5D%2C+%5B8.705234%2C+49.355848%5D%2C+%5B8.705404%2C+49.355871%5D%2C+%5B8.705776%2C+49.35592%5D%2C+%5B8.706353%2C+49.356042%5D%2C+%5B8.706882%2C+49.356123%5D%2C+%5B8.708303%2C+49.356341%5D%2C+%5B8.70964%2C+49.35658%5D%2C+%5B8.71027%2C+49.355542%5D%2C+%5B8.710889%2C+49.35511%5D%2C+%5B8.711249%2C+49.35521%5D%2C+%5B8.7114%2C+49.35534%5D%2C+%5B8.711683%2C+49.355525%5D%2C+%5B8.711946%2C+49.355618%5D%2C+%5B8.712513%2C+49.355659%5D%2C+%5B8.71339%2C+49.355695%5D%2C+%5B8.71423%2C+49.355746%5D%2C+%5B8.715084%2C+49.355951%5D%2C+%5B8.715987%2C+49.35624%5D%2C+%5B8.716396%2C+49.356342%5D%2C+%5B8.718652%2C+49.356379%5D%2C+%5B8.71896%2C+49.356351%5D%2C+%5B8.719342%2C+49.356762%5D%2C+%5B8.719472%2C+49.357065%5D%2C+%5B8.71971%2C+49.357302%5D%2C+%5B8.720122%2C+49.357284%5D%2C+%5B8.721267%2C+49.357192%5D%2C+%5B8.721461%2C+49.357173%5D%2C+%5B8.722389%2C+49.357253%5D%2C+%5B8.723668%2C+49.357439%5D%2C+%5B8.724196%2C+49.357602%5D%2C+%5B8.724937%2C+49.357921%5D%2C+%5B8.72524%2C+49.358308%5D%2C+%5B8.725498%2C+49.358663%5D%2C+%5B8.725599%2C+49.358803%5D%2C+%5B8.726016%2C+49.359476%5D%2C+%5B8.726055%2C+49.360045%5D%2C+%5B8.726094%2C+49.360374%5D%2C+%5B8.727113%2C+49.361989%5D%2C+%5B8.727202%2C+49.362889%5D%2C+%5B8.727423%2C+49.364132%5D%2C+%5B8.727717%2C+49.365509%5D%2C+%5B8.728446%2C+49.366441%5D%2C+%5B8.728671%2C+49.366705%5D%2C+%5B8.728942%2C+49.366897%5D%2C+%5B8.730404%2C+49.367933%5D%2C+%5B8.729929%2C+49.368208%5D%2C+%5B8.728541%2C+49.368817%5D%2C+%5B8.72836%2C+49.368682%5D%2C+%5B8.726259%2C+49.369455%5D%2C+%5B8.725357%2C+49.370066%5D%2C+%5B8.725576%2C+49.37022%5D%2C+%5B8.725009%2C+49.371211%5D%2C+%5B8.724729%2C+49.37225%5D%2C+%5B8.724401%2C+49.373496%5D%2C+%5B8.724479%2C+49.373747%5D%2C+%5B8.724841%2C+49.373956%5D%2C+%5B8.727404%2C+49.375554%5D%2C+%5B8.72822%2C+49.374821%5D%2C+%5B8.729138%2C+49.374009%5D%2C+%5B8.730155%2C+49.373179%5D%2C+%5B8.730385%2C+49.373252%5D%2C+%5B8.730507%2C+49.373311%5D%2C+%5B8.731437%2C+49.372716%5D%2C+%5B8.732328%2C+49.372224%5D%2C+%5B8.733929%2C+49.372675%5D%2C+%5B8.735724%2C+49.372956%5D%2C+%5B8.736319%2C+49.373324%5D%2C+%5B8.737564%2C+49.374161%5D%2C+%5B8.738416%2C+49.374491%5D%2C+%5B8.740049%2C+49.374553%5D%2C+%5B8.740729%2C+49.374442%5D%2C+%5B8.741468%2C+49.373924%5D%2C+%5B8.742963%2C+49.373108%5D%2C+%5B8.744141%2C+49.372795%5D%2C+%5B8.745571%2C+49.37295%5D%2C+%5B8.746882%2C+49.373148%5D%2C+%5B8.747531%2C+49.373299%5D%2C+%5B8.747803%2C+49.373403%5D%2C+%5B8.74801%2C+49.373481%5D%2C+%5B8.749031%2C+49.373788%5D%2C+%5B8.749349%2C+49.373809%5D%2C+%5B8.749506%2C+49.373874%5D%2C+%5B8.749682%2C+49.374157%5D%2C+%5B8.749784%2C+49.374269%5D%2C+%5B8.749979%2C+49.37486%5D%2C+%5B8.750254%2C+49.375234%5D%2C+%5B8.750486%2C+49.375439%5D%2C+%5B8.751287%2C+49.375853%5D%2C+%5B8.751986%2C+49.376167%5D%2C+%5B8.752481%2C+49.376371%5D%2C+%5B8.75318%2C+49.376893%5D%2C+%5B8.753542%2C+49.377091%5D%2C+%5B8.754226%2C+49.377357%5D%2C+%5B8.754767%2C+49.377572%5D%2C+%5B8.754718%2C+49.377723%5D%2C+%5B8.754876%2C+49.377766%5D%2C+%5B8.75543%2C+49.377841%5D%2C+%5B8.756198%2C+49.37791%5D%2C+%5B8.756906%2C+49.377922%5D%2C+%5B8.75795%2C+49.377906%5D%2C+%5B8.758486%2C+49.377899%5D%2C+%5B8.758903%2C+49.377943%5D%2C+%5B8.759138%2C+49.377807%5D%2C+%5B8.759187%2C+49.377808%5D%2C+%5B8.759786%2C+49.377905%5D%2C+%5B8.760544%2C+49.378024%5D%2C+%5B8.762615%2C+49.378444%5D%2C+%5B8.763084%2C+49.378532%5D%2C+%5B8.763661%2C+49.378542%5D%2C+%5B8.76537%2C+49.378518%5D%2C+%5B8.766125%2C+49.378539%5D%2C+%5B8.766754%2C+49.378608%5D%2C+%5B8.76849%2C+49.379027%5D%2C+%5B8.76883%2C+49.379193%5D%2C+%5B8.768381%2C+49.379612%5D%2C+%5B8.767516%2C+49.380685%5D%2C+%5B8.766845%2C+49.381341%5D%2C+%5B8.766279%2C+49.382016%5D%2C+%5B8.765944%2C+49.3827%5D%2C+%5B8.76528%2C+49.383389%5D%2C+%5B8.764256%2C+49.384184%5D%2C+%5B8.763735%2C+49.384783%5D%2C+%5B8.76338%2C+49.385218%5D%2C+%5B8.763158%2C+49.385451%5D%2C+%5B8.762978%2C+49.385762%5D%2C+%5B8.762909%2C+49.385993%5D%2C+%5B8.762887%2C+49.386438%5D%2C+%5B8.762991%2C+49.386902%5D%2C+%5B8.762978%2C+49.387249%5D%2C+%5B8.762899%2C+49.387516%5D%2C+%5B8.763192%2C+49.387952%5D%2C+%5B8.764231%2C+49.387498%5D%2C+%5B8.766654%2C+49.387178%5D%2C+%5B8.767235%2C+49.387062%5D%2C+%5B8.767759%2C+49.386865%5D%2C+%5B8.768178%2C+49.386789%5D%2C+%5B8.768518%2C+49.386845%5D%2C+%5B8.768945%2C+49.386974%5D%2C+%5B8.76931%2C+49.387141%5D%2C+%5B8.769787%2C+49.387254%5D%2C+%5B8.770146%2C+49.387651%5D%2C+%5B8.771331%2C+49.388121%5D%2C+%5B8.771843%2C+49.388408%5D%2C+%5B8.772677%2C+49.388667%5D%2C+%5B8.772941%2C+49.388877%5D%2C+%5B8.773016%2C+49.389213%5D%2C+%5B8.773661%2C+49.389392%5D%2C+%5B8.773463%2C+49.389673%5D%2C+%5B8.773409%2C+49.390055%5D%2C+%5B8.77366%2C+49.390523%5D%2C+%5B8.773878%2C+49.390825%5D%2C+%5B8.774229%2C+49.390979%5D%2C+%5B8.774594%2C+49.391209%5D%2C+%5B8.775114%2C+49.391654%5D%2C+%5B8.775375%2C+49.392035%5D%2C+%5B8.775841%2C+49.392457%5D%2C+%5B8.775179%2C+49.393053%5D%2C+%5B8.776011%2C+49.393547%5D%2C+%5B8.776472%2C+49.394054%5D%2C+%5B8.776103%2C+49.394241%5D%2C+%5B8.775839%2C+49.394462%5D%2C+%5B8.775613%2C+49.394778%5D%2C+%5B8.775432%2C+49.39513%5D%2C+%5B8.775357%2C+49.395498%5D%2C+%5B8.775351%2C+49.39589%5D%2C+%5B8.775405%2C+49.396353%5D%2C+%5B8.77561%2C+49.396942%5D%2C+%5B8.775853%2C+49.397499%5D%2C+%5B8.77627%2C+49.398226%5D%2C+%5B8.776723%2C+49.39895%5D%2C+%5B8.777297%2C+49.399896%5D%2C+%5B8.777925%2C+49.401157%5D%2C+%5B8.778345%2C+49.40224%5D%2C+%5B8.779285%2C+49.402018%5D%2C+%5B8.78048%2C+49.4018%5D%2C+%5B8.781842%2C+49.401604%5D%2C+%5B8.783078%2C+49.401452%5D%2C+%5B8.783982%2C+49.401369%5D%2C+%5B8.784682%2C+49.401329%5D%2C+%5B8.785426%2C+49.40138%5D%2C+%5B8.78645%2C+49.401532%5D%2C+%5B8.787479%2C+49.40178%5D%2C+%5B8.789244%2C+49.402355%5D%2C+%5B8.790122%2C+49.40271%5D%2C+%5B8.7907%2C+49.403068%5D%2C+%5B8.791436%2C+49.403734%5D%2C+%5B8.792046%2C+49.404237%5D%2C+%5B8.792756%2C+49.404675%5D%2C+%5B8.793315%2C+49.405004%5D%2C+%5B8.793591%2C+49.405187%5D%2C+%5B8.793708%2C+49.405634%5D%2C+%5B8.793657%2C+49.406063%5D%2C+%5B8.793701%2C+49.40635%5D%2C+%5B8.793964%2C+49.406931%5D%2C+%5B8.79405%2C+49.40719%5D%2C+%5B8.793454%2C+49.408909%5D%2C+%5B8.79312%2C+49.409948%5D%2C+%5B8.793072%2C+49.410423%5D%2C+%5B8.792942%2C+49.411043%5D%2C+%5B8.792821%2C+49.411647%5D%2C+%5B8.792762%2C+49.411853%5D%2C+%5B8.792359%2C+49.412349%5D%2C+%5B8.790377%2C+49.414782%5D%2C+%5B8.790199%2C+49.414964%5D%2C+%5B8.790132%2C+49.415286%5D%2C+%5B8.789943%2C+49.416379%5D%2C+%5B8.78948%2C+49.417608%5D%2C+%5B8.789429%2C+49.418818%5D%2C+%5B8.789394%2C+49.419256%5D%2C+%5B8.789554%2C+49.419831%5D%2C+%5B8.789819%2C+49.420577%5D%2C+%5B8.789964%2C+49.421041%5D%2C+%5B8.790162%2C+49.421213%5D%2C+%5B8.791171%2C+49.42288%5D%2C+%5B8.791273%2C+49.423259%5D%2C+%5B8.790903%2C+49.424051%5D%2C+%5B8.789978%2C+49.425944%5D%2C+%5B8.789643%2C+49.42708%5D%2C+%5B8.78907%2C+49.430067%5D%2C+%5B8.78834%2C+49.431146%5D%2C+%5B8.786537%2C+49.432578%5D%2C+%5B8.779296%2C+49.432764%5D%2C+%5B8.777773%2C+49.432907%5D%2C+%5B8.777268%2C+49.433012%5D%2C+%5B8.776973%2C+49.433155%5D%2C+%5B8.776765%2C+49.433357%5D%2C+%5B8.775986%2C+49.434071%5D%2C+%5B8.774992%2C+49.43454%5D%2C+%5B8.774022%2C+49.434993%5D%2C+%5B8.773641%2C+49.435318%5D%2C+%5B8.773252%2C+49.435419%5D%2C+%5B8.772923%2C+49.435463%5D%2C+%5B8.77217%2C+49.435536%5D%2C+%5B8.77045%2C+49.435737%5D%2C+%5B8.769482%2C+49.436046%5D%2C+%5B8.766398%2C+49.437365%5D%2C+%5B8.766515%2C+49.440274%5D%2C+%5B8.766223%2C+49.440275%5D%2C+%5B8.76637%2C+49.441863%5D%2C+%5B8.766334%2C+49.443584%5D%2C+%5B8.764728%2C+49.446047%5D%2C+%5B8.761766%2C+49.448135%5D%2C+%5B8.761703%2C+49.450618%5D%2C+%5B8.761818%2C+49.450806%5D%2C+%5B8.762051%2C+49.450947%5D%2C+%5B8.763169%2C+49.451433%5D%2C+%5B8.764276%2C+49.451893%5D%2C+%5B8.765277%2C+49.452469%5D%2C+%5B8.766319%2C+49.453103%5D%2C+%5B8.767107%2C+49.453699%5D%2C+%5B8.767706%2C+49.454192%5D%2C+%5B8.765332%2C+49.455547%5D%2C+%5B8.763575%2C+49.456368%5D%2C+%5B8.76329%2C+49.456467%5D%2C+%5B8.762939%2C+49.456488%5D%2C+%5B8.76055%2C+49.456573%5D%2C+%5B8.758634%2C+49.456662%5D%2C+%5B8.757555%2C+49.456702%5D%2C+%5B8.757041%2C+49.457473%5D%2C+%5B8.756618%2C+49.457934%5D%2C+%5B8.756062%2C+49.458405%5D%2C+%5B8.755408%2C+49.458854%5D%2C+%5B8.754686%2C+49.459468%5D%2C+%5B8.754705%2C+49.459642%5D%2C+%5B8.753986%2C+49.459693%5D%2C+%5B8.753099%2C+49.459548%5D%2C+%5B8.75158%2C+49.459204%5D%2C+%5B8.750438%2C+49.458958%5D%2C+%5B8.750091%2C+49.459072%5D%2C+%5B8.748992%2C+49.459252%5D%2C+%5B8.747833%2C+49.459441%5D%2C+%5B8.746352%2C+49.459567%5D%2C+%5B8.74547%2C+49.459614%5D%2C+%5B8.745235%2C+49.459571%5D%2C+%5B8.744884%2C+49.459434%5D%2C+%5B8.744315%2C+49.459159%5D%2C+%5B8.743336%2C+49.458648%5D%2C+%5B8.74322%2C+49.458497%5D%2C+%5B8.74324%2C+49.458378%5D%2C+%5B8.743121%2C+49.458305%5D%2C+%5B8.742643%2C+49.45808%5D%2C+%5B8.741969%2C+49.45777%5D%2C+%5B8.741674%2C+49.457558%5D%2C+%5B8.741746%2C+49.457041%5D%2C+%5B8.741807%2C+49.456563%5D%2C+%5B8.741842%2C+49.456478%5D%2C+%5B8.742048%2C+49.4564%5D%2C+%5B8.74257%2C+49.456328%5D%2C+%5B8.74305%2C+49.456335%5D%2C+%5B8.743558%2C+49.456352%5D%2C+%5B8.744051%2C+49.456283%5D%2C+%5B8.744436%2C+49.456168%5D%2C+%5B8.744628%2C+49.456101%5D%2C+%5B8.744776%2C+49.456069%5D%2C+%5B8.744954%2C+49.456089%5D%2C+%5B8.745101%2C+49.456082%5D%2C+%5B8.745388%2C+49.455974%5D%2C+%5B8.745895%2C+49.455754%5D%2C+%5B8.746052%2C+49.455642%5D%2C+%5B8.746083%2C+49.455363%5D%2C+%5B8.746078%2C+49.455054%5D%2C+%5B8.746063%2C+49.454896%5D%2C+%5B8.745975%2C+49.454771%5D%2C+%5B8.745912%2C+49.454691%5D%2C+%5B8.745943%2C+49.454522%5D%2C+%5B8.746025%2C+49.45416%5D%2C+%5B8.746118%2C+49.453701%5D%2C+%5B8.746201%2C+49.453499%5D%2C+%5B8.746235%2C+49.453234%5D%2C+%5B8.745966%2C+49.452626%5D%2C+%5B8.745586%2C+49.451991%5D%2C+%5B8.744871%2C+49.451023%5D%2C+%5B8.744719%2C+49.450685%5D%2C+%5B8.744556%2C+49.450429%5D%2C+%5B8.744724%2C+49.450278%5D%2C+%5B8.74472%2C+49.450033%5D%2C+%5B8.744778%2C+49.449761%5D%2C+%5B8.745026%2C+49.449477%5D%2C+%5B8.745437%2C+49.449085%5D%2C+%5B8.745681%2C+49.448824%5D%2C+%5B8.745748%2C+49.448674%5D%2C+%5B8.745808%2C+49.448379%5D%2C+%5B8.745854%2C+49.448142%5D%2C+%5B8.745742%2C+49.447418%5D%2C+%5B8.7457%2C+49.446891%5D%2C+%5B8.739206%2C+49.446386%5D%2C+%5B8.739212%2C+49.44694%5D%2C+%5B8.738431%2C+49.448017%5D%2C+%5B8.738008%2C+49.448423%5D%2C+%5B8.737318%2C+49.44906%5D%2C+%5B8.737083%2C+49.449171%5D%2C+%5B8.736852%2C+49.449237%5D%2C+%5B8.736541%2C+49.449242%5D%2C+%5B8.735799%2C+49.449127%5D%2C+%5B8.735152%2C+49.448956%5D%2C+%5B8.734623%2C+49.44883%5D%2C+%5B8.732748%2C+49.448569%5D%2C+%5B8.730015%2C+49.448373%5D%2C+%5B8.726439%2C+49.448213%5D%2C+%5B8.725438%2C+49.448206%5D%2C+%5B8.724195%2C+49.448452%5D%2C+%5B8.723635%2C+49.448579%5D%2C+%5B8.722897%2C+49.448821%5D%2C+%5B8.722459%2C+49.449016%5D%2C+%5B8.722077%2C+49.449185%5D%2C+%5B8.721853%2C+49.449345%5D%2C+%5B8.721696%2C+49.449503%5D%2C+%5B8.721441%2C+49.449815%5D%2C+%5B8.721023%2C+49.450238%5D%2C+%5B8.720491%2C+49.450545%5D%2C+%5B8.720086%2C+49.450745%5D%2C+%5B8.719614%2C+49.451065%5D%2C+%5B8.718989%2C+49.451482%5D%2C+%5B8.718324%2C+49.451829%5D%2C+%5B8.717828%2C+49.452039%5D%2C+%5B8.717415%2C+49.452118%5D%2C+%5B8.716995%2C+49.452187%5D%2C+%5B8.716368%2C+49.452291%5D%2C+%5B8.715647%2C+49.452383%5D%2C+%5B8.715174%2C+49.45242%5D%2C+%5B8.714813%2C+49.452338%5D%2C+%5B8.714396%2C+49.452172%5D%2C+%5B8.714027%2C+49.451964%5D%2C+%5B8.713729%2C+49.451763%5D%2C+%5B8.713448%2C+49.451547%5D%2C+%5B8.71308%2C+49.451414%5D%2C+%5B8.712981%2C+49.451337%5D%2C+%5B8.712907%2C+49.451188%5D%2C+%5B8.712618%2C+49.450635%5D%2C+%5B8.712221%2C+49.450263%5D%2C+%5B8.712063%2C+49.450181%5D%2C+%5B8.711923%2C+49.450062%5D%2C+%5B8.711712%2C+49.449848%5D%2C+%5B8.711383%2C+49.449483%5D%2C+%5B8.710697%2C+49.449013%5D%2C+%5B8.709519%2C+49.447725%5D%2C+%5B8.709467%2C+49.447501%5D%2C+%5B8.709412%2C+49.446647%5D%2C+%5B8.709325%2C+49.446198%5D%2C+%5B8.708958%2C+49.446156%5D%2C+%5B8.708062%2C+49.445948%5D%2C+%5B8.707527%2C+49.44589%5D%2C+%5B8.706733%2C+49.445857%5D%2C+%5B8.706043%2C+49.445872%5D%2C+%5B8.705379%2C+49.445924%5D%2C+%5B8.704486%2C+49.446022%5D%2C+%5B8.703989%2C+49.446%5D%2C+%5B8.703226%2C+49.44592%5D%2C+%5B8.702235%2C+49.445819%5D%2C+%5B8.700441%2C+49.445474%5D%2C+%5B8.698009%2C+49.445025%5D%2C+%5B8.696864%2C+49.444866%5D%2C+%5B8.696436%2C+49.444666%5D%2C+%5B8.695967%2C+49.444555%5D%2C+%5B8.695515%2C+49.444586%5D%2C+%5B8.693686%2C+49.444206%5D%2C+%5B8.692734%2C+49.444117%5D%2C+%5B8.692166%2C+49.444018%5D%2C+%5B8.691707%2C+49.443845%5D%2C+%5B8.691244%2C+49.44365%5D%2C+%5B8.690496%2C+49.443236%5D%2C+%5B8.68937%2C+49.443263%5D%2C+%5B8.687857%2C+49.443212%5D%2C+%5B8.687569%2C+49.443035%5D%2C+%5B8.687363%2C+49.443008%5D%2C+%5B8.686799%2C+49.443048%5D%2C+%5B8.686065%2C+49.443213%5D%2C+%5B8.685572%2C+49.443319%5D%2C+%5B8.685446%2C+49.443329%5D%2C+%5B8.68496%2C+49.443355%5D%2C+%5B8.684607%2C+49.44354%5D%2C+%5B8.6843%2C+49.443671%5D%2C+%5B8.683887%2C+49.443768%5D%2C+%5B8.683496%2C+49.443878%5D%2C+%5B8.682964%2C+49.444%5D%2C+%5B8.682677%2C+49.443971%5D%2C+%5B8.682335%2C+49.443862%5D%2C+%5B8.682116%2C+49.443773%5D%2C+%5B8.681657%2C+49.443692%5D%2C+%5B8.681244%2C+49.443675%5D%2C+%5B8.680796%2C+49.443534%5D%2C+%5B8.680393%2C+49.443483%5D%2C+%5B8.67967%2C+49.443133%5D%2C+%5B8.679113%2C+49.44302%5D%2C+%5B8.679192%2C+49.442698%5D%2C+%5B8.678359%2C+49.442592%5D%2C+%5B8.678334%2C+49.442624%5D%2C+%5B8.677207%2C+49.442494%5D%2C+%5B8.677012%2C+49.442391%5D%2C+%5B8.676933%2C+49.442349%5D%2C+%5B8.676097%2C+49.442199%5D%2C+%5B8.675185%2C+49.442035%5D%2C+%5B8.674777%2C+49.441968%5D%2C+%5B8.673293%2C+49.441504%5D%2C+%5B8.672861%2C+49.441253%5D%2C+%5B8.67274%2C+49.441326%5D%2C+%5B8.672341%2C+49.441125%5D%2C+%5B8.671575%2C+49.440834%5D%2C+%5B8.670761%2C+49.441343%5D%2C+%5B8.67033%2C+49.441494%5D%2C+%5B8.669915%2C+49.441467%5D%2C+%5B8.668932%2C+49.441262%5D%2C+%5B8.667104%2C+49.440835%5D%2C+%5B8.666264%2C+49.440772%5D%2C+%5B8.665848%2C+49.44064%5D%2C+%5B8.665677%2C+49.440536%5D%2C+%5B8.665516%2C+49.440349%5D%2C+%5B8.665451%2C+49.4403%5D%2C+%5B8.665403%2C+49.440276%5D%2C+%5B8.665344%2C+49.440267%5D%2C+%5B8.665214%2C+49.440227%5D%2C+%5B8.663494%2C+49.439941%5D%2C+%5B8.663245%2C+49.439948%5D%2C+%5B8.663238%2C+49.439857%5D%2C+%5B8.663046%2C+49.43963%5D%2C+%5B8.662637%2C+49.439686%5D%2C+%5B8.662439%2C+49.439689%5D%2C+%5B8.662374%2C+49.439664%5D%2C+%5B8.661735%2C+49.439538%5D%2C+%5B8.66046%2C+49.439285%5D%2C+%5B8.658385%2C+49.438873%5D%2C+%5B8.658267%2C+49.439039%5D%2C+%5B8.657003%2C+49.438758%5D%2C+%5B8.656308%2C+49.438606%5D%2C+%5B8.65531%2C+49.439943%5D%2C+%5B8.653394%2C+49.439318%5D%2C+%5B8.649054%2C+49.437866%5D%2C+%5B8.648862%2C+49.437785%5D%2C+%5B8.648968%2C+49.437693%5D%2C+%5B8.648204%2C+49.437252%5D%2C+%5B8.647218%2C+49.436878%5D%2C+%5B8.646552%2C+49.436626%5D%2C+%5B8.646193%2C+49.436973%5D%2C+%5B8.646016%2C+49.437132%5D%2C+%5B8.645786%2C+49.437345%5D%2C+%5B8.645488%2C+49.437604%5D%2C+%5B8.645159%2C+49.437851%5D%2C+%5B8.644754%2C+49.438187%5D%2C+%5B8.644168%2C+49.438623%5D%2C+%5B8.643741%2C+49.438944%5D%2C+%5B8.643149%2C+49.439309%5D%2C+%5B8.64261%2C+49.43961%5D%2C+%5B8.642565%2C+49.439635%5D%2C+%5B8.642246%2C+49.439785%5D%2C+%5B8.641292%2C+49.440204%5D%2C+%5B8.64031%2C+49.440607%5D%2C+%5B8.639305%2C+49.44098%5D%2C+%5B8.638212%2C+49.441336%5D%2C+%5B8.637172%2C+49.441631%5D%2C+%5B8.636078%2C+49.441893%5D%2C+%5B8.634983%2C+49.442126%5D%2C+%5B8.633858%2C+49.442342%5D%2C+%5B8.632702%2C+49.442524%5D%2C+%5B8.631601%2C+49.44272%5D%2C+%5B8.630453%2C+49.442879%5D%2C+%5B8.629367%2C+49.443018%5D%2C+%5B8.628703%2C+49.443115%5D%2C+%5B8.628211%2C+49.441976%5D%2C+%5B8.628012%2C+49.44157%5D%2C+%5B8.627351%2C+49.440827%5D%2C+%5B8.626819%2C+49.440265%5D%2C+%5B8.626616%2C+49.440059%5D%2C+%5B8.626743%2C+49.439998%5D%2C+%5B8.626134%2C+49.439394%5D%2C+%5B8.62331%2C+49.436631%5D%2C+%5B8.623037%2C+49.436356%5D%2C+%5B8.622925%2C+49.436225%5D%2C+%5B8.622831%2C+49.436102%5D%2C+%5B8.62272%2C+49.435915%5D%2C+%5B8.622619%2C+49.43575%5D%2C+%5B8.622521%2C+49.435595%5D%2C+%5B8.622406%2C+49.435412%5D%2C+%5B8.622309%2C+49.435279%5D%2C+%5B8.622204%2C+49.435144%5D%2C+%5B8.621395%2C+49.434225%5D%2C+%5B8.621165%2C+49.433964%5D%2C+%5B8.621049%2C+49.433843%5D%2C+%5B8.620927%2C+49.433893%5D%2C+%5B8.619493%2C+49.432264%5D%2C+%5B8.61626%2C+49.428621%5D%2C+%5B8.615974%2C+49.428303%5D%2C+%5B8.61531%2C+49.427884%5D%2C+%5B8.609952%2C+49.424382%5D%2C+%5B8.609733%2C+49.424242%5D%2C+%5B8.605472%2C+49.425826%5D%2C+%5B8.599884%2C+49.427972%5D%2C+%5B8.598382%2C+49.428508%5D%2C+%5B8.59849%2C+49.428044%5D%2C+%5B8.59823%2C+49.428143%5D%2C+%5B8.593259%2C+49.430025%5D%2C+%5B8.592963%2C+49.430133%5D%2C+%5B8.590778%2C+49.427735%5D%2C+%5B8.59045%2C+49.427367%5D%2C+%5B8.590054%2C+49.427347%5D%2C+%5B8.589757%2C+49.427329%5D%2C+%5B8.589435%2C+49.427296%5D%2C+%5B8.588442%2C+49.427151%5D%2C+%5B8.587764%2C+49.427038%5D%2C+%5B8.58724%2C+49.426958%5D%2C+%5B8.587079%2C+49.426934%5D%2C+%5B8.586232%2C+49.426846%5D%2C+%5B8.584079%2C+49.4266%5D%2C+%5B8.583508%2C+49.426541%5D%2C+%5B8.582919%2C+49.426506%5D%2C+%5B8.582023%2C+49.426473%5D%2C+%5B8.581379%2C+49.42645%5D%2C+%5B8.579833%2C+49.426199%5D%2C+%5B8.577791%2C+49.425274%5D%2C+%5B8.577288%2C+49.425085%5D%2C+%5B8.575385%2C+49.424356%5D%2C+%5B8.57529%2C+49.424422%5D%2C+%5B8.574243%2C+49.424021%5D%2C+%5B8.573179%2C+49.4236%5D%5D%5D%7D%2C+%22properties%22%3A+%7B%22osm_id%22%3A+-285864%2C+%22boundary%22%3A+%22administrative%22%2C+%22admin_level%22%3A+6%2C+%22parents%22%3A+%22-22027%2C-62611%2C-51477%22%2C+%22name%22%3A+%22Heidelberg%22%2C+%22local_name%22%3A+%22Heidelberg%22%2C+%22name_en%22%3A+null%7D%7D%5D%7D - headers: - accept: - - '*/*' - accept-encoding: - - gzip, deflate - connection: - - keep-alive - content-length: - - '30534' - content-type: - - application/x-www-form-urlencoded - host: - - api.ohsome.org - user-agent: - - ohsome-quality-api/1.0.1 - method: POST - uri: https://api.ohsome.org/v1/elements/area - response: - content: "{\n \"attribution\" : {\n \"url\" : \"https://ohsome.org/copyrights\",\n - \ \"text\" : \"\xA9 OpenStreetMap contributors\"\n },\n \"apiVersion\" : - \"1.10.0\",\n \"result\" : [ {\n \"timestamp\" : \"2023-09-10T20:00:00Z\",\n - \ \"value\" : 6519581.21\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' - cache-control: - - no-cache, no-store, must-revalidate - connection: - - Keep-Alive - content-encoding: - - gzip - content-type: - - application/json - date: - - Tue, 19 Sep 2023 13:46:47 GMT - keep-alive: - - timeout=5, max=100 - server: - - Apache/2.4.52 (Ubuntu) - strict-transport-security: - - max-age=63072000; includeSubdomains; - transfer-encoding: - - chunked - vary: - - accept-encoding - http_version: HTTP/1.1 - status_code: 200 - request: body: filter=building%3D%2A+and+building%21%3Dno+and+geometry%3Apolygon&bpolys=%7B%22type%22%3A+%22FeatureCollection%22%2C+%22features%22%3A+%5B%7B%22type%22%3A+%22Feature%22%2C+%22geometry%22%3A+%7B%22type%22%3A+%22MultiPolygon%22%2C+%22coordinates%22%3A+%5B%5B%5B%5B13.368229%2C+52.493336%5D%2C+%5B13.376562%2C+52.49167%5D%2C+%5B13.373518%2C+52.48797%5D%2C+%5B13.374019%2C+52.485167%5D%2C+%5B13.371607%2C+52.484979%5D%2C+%5B13.394261%2C+52.485775%5D%2C+%5B13.394613%2C+52.484022%5D%2C+%5B13.406346%2C+52.482792%5D%2C+%5B13.407887%2C+52.48886%5D%2C+%5B13.423705%2C+52.486384%5D%2C+%5B13.42535%2C+52.488182%5D%2C+%5B13.4204%2C+52.495864%5D%2C+%5B13.439262%2C+52.48961%5D%2C+%5B13.445549%2C+52.494856%5D%2C+%5B13.45293%2C+52.497707%5D%2C+%5B13.463355%2C+52.495486%5D%2C+%5B13.464279%2C+52.493719%5D%2C+%5B13.476147%2C+52.489944%5D%2C+%5B13.478628%2C+52.487033%5D%2C+%5B13.481678%2C+52.487638%5D%2C+%5B13.482954%2C+52.48605%5D%2C+%5B13.491443%2C+52.488283%5D%2C+%5B13.473076%2C+52.499003%5D%2C+%5B13.468573%2C+52.499657%5D%2C+%5B13.471164%2C+52.505138%5D%2C+%5B13.476266%2C+52.510439%5D%2C+%5B13.475888%2C+52.514863%5D%2C+%5B13.477729%2C+52.51472%5D%2C+%5B13.47239%2C+52.520571%5D%2C+%5B13.462695%2C+52.519928%5D%2C+%5B13.45529%2C+52.521272%5D%2C+%5B13.456157%2C+52.522458%5D%2C+%5B13.452183%2C+52.527796%5D%2C+%5B13.447168%2C+52.526408%5D%2C+%5B13.442277%2C+52.531026%5D%2C+%5B13.438748%2C+52.528778%5D%2C+%5B13.423642%2C+52.527915%5D%2C+%5B13.419753%2C+52.525546%5D%2C+%5B13.429188%2C+52.521203%5D%2C+%5B13.425927%2C+52.518393%5D%2C+%5B13.42278%2C+52.512234%5D%2C+%5B13.429402%2C+52.508563%5D%2C+%5B13.4272%2C+52.505667%5D%2C+%5B13.414072%2C+52.504037%5D%2C+%5B13.409969%2C+52.506928%5D%2C+%5B13.40803%2C+52.506183%5D%2C+%5B13.400228%2C+52.509382%5D%2C+%5B13.39923%2C+52.508077%5D%2C+%5B13.37765%2C+52.507966%5D%2C+%5B13.37498%2C+52.503376%5D%2C+%5B13.373609%2C+52.504164%5D%2C+%5B13.369514%2C+52.498879%5D%2C+%5B13.368229%2C+52.493336%5D%5D%5D%5D%7D%2C+%22properties%22%3A+%7B%22osm_id%22%3A+-55764%2C+%22boundary%22%3A+%22administrative%22%2C+%22admin_level%22%3A+9%2C+%22parents%22%3A+%22-62422%2C-51477%22%2C+%22name%22%3A+%22Friedrichshain-Kreuzberg%22%2C+%22local_name%22%3A+%22Friedrichshain-Kreuzberg%22%2C+%22name_en%22%3A+null%7D%7D%5D%7D headers: @@ -72,14 +15,14 @@ interactions: host: - api.ohsome.org user-agent: - - ohsome-quality-api/1.0.1 + - ohsome-quality-api/1.2.1 method: POST uri: https://api.ohsome.org/v1/elements/area response: content: "{\n \"attribution\" : {\n \"url\" : \"https://ohsome.org/copyrights\",\n \ \"text\" : \"\xA9 OpenStreetMap contributors\"\n },\n \"apiVersion\" : - \"1.10.0\",\n \"result\" : [ {\n \"timestamp\" : \"2023-09-10T20:00:00Z\",\n - \ \"value\" : 5036441.31\n } ]\n}" + \"1.10.1\",\n \"result\" : [ {\n \"timestamp\" : \"2024-01-30T09:00:00Z\",\n + \ \"value\" : 5037018.84\n } ]\n}" headers: access-control-allow-credentials: - 'true' @@ -100,11 +43,11 @@ interactions: content-type: - application/json date: - - Tue, 26 Sep 2023 12:12:22 GMT + - Sat, 03 Feb 2024 10:37:04 GMT keep-alive: - timeout=5, max=100 server: - - Apache/2.4.52 (Ubuntu) + - Apache strict-transport-security: - max-age=63072000; includeSubdomains; transfer-encoding: @@ -114,7 +57,7 @@ interactions: http_version: HTTP/1.1 status_code: 200 - request: - body: filter=building%3D%2A+and+building%21%3Dno+and+geometry%3Apolygon&bpolys=%7B%22type%22%3A+%22FeatureCollection%22%2C+%22features%22%3A+%5B%7B%22type%22%3A+%22Feature%22%2C+%22geometry%22%3A+%7B%22type%22%3A+%22Polygon%22%2C+%22coordinates%22%3A+%5B%5B%5B13.369514%2C+52.498879%5D%2C+%5B13.373609%2C+52.504164%5D%2C+%5B13.37498%2C+52.503376%5D%2C+%5B13.37765%2C+52.507966%5D%2C+%5B13.39923%2C+52.508077%5D%2C+%5B13.400228%2C+52.509382%5D%2C+%5B13.40803%2C+52.506183%5D%2C+%5B13.409969%2C+52.506928%5D%2C+%5B13.414072%2C+52.504037%5D%2C+%5B13.4272%2C+52.505667%5D%2C+%5B13.429402%2C+52.508563%5D%2C+%5B13.42278%2C+52.512234%5D%2C+%5B13.425927%2C+52.518393%5D%2C+%5B13.429188%2C+52.521203%5D%2C+%5B13.419753%2C+52.525546%5D%2C+%5B13.423642%2C+52.527915%5D%2C+%5B13.438748%2C+52.528778%5D%2C+%5B13.442277%2C+52.531026%5D%2C+%5B13.447168%2C+52.526408%5D%2C+%5B13.452183%2C+52.527796%5D%2C+%5B13.456157%2C+52.522458%5D%2C+%5B13.45529%2C+52.521272%5D%2C+%5B13.462695%2C+52.519928%5D%2C+%5B13.47239%2C+52.520571%5D%2C+%5B13.477729%2C+52.51472%5D%2C+%5B13.475888%2C+52.514863%5D%2C+%5B13.476266%2C+52.510439%5D%2C+%5B13.471164%2C+52.505138%5D%2C+%5B13.468573%2C+52.499657%5D%2C+%5B13.473076%2C+52.499003%5D%2C+%5B13.491443%2C+52.488283%5D%2C+%5B13.482954%2C+52.48605%5D%2C+%5B13.481678%2C+52.487638%5D%2C+%5B13.478628%2C+52.487033%5D%2C+%5B13.476147%2C+52.489944%5D%2C+%5B13.464279%2C+52.493719%5D%2C+%5B13.463355%2C+52.495486%5D%2C+%5B13.45293%2C+52.497707%5D%2C+%5B13.445549%2C+52.494856%5D%2C+%5B13.439262%2C+52.48961%5D%2C+%5B13.4204%2C+52.495864%5D%2C+%5B13.42535%2C+52.488182%5D%2C+%5B13.423705%2C+52.486384%5D%2C+%5B13.407887%2C+52.48886%5D%2C+%5B13.406346%2C+52.482792%5D%2C+%5B13.394613%2C+52.484022%5D%2C+%5B13.394261%2C+52.485775%5D%2C+%5B13.371607%2C+52.484979%5D%2C+%5B13.374019%2C+52.485167%5D%2C+%5B13.373518%2C+52.48797%5D%2C+%5B13.376562%2C+52.49167%5D%2C+%5B13.368229%2C+52.493336%5D%2C+%5B13.369514%2C+52.498879%5D%5D%5D%7D%2C+%22properties%22%3A+%7B%22osm_id%22%3A+-55764%2C+%22boundary%22%3A+%22administrative%22%2C+%22admin_level%22%3A+9%2C+%22parents%22%3A+%22-62422%2C-51477%22%2C+%22name%22%3A+%22Friedrichshain-Kreuzberg%22%2C+%22local_name%22%3A+%22Friedrichshain-Kreuzberg%22%2C+%22name_en%22%3A+null%7D%7D%5D%7D + body: filter=building%3D%2A+and+building%21%3Dno+and+geometry%3Apolygon&bpolys=%7B%22type%22%3A+%22FeatureCollection%22%2C+%22features%22%3A+%5B%7B%22type%22%3A+%22Feature%22%2C+%22geometry%22%3A+%7B%22type%22%3A+%22MultiPolygon%22%2C+%22coordinates%22%3A+%5B%5B%5B%5B13.368229%2C+52.493336%5D%2C+%5B13.376562%2C+52.49167%5D%2C+%5B13.373518%2C+52.48797%5D%2C+%5B13.374019%2C+52.485167%5D%2C+%5B13.371607%2C+52.484979%5D%2C+%5B13.394261%2C+52.485775%5D%2C+%5B13.394613%2C+52.484022%5D%2C+%5B13.406346%2C+52.482792%5D%2C+%5B13.407887%2C+52.48886%5D%2C+%5B13.423705%2C+52.486384%5D%2C+%5B13.42535%2C+52.488182%5D%2C+%5B13.4204%2C+52.495864%5D%2C+%5B13.439262%2C+52.48961%5D%2C+%5B13.445549%2C+52.494856%5D%2C+%5B13.45293%2C+52.497707%5D%2C+%5B13.463355%2C+52.495486%5D%2C+%5B13.464279%2C+52.493719%5D%2C+%5B13.476147%2C+52.489944%5D%2C+%5B13.478628%2C+52.487033%5D%2C+%5B13.481678%2C+52.487638%5D%2C+%5B13.482954%2C+52.48605%5D%2C+%5B13.491443%2C+52.488283%5D%2C+%5B13.473076%2C+52.499003%5D%2C+%5B13.468573%2C+52.499657%5D%2C+%5B13.471164%2C+52.505138%5D%2C+%5B13.476266%2C+52.510439%5D%2C+%5B13.475888%2C+52.514863%5D%2C+%5B13.477729%2C+52.51472%5D%2C+%5B13.47239%2C+52.520571%5D%2C+%5B13.462695%2C+52.519928%5D%2C+%5B13.45529%2C+52.521272%5D%2C+%5B13.456157%2C+52.522458%5D%2C+%5B13.452183%2C+52.527796%5D%2C+%5B13.447168%2C+52.526408%5D%2C+%5B13.442277%2C+52.531026%5D%2C+%5B13.438748%2C+52.528778%5D%2C+%5B13.423642%2C+52.527915%5D%2C+%5B13.419753%2C+52.525546%5D%2C+%5B13.429188%2C+52.521203%5D%2C+%5B13.425927%2C+52.518393%5D%2C+%5B13.42278%2C+52.512234%5D%2C+%5B13.429402%2C+52.508563%5D%2C+%5B13.4272%2C+52.505667%5D%2C+%5B13.414072%2C+52.504037%5D%2C+%5B13.409969%2C+52.506928%5D%2C+%5B13.40803%2C+52.506183%5D%2C+%5B13.400228%2C+52.509382%5D%2C+%5B13.39923%2C+52.508077%5D%2C+%5B13.37765%2C+52.507966%5D%2C+%5B13.37498%2C+52.503376%5D%2C+%5B13.373609%2C+52.504164%5D%2C+%5B13.369514%2C+52.498879%5D%2C+%5B13.368229%2C+52.493336%5D%5D%5D%5D%7D%2C+%22properties%22%3A+%7B%22osm_id%22%3A+-55764%2C+%22boundary%22%3A+%22administrative%22%2C+%22admin_level%22%3A+9%2C+%22parents%22%3A+%22-62422%2C-51477%22%2C+%22name%22%3A+%22Friedrichshain-Kreuzberg%22%2C+%22local_name%22%3A+%22Friedrichshain-Kreuzberg%22%2C+%22name_en%22%3A+null%7D%7D%5D%7D headers: accept: - '*/*' @@ -123,20 +66,20 @@ interactions: connection: - keep-alive content-length: - - '2231' + - '2242' content-type: - application/x-www-form-urlencoded host: - api.ohsome.org user-agent: - - ohsome-quality-api/1.0.2 + - ohsome-quality-api/1.2.1 method: POST uri: https://api.ohsome.org/v1/elements/area response: content: "{\n \"attribution\" : {\n \"url\" : \"https://ohsome.org/copyrights\",\n \ \"text\" : \"\xA9 OpenStreetMap contributors\"\n },\n \"apiVersion\" : - \"1.10.1\",\n \"result\" : [ {\n \"timestamp\" : \"2023-10-22T20:00:00Z\",\n - \ \"value\" : 5026307.68\n } ]\n}" + \"1.10.1\",\n \"result\" : [ {\n \"timestamp\" : \"2024-01-30T09:00:00Z\",\n + \ \"value\" : 5037018.84\n } ]\n}" headers: access-control-allow-credentials: - 'true' @@ -157,7 +100,7 @@ interactions: content-type: - application/json date: - - Mon, 13 Nov 2023 11:14:11 GMT + - Sat, 03 Feb 2024 10:37:12 GMT keep-alive: - timeout=5, max=100 server: diff --git a/tests/integrationtests/indicators/test_building_comparison.py b/tests/integrationtests/indicators/test_building_comparison.py index 4c645f0be..291b951eb 100644 --- a/tests/integrationtests/indicators/test_building_comparison.py +++ b/tests/integrationtests/indicators/test_building_comparison.py @@ -8,52 +8,96 @@ from ohsome_quality_api.indicators.building_comparison.indicator import ( BuildingComparison, - get_sources, ) from tests.integrationtests.utils import oqapi_vcr -@pytest.fixture(scope="class") +@pytest.fixture def mock_get_building_area(class_mocker): - async_mock = AsyncMock(return_value=4842587.791645115) + async def side_effect_function(*args, **kwargs): + if args[1] == "EUBUCCO": + return 6000000.791645115 + else: + return 5000000.791645115 + + async_mock = AsyncMock(side_effect=side_effect_function) class_mocker.patch( - "ohsome_quality_api.indicators.building_comparison.indicator.get_eubucco_building_area", + "ohsome_quality_api.indicators.building_comparison.indicator.get_reference_building_area", side_effect=async_mock, ) -@pytest.fixture(scope="class") +@pytest.fixture def mock_get_building_area_low(class_mocker): - async_mock = AsyncMock(return_value=1) + async_mock = AsyncMock(return_value=1000000) + class_mocker.patch( + "ohsome_quality_api.indicators.building_comparison.indicator.get_reference_building_area", + side_effect=async_mock, + ) + + +@pytest.fixture +def mock_get_building_area_low_some(class_mocker): + async def side_effect_function(*args, **kwargs): + if args[1] == "EUBUCCO": + return 1 + else: + return 6000000.791645115 + + async_mock = AsyncMock(side_effect=side_effect_function) class_mocker.patch( - "ohsome_quality_api.indicators.building_comparison.indicator.get_eubucco_building_area", + "ohsome_quality_api.indicators.building_comparison.indicator.get_reference_building_area", side_effect=async_mock, ) -@pytest.fixture(scope="class") +@pytest.fixture def mock_get_building_area_empty(class_mocker): async_mock = AsyncMock(return_value=0) class_mocker.patch( - "ohsome_quality_api.indicators.building_comparison.indicator.get_eubucco_building_area", + "ohsome_quality_api.indicators.building_comparison.indicator.get_reference_building_area", side_effect=async_mock, ) -@pytest.fixture(scope="class") -def mock_get_eubucco_coverage_intersection(class_mocker, feature_germany_berlin): +@pytest.fixture +def mock_get_intersection_geom(class_mocker, feature_germany_berlin): async_mock = AsyncMock(return_value=feature_germany_berlin) class_mocker.patch( - "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_eubucco_coverage_intersection", + "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_intersection_geom", side_effect=async_mock, ) -@pytest.fixture(scope="class") -def mock_get_eubucco_coverage_intersection_area(class_mocker): - async_mock = AsyncMock(return_value=[{"area_ratio": 1}]) +@pytest.fixture +def mock_get_intersection_area(class_mocker): + async_mock = AsyncMock(return_value=1.0) class_mocker.patch( - "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_eubucco_coverage_intersection_area", + "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_intersection_area", + side_effect=async_mock, + ) + + +@pytest.fixture +def mock_get_intersection_area_none(class_mocker): + async_mock = AsyncMock(return_value=0) + class_mocker.patch( + "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_intersection_area", + side_effect=async_mock, + ) + + +@pytest.fixture +def mock_get_intersection_area_some(class_mocker): + async def side_effect(*args, **kwargs): + if "eubucco" in args[1]: + return 0.0 # 0 % + else: + return 1.0 # 100 % + + async_mock = AsyncMock(side_effect=side_effect) + class_mocker.patch( + "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_intersection_area", side_effect=async_mock, ) @@ -64,39 +108,83 @@ def test_init(self, topic_building_area, feature_germany_berlin): indicator = BuildingComparison(topic_building_area, feature_germany_berlin) assert indicator.th_high == 0.85 assert indicator.th_low == 0.5 + assert isinstance(indicator.data_ref, dict) + + def test_get_sources(self, topic_building_area, feature_germany_berlin): + indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + source = indicator.format_sources() + assert "EUBUCCO" in source + + def test_attribution(self, topic_building_area, feature_germany_berlin): + indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + assert indicator.attribution is not None + assert indicator.attribution != "" class TestPreprocess: @oqapi_vcr.use_cassette - def test_preprocess( + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_area", + "mock_get_intersection_geom", + ) + def test_preprocess(self, topic_building_area, feature_germany_berlin): + indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + asyncio.run(indicator.preprocess()) + + for area in indicator.area_osm.values(): + assert area is not None + assert area > 0 + assert isinstance(indicator.result.timestamp, datetime) + assert isinstance(indicator.result.timestamp_osm, datetime) + + @oqapi_vcr.use_cassette + def test_preprocess_no_intersection( self, mock_get_building_area, topic_building_area, feature_germany_berlin, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, + mock_get_intersection_area_none, + ): + indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + asyncio.run(indicator.preprocess()) + + for area in indicator.area_cov.values(): + assert area == 0.0 + assert isinstance(indicator.result.timestamp, datetime) + assert indicator.result.timestamp_osm is None + + @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area_some", + ) + def test_preprocess_some_intersection( + self, + topic_building_area, + feature_germany_berlin, ): indicator = BuildingComparison(topic_building_area, feature_germany_berlin) asyncio.run(indicator.preprocess()) - assert indicator.area_osm is not None - assert indicator.area_osm > 0 - assert indicator.area_references == { - "EUBUCCO": 4.842587791645116, - } + assert 1.0 in list(indicator.area_cov.values()) + assert 0.0 in list(indicator.area_cov.values()) assert isinstance(indicator.result.timestamp, datetime) assert isinstance(indicator.result.timestamp_osm, datetime) class TestCalculate: @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_area", + "mock_get_intersection_geom", + ) def test_calculate( self, - mock_get_building_area, topic_building_area, feature_germany_berlin, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, ): indicator = BuildingComparison(topic_building_area, feature_germany_berlin) asyncio.run(indicator.preprocess()) @@ -104,82 +192,172 @@ def test_calculate( assert indicator.result.value is not None assert indicator.result.value > 0 assert indicator.result.class_ is not None - assert indicator.result.class_ == 5 + assert indicator.result.class_ >= 0 @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area_empty", + "mock_get_intersection_area", + "mock_get_intersection_geom", + ) def test_calculate_reference_area_0( self, - mock_get_building_area_empty, topic_building_area, feature_germany_heidelberg, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, ): indicator = BuildingComparison(topic_building_area, feature_germany_heidelberg) asyncio.run(indicator.preprocess()) - indicator.coverage["EUBUCCO"] is None indicator.calculate() - assert indicator.result.value is None + assert indicator.result.value == 0.0 @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_intersection_area", + "mock_get_intersection_geom", + "mock_get_building_area_low", + ) def test_calculate_above_one_th( self, - mock_get_building_area_low, topic_building_area, feature_germany_heidelberg, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, ): indicator = BuildingComparison(topic_building_area, feature_germany_heidelberg) asyncio.run(indicator.preprocess()) indicator.calculate() - assert indicator.result.value is not None - assert indicator.result.value > 0 + assert indicator.result.value is None assert indicator.result.class_ is None assert indicator.result.description is not None + for v in indicator.area_ref.values(): + assert v is not None + for v in indicator.area_osm.values(): + assert v is not None assert ( - "Warning: Because of a big difference between OSM and the reference " - + "data no quality estimation has been made. " - + "It could be that the reference data is outdated. " + "Warning: OSM has substantivly more buildings mapped than the Reference " + "datasets. No quality estimation has been made." in indicator.result.description ) assert indicator.result.label == "undefined" + @oqapi_vcr.use_cassette + @pytest.mark.usefixtures("mock_get_intersection_area_none") + def test_calculate_no_intersection( + self, + topic_building_area, + feature_germany_heidelberg, + ): + indicator = BuildingComparison(topic_building_area, feature_germany_heidelberg) + asyncio.run(indicator.preprocess()) + indicator.calculate() + assert indicator.result.value is None + assert indicator.result.class_ is None + assert indicator.result.description is not None + assert ( + "Comparison could not be made. None of the reference datasets covers the " + "area-of-interest." in indicator.result.description + ) + assert indicator.result.label == "undefined" + + @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area_some", + ) + def test_calculate_some_intersection( + self, + topic_building_area, + feature_germany_heidelberg, + ): + indicator = BuildingComparison(topic_building_area, feature_germany_heidelberg) + asyncio.run(indicator.preprocess()) + indicator.calculate() + assert indicator.result.value is not None + assert indicator.result.value > 0 + assert indicator.result.class_ is not None + assert indicator.result.class_ >= 0 + assert indicator.result.description is not None + # major edge case description + assert ( + "Reference dataset EUBUCCO does not cover area-of-interest." + in indicator.result.description + ) + assert ( + "of the area-of-interest is covered by the reference dataset" + in indicator.result.description + ) -class TestFigure: - @pytest.fixture(scope="class") @oqapi_vcr.use_cassette - def indicator( + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area", + "mock_get_building_area_low_some", + ) + def test_calculate_above_one_th_and_expected( self, - mock_get_building_area, topic_building_area, - feature_germany_berlin, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, + feature_germany_heidelberg, ): - indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + indicator = BuildingComparison(topic_building_area, feature_germany_heidelberg) asyncio.run(indicator.preprocess()) indicator.calculate() - return indicator + assert indicator.result.value is not None + assert indicator.result.value > 0 + assert indicator.result.class_ is not None + assert indicator.result.class_ >= 0 + assert indicator.result.label != "undefined" + assert indicator.result.description is not None + assert ( + "of the area-of-interest is covered by the reference dataset" + in indicator.result.description + ) + # TODO: should user be warned if a dataset was not fit for comparison? + # -> for ratio above self.above_one_th + # assert ( + # "this data is not considered in the overall result value." + # in indicator.result.description + # ) + - def test_create_figure(self, indicator): +class TestFigure: + @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area", + ) + def test_create_figure(self, topic_building_area, feature_germany_berlin): + indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + asyncio.run(indicator.preprocess()) + indicator.calculate() indicator.create_figure() assert isinstance(indicator.result.figure, dict) pgo.Figure(indicator.result.figure) # test for valid Plotly figure + @oqapi_vcr.use_cassette @pytest.mark.skip(reason="Only for manual testing.") # comment for manual test - def test_create_figure_manual(self, indicator): + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area", + ) + def test_create_figure_manual(self, topic_building_area, feature_germany_berlin): + indicator = BuildingComparison(topic_building_area, feature_germany_berlin) + asyncio.run(indicator.preprocess()) + indicator.calculate() indicator.create_figure() pio.show(indicator.result.figure) @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area", + ) def test_create_figure_above_one_th( self, - mock_get_building_area_low, topic_building_area, feature_germany_berlin, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, ): indicator = BuildingComparison(topic_building_area, feature_germany_berlin) asyncio.run(indicator.preprocess()) @@ -190,13 +368,15 @@ def test_create_figure_above_one_th( pgo.Figure(indicator.result.figure) @oqapi_vcr.use_cassette + @pytest.mark.usefixtures( + "mock_get_building_area", + "mock_get_intersection_geom", + "mock_get_intersection_area", + ) def test_create_figure_building_area_zero( self, - mock_get_building_area_empty, topic_building_area, feature_germany_berlin, - mock_get_eubucco_coverage_intersection_area, - mock_get_eubucco_coverage_intersection, ): indicator = BuildingComparison(topic_building_area, feature_germany_berlin) asyncio.run(indicator.preprocess()) @@ -205,8 +385,3 @@ def test_create_figure_building_area_zero( assert isinstance(indicator.result.figure, dict) assert indicator.result.figure["data"][0]["type"] == "bar" pgo.Figure(indicator.result.figure) - - -def test_get_sources(): - source = get_sources(["EUBUCCO"]) - assert source == "EUBUCCO" diff --git a/tests/integrationtests/test_base_indicator.py b/tests/integrationtests/test_base_indicator.py index 155345e76..eb31bbb95 100644 --- a/tests/integrationtests/test_base_indicator.py +++ b/tests/integrationtests/test_base_indicator.py @@ -2,7 +2,7 @@ import plotly.graph_objects as pgo import pytest -from geojson import Polygon +from geojson import Feature from ohsome_quality_api.indicators.minimal.indicator import Minimal from ohsome_quality_api.indicators.models import Result @@ -59,15 +59,18 @@ def test_attribution_class_property(self): def test_coverage(self): coverage = asyncio.run(Minimal.coverage(inverse=False)) - assert isinstance(coverage, Polygon) - assert coverage.is_valid + for feature in coverage: + assert isinstance(feature, Feature) + assert feature.is_valid coverage_default = asyncio.run(Minimal.coverage()) - assert isinstance(coverage_default, Polygon) - assert coverage_default.is_valid + for feature in coverage_default: + assert isinstance(feature, Feature) + assert feature.is_valid assert coverage_default == coverage coverage_inversed = asyncio.run(Minimal.coverage(inverse=True)) - assert isinstance(coverage_inversed, Polygon) - assert coverage_inversed.is_valid + for feature in coverage_inversed: + assert isinstance(feature, Feature) + assert feature.is_valid assert coverage != coverage_inversed assert coverage_default != coverage_inversed diff --git a/tests/integrationtests/test_geodatabase.py b/tests/integrationtests/test_geodatabase.py index 8b27feddf..af4858c16 100644 --- a/tests/integrationtests/test_geodatabase.py +++ b/tests/integrationtests/test_geodatabase.py @@ -3,6 +3,7 @@ import geojson import pytest +from geojson import Feature import ohsome_quality_api.geodatabase.client as db_client @@ -64,35 +65,38 @@ def test_get_shdi_multiple_intersections(): assert result[0]["shdi"] <= 1.0 -def test_get_building_area(feature_germany_berlin): - result = asyncio.run(db_client.get_building_area(feature_germany_berlin)) - assert result[0]["area"] == 4842587.791645115 - - -def test_get_eubucco_coverage(): - result = asyncio.run(db_client.get_eubucco_coverage()) - obj: geojson.MultiPolygon = geojson.loads(result[0]["geom"]) - assert obj.is_valid - assert isinstance(obj, geojson.MultiPolygon) +@pytest.mark.parametrize( + "table", ["eubucco_v0_1_coverage_simple", "eubucco_v0_1_coverage_inversed"] +) +def test_get_reference_coverage(table): + result = asyncio.run(db_client.get_reference_coverage(table)) + assert result.is_valid + assert isinstance(result, Feature) def test_get_eubucco_coverage_intersection_area_none( feature_collection_germany_heidelberg, ): bpoly = feature_collection_germany_heidelberg.features[0] - result = asyncio.run(db_client.get_eubucco_coverage_intersection_area(bpoly)) - assert result == [] + result = asyncio.run( + db_client.get_intersection_area(bpoly, "eubucco_v0_1_coverage_simple") + ) + assert result == 0.0 def test_get_eubucco_coverage_intersection_area(feature_germany_berlin): bpoly = feature_germany_berlin - result = asyncio.run(db_client.get_eubucco_coverage_intersection_area(bpoly)) - assert pytest.approx(1.0, 0.1) == result[0]["area_ratio"] + result = asyncio.run( + db_client.get_intersection_area(bpoly, "eubucco_v0_1_coverage_simple") + ) + assert pytest.approx(1.0, 0.1) == result def test_get_coverage_intersection(feature_germany_berlin): bpoly = feature_germany_berlin - result = asyncio.run(db_client.get_eubucco_coverage_intersection(bpoly)) + result = asyncio.run( + db_client.get_intersection_geom(bpoly, "eubucco_v0_1_coverage_simple") + ) assert result["geometry"].is_valid assert isinstance(result, geojson.feature.Feature) diff --git a/tests/unittests/test_indicators_definitions.py b/tests/unittests/test_indicators_definitions.py index e5564f244..f6d28640b 100644 --- a/tests/unittests/test_indicators_definitions.py +++ b/tests/unittests/test_indicators_definitions.py @@ -9,34 +9,26 @@ @pytest.fixture(scope="class") -def mock_select_eubucco_coverage(class_mocker, feature_germany_berlin): - async def side_effect(*args, **kwargs): - inverse = args[0] - - if inverse: - return [ - { - "geom": geojson.dumps( - Polygon( - coordinates=[ - [ - (-180, 90), - (-180, -90), - (180, -90), - (180, 90), - (-180, 90), - ] - ] - ) - ) - } - ] - else: - return [{"geom": geojson.dumps(Polygon(coordinates=[]))}] - - async_mock = AsyncMock(side_effect=side_effect) +def mock_get_reference_coverage(class_mocker): + async_mock = AsyncMock( + return_value=str( + geojson.dumps( + Polygon( + coordinates=[ + [ + (-180, 90), + (-180, -90), + (180, -90), + (180, 90), + (-180, 90), + ] + ] + ) + ) + ) + ) class_mocker.patch( - "ohsome_quality_api.indicators.building_completeness.indicator.db_client.get_eubucco_coverage", + "ohsome_quality_api.indicators.building_comparison.indicator.db_client.get_reference_coverage", side_effect=async_mock, ) @@ -66,22 +58,23 @@ def test_get_indicator_definitions_with_project(): assert indicator.projects == ["core"] -def test_get_coverage(mock_select_eubucco_coverage): +def test_get_coverage(mock_get_reference_coverage): coverage = asyncio.run( definitions.get_coverage("building-comparison", inverse=False) ) assert coverage.is_valid - coverage_default = asyncio.run(definitions.get_coverage("building-comparison")) - assert coverage_default.is_valid - assert coverage == coverage_default - coverage_inversed = asyncio.run( + assert isinstance(coverage, geojson.FeatureCollection) + + coverage = asyncio.run(definitions.get_coverage("building-comparison")) + assert coverage.is_valid + assert isinstance(coverage, geojson.FeatureCollection) + + coverage = asyncio.run( definitions.get_coverage("building-comparison", inverse=True) ) - assert coverage_inversed.is_valid - assert coverage != coverage_inversed - assert coverage_default != coverage_inversed - + assert coverage.is_valid assert isinstance(coverage, geojson.FeatureCollection) + coverage = asyncio.run(definitions.get_coverage("mapping-saturation")) assert coverage.is_valid assert isinstance(coverage, geojson.FeatureCollection)