Skip to content

Commit 0fef807

Browse files
wip
1 parent e28f9ed commit 0fef807

File tree

17 files changed

+112
-108
lines changed

17 files changed

+112
-108
lines changed

workers/ohsome_quality_analyst/api/api.py

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
from ohsome_quality_analyst.config import configure_logging
3333
from ohsome_quality_analyst.definitions import (
3434
ATTRIBUTION_URL,
35-
INDICATOR_LAYER,
3635
get_attribution,
3736
get_dataset_names,
3837
get_fid_fields,
@@ -280,14 +279,6 @@ async def get_available_regions(asGeoJSON: bool = False):
280279
return response
281280

282281

283-
@app.get("/indicator-layer-combinations")
284-
async def get_indicator_layer_combinations():
285-
"""Get names of available indicator-layer combinations."""
286-
response = empty_api_response()
287-
response["result"] = INDICATOR_LAYER
288-
return response
289-
290-
291282
@app.get("/indicators")
292283
async def indicator_names():
293284
"""Get names of available indicators."""

workers/ohsome_quality_analyst/api/request_models.py

Lines changed: 33 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,20 @@
88
"""
99

1010
from enum import Enum
11-
from typing import Optional, Union
11+
from typing import Optional, Tuple, Union
1212

1313
import pydantic
1414
from geojson import Feature, FeatureCollection
1515
from pydantic import BaseModel
1616

1717
from ohsome_quality_analyst.base.layer import LayerData
1818
from ohsome_quality_analyst.definitions import (
19-
INDICATOR_LAYER,
2019
get_dataset_names,
2120
get_fid_fields,
2221
get_indicator_names,
2322
get_layer_keys,
2423
get_report_names,
24+
get_valid_layers,
2525
)
2626
from ohsome_quality_analyst.utils.helper import loads_geojson, snake_to_lower_camel
2727

@@ -36,6 +36,14 @@ class BaseIndicator(BaseModel):
3636
name: IndicatorEnum = pydantic.Field(
3737
..., title="Indicator Name", example="GhsPopComparisonBuildings"
3838
)
39+
threshholds: Optional[
40+
Tuple[
41+
Union[float, str],
42+
Union[str, float],
43+
Union[str, float],
44+
Union[str, float],
45+
]
46+
] = None
3947
include_svg: bool = False
4048
include_html: bool = False
4149
include_data: bool = False
@@ -48,6 +56,17 @@ class Config:
4856
allow_mutation = False
4957
extra = "forbid"
5058

59+
@pydantic.root_validator
60+
@classmethod
61+
def validate_thresholds(cls, values):
62+
if values["threshholds"] is not None and values["name"] != "Currentness":
63+
raise ValueError(
64+
"Setting custom threshholds is only supported for the Currentness "
65+
+ "Indicator.",
66+
)
67+
else:
68+
return values
69+
5170

5271
class BaseReport(BaseModel):
5372
name: ReportEnum = pydantic.Field(
@@ -131,13 +150,13 @@ class IndicatorBpolys(BaseIndicator, BaseLayerName, BaseBpolys):
131150
@pydantic.root_validator
132151
@classmethod
133152
def validate_indicator_layer(cls, values):
134-
try:
135-
indicator_layer = (values["name"].value, values["layer_key"].value)
136-
except KeyError:
137-
raise ValueError("An issue with the layer or indicator name occurred.")
138-
if indicator_layer not in INDICATOR_LAYER:
153+
indicator_key = values["name"].value
154+
layer_key = values["layer_key"].value
155+
if layer_key not in get_valid_layers(indicator_key):
139156
raise ValueError(
140-
"Indicator layer combination is invalid: " + str(indicator_layer)
157+
"Layer ({0}) is not available for indicator ({1})".format(
158+
layer_key, indicator_key
159+
)
141160
)
142161
else:
143162
return values
@@ -147,13 +166,13 @@ class IndicatorDatabase(BaseIndicator, BaseLayerName, BaseDatabase):
147166
@pydantic.root_validator
148167
@classmethod
149168
def validate_indicator_layer(cls, values):
150-
try:
151-
indicator_layer = (values["name"].value, values["layer_key"].value)
152-
except KeyError:
153-
raise ValueError("An issue with the layer or indicator name occurred.")
154-
if indicator_layer not in INDICATOR_LAYER:
169+
indicator_key = values["name"].value
170+
layer_key = values["layer_key"].value
171+
if layer_key not in get_valid_layers(indicator_key):
155172
raise ValueError(
156-
"Indicator layer combination is invalid: " + str(indicator_layer)
173+
"Layer ({0}) is not available for indicator ({1})".format(
174+
layer_key, indicator_key
175+
)
157176
)
158177
else:
159178
return values

workers/ohsome_quality_analyst/base/indicator.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import asdict, dataclass
66
from datetime import datetime, timezone
77
from io import StringIO
8-
from typing import Dict, Literal, Optional
8+
from typing import Dict, Literal, Optional, Tuple
99

1010
import matplotlib.pyplot as plt
1111
from dacite import from_dict
@@ -42,8 +42,8 @@ class Result:
4242
value is determined by the result classes
4343
value (float): The result value
4444
class_ (int): The result class. An integer between 1 and 5. It maps to the
45-
result labels. This value is used by the reports to determine an overall
46-
result.
45+
result labels (1 -> red; 5 -> green). This value is used by the reports to
46+
determine an overall result.
4747
description (str): The result description.
4848
svg (str): Figure of the result as SVG
4949
"""
@@ -63,17 +63,32 @@ def label(self) -> Literal["green", "yellow", "red", "undefined"]:
6363

6464

6565
class BaseIndicator(metaclass=ABCMeta):
66-
"""The base class of every indicator."""
66+
"""The base class of every indicator.
67+
68+
Attributes:
69+
thresholds (tuple): A tuple with four float values representing the thresholds
70+
between the result classes. The first element is the threshold between the
71+
result class 1 and 2, the second element is the threshold between the result
72+
class 2 and 3 and so on.
73+
"""
6774

6875
def __init__(
6976
self,
7077
layer: Layer,
7178
feature: Feature,
79+
thresholds: Optional[Tuple[float, float, float, float]] = None,
7280
) -> None:
7381
self.layer: Layer = layer
7482
self.feature: Feature = feature
83+
7584
# setattr(object, key, value) could be used instead of relying on from_dict.
7685
metadata = get_metadata("indicators", type(self).__name__)
86+
layer_thresholds = metadata.pop("layer-thresholds")
87+
if thresholds is None:
88+
# TODO: filter layer_thresholds
89+
self.thresholds = layer_thresholds[layer.key]
90+
else:
91+
self.thresholds = thresholds
7792
self.metadata: Metadata = from_dict(data_class=Metadata, data=metadata)
7893
self.result: Result = Result(
7994
description=self.metadata.label_description["undefined"],

workers/ohsome_quality_analyst/cli/cli.py

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,7 @@
1515
)
1616
from ohsome_quality_analyst.cli import options
1717
from ohsome_quality_analyst.config import configure_logging, get_config_value
18-
from ohsome_quality_analyst.definitions import (
19-
INDICATOR_LAYER,
20-
load_layer_definitions,
21-
load_metadata,
22-
)
18+
from ohsome_quality_analyst.definitions import load_layer_definitions, load_metadata
2319
from ohsome_quality_analyst.geodatabase import client as db_client
2420
from ohsome_quality_analyst.utils.helper import json_serialize, write_geojson
2521

@@ -93,13 +89,6 @@ def get_available_regions():
9389
click.echo(format_row.format(region["ogc_fid"], region["name"]))
9490

9591

96-
@cli.command("list-indicator-layer-combination")
97-
def get_indicator_layer_combination():
98-
"""List all possible indicator-layer-combinations."""
99-
for combination in INDICATOR_LAYER:
100-
click.echo(combination)
101-
102-
10392
@cli.command("create-indicator")
10493
@cli_option(options.indicator_name)
10594
@cli_option(options.layer_key)

workers/ohsome_quality_analyst/definitions.py

Lines changed: 15 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
"""Global Variables and Functions."""
2+
from __future__ import annotations
3+
24
import glob
35
import logging
46
import os
57
from dataclasses import dataclass
68
from types import MappingProxyType
7-
from typing import Dict, List, Optional
9+
from typing import Dict, List, Literal, Optional, Tuple
810

911
import yaml
1012

@@ -57,60 +59,6 @@ class RasterDataset:
5759
),
5860
)
5961

60-
# Possible indicator layer combinations
61-
INDICATOR_LAYER = (
62-
("BuildingCompleteness", "building_area"),
63-
("GhsPopComparisonBuildings", "building_count"),
64-
("GhsPopComparisonRoads", "jrc_road_length"),
65-
("GhsPopComparisonRoads", "major_roads_length"),
66-
("MappingSaturation", "building_count"),
67-
("MappingSaturation", "major_roads_length"),
68-
("MappingSaturation", "amenities"),
69-
("MappingSaturation", "jrc_health_count"),
70-
("MappingSaturation", "jrc_mass_gathering_sites_count"),
71-
("MappingSaturation", "jrc_railway_length"),
72-
("MappingSaturation", "jrc_road_length"),
73-
("MappingSaturation", "jrc_education_count"),
74-
("MappingSaturation", "mapaction_settlements_count"),
75-
("MappingSaturation", "mapaction_major_roads_length"),
76-
("MappingSaturation", "mapaction_rail_length"),
77-
("MappingSaturation", "mapaction_lakes_area"),
78-
("MappingSaturation", "mapaction_rivers_length"),
79-
("MappingSaturation", "ideal_vgi_infrastructure"),
80-
("MappingSaturation", "poi"),
81-
("MappingSaturation", "lulc"),
82-
("Currentness", "major_roads_count"),
83-
("Currentness", "building_count"),
84-
("Currentness", "amenities"),
85-
("Currentness", "jrc_health_count"),
86-
("Currentness", "jrc_education_count"),
87-
("Currentness", "jrc_road_count"),
88-
("Currentness", "jrc_railway_count"),
89-
("Currentness", "jrc_airport_count"),
90-
("Currentness", "jrc_water_treatment_plant_count"),
91-
("Currentness", "jrc_power_generation_plant_count"),
92-
("Currentness", "jrc_cultural_heritage_site_count"),
93-
("Currentness", "jrc_bridge_count"),
94-
("Currentness", "jrc_mass_gathering_sites_count"),
95-
("Currentness", "mapaction_settlements_count"),
96-
("Currentness", "mapaction_major_roads_length"),
97-
("Currentness", "mapaction_rail_length"),
98-
("Currentness", "mapaction_lakes_count"),
99-
("Currentness", "mapaction_rivers_length"),
100-
("PoiDensity", "poi"),
101-
("TagsRatio", "building_count"),
102-
("TagsRatio", "major_roads_length"),
103-
("TagsRatio", "jrc_health_count"),
104-
("TagsRatio", "jrc_education_count"),
105-
("TagsRatio", "jrc_road_length"),
106-
("TagsRatio", "jrc_airport_count"),
107-
("TagsRatio", "jrc_power_generation_plant_count"),
108-
("TagsRatio", "jrc_cultural_heritage_site_count"),
109-
("TagsRatio", "jrc_bridge_count"),
110-
("TagsRatio", "jrc_mass_gathering_sites_count"),
111-
("Minimal", "minimal"),
112-
)
113-
11462
ATTRIBUTION_TEXTS = MappingProxyType(
11563
{
11664
"OSM": "© OpenStreetMap contributors",
@@ -125,17 +73,16 @@ class RasterDataset:
12573
)
12674

12775

128-
def load_metadata(module_name: str) -> Dict:
129-
"""Read metadata of all indicators or reports from YAML files.
76+
def load_metadata(module_name: Literal["indicators", "reports"]) -> Dict:
77+
"""Load metadata of all indicators or reports from YAML files.
13078
131-
Those text files are located in the directory of each indicator/report.
79+
The YAML files are located in the directory of each individual indicator or report.
13280
133-
Args:
134-
module_name: Either indicators or reports.
13581
Returns:
136-
A Dict with the class names of the indicators/reports
137-
as keys and metadata as values.
82+
A dictionary with the indicator or report keys as directory keys and the content
83+
of the YAML file (metadata) as values.
13884
"""
85+
# TODO: Is this check needed if Literal is used in func declaration?
13986
if module_name != "indicators" and module_name != "reports":
14087
raise ValueError("module name value can only be 'indicators' or 'reports'.")
14188

@@ -272,11 +219,15 @@ def get_attribution(data_keys: list) -> str:
272219
return "; ".join([str(v) for v in filtered.values()])
273220

274221

222+
# TODO
275223
def get_valid_layers(indcator_name: str) -> tuple:
276224
"""Get valid Indicator/Layer combination of an Indicator."""
277-
return tuple([tup[1] for tup in INDICATOR_LAYER if tup[0] == indcator_name])
225+
return tuple(
226+
[tup[1] for tup in INDICATOR_LAYER_THRESHOLDS if tup[0] == indcator_name]
227+
)
278228

279229

230+
# TODO
280231
def get_valid_indicators(layer_key: str) -> tuple:
281232
"""Get valid Indicator/Layer combination of a Layer."""
282-
return tuple([tup[0] for tup in INDICATOR_LAYER if tup[1] == layer_key])
233+
return tuple([tup[0] for tup in INDICATOR_LAYER_THRESHOLDS if tup[1] == layer_key])

workers/ohsome_quality_analyst/indicators/building_completeness/indicator.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import os
33
from io import StringIO
44
from string import Template
5+
from typing import Optional, Tuple
56

67
import dateutil.parser
78
import geojson
@@ -58,10 +59,14 @@ def __init__(
5859
self,
5960
layer: Layer,
6061
feature: Feature,
62+
thresholds: Optional[Tuple[float, float, float, float]],
6163
) -> None:
64+
if thresholds is None:
65+
thresholds = (0.2, 0.5, 0.8, 0.9)
6266
super().__init__(
6367
layer=layer,
6468
feature=feature,
69+
thresholds=thresholds,
6570
)
6671
self.model_name: str = "Random Forest Regressor"
6772
# Lists of elements per hexagonal cell

workers/ohsome_quality_analyst/indicators/building_completeness/metadata.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ BuildingCompleteness:
2323
average of the ratios per hex-cell between the building area mapped in OSM and the
2424
predicted building area is $completeness_ratio %. The weight is the
2525
predicted building area.
26+
layer-thresholds:
27+
- { layer: building_area, thresholds: [0.2, 0.5, 0.8, 0.9] }

workers/ohsome_quality_analyst/indicators/currentness/metadata.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,7 @@ Currentness:
2626
bad or if there is just nothing to map here.
2727
result_description: |
2828
Over 50% of the $elements features ($layer_name) were edited $years.
29+
layer-thresholds:
30+
- { layer: amenities, thresholds: [ 0.2, null, 0.6, null] }
31+
- { layer: building_count, thresholds: [ 0.2, null, 0.6, null] }
32+
- { layer: major_roads_count, thresholds: [ 0.2, null, 0.6, null] }

workers/ohsome_quality_analyst/indicators/ghs_pop_comparison_buildings/metadata.yaml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ GhsPopComparisonBuildings:
2323
$pop_count people living in an area of
2424
$area sqkm, which results in a population density
2525
$pop_count_per_sqkm of people per sqkm.
26-
$feature_count_per_sqkm buildings per sqkm mapped.
26+
$feature_count_per_sqkm buildings per sqkm mapped.
27+
layer-thresholds:
28+
- { layer: building_count, thresholds: [{ a:0.75 }, null, { a: 5.0 }, null] }

workers/ohsome_quality_analyst/indicators/ghs_pop_comparison_roads/metadata.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ GhsPopComparisonRoads:
2424
$area sqkm, which results in a population density
2525
$pop_count_per_sqkm of people per sqkm.
2626
$feature_length_per_sqkm km of roads per sqkm mapped.
27+
layer-thresholds:
28+
- { layer: major_roads_length, thresholds: [{ a: 1000 }, null, { a: 500 }, null] }

workers/ohsome_quality_analyst/indicators/mapping_saturation/metadata.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ MappingSaturation:
1515
Saturation could not be calculated.
1616
result_description: |
1717
The saturation of the last 3 years is $saturation%.
18+
layer-thresholds:
19+
- { layer: amenities, thresholds: [0.3, null, 0.97, null] }
20+
- { layer: amenities, thresholds: [0.3, null, 0.97, null] }
21+
- { layer: building_count, thresholds: [0.3, null, 0.97, null] }
22+
- { layer: ideal_vgi_infrastructure, thresholds: [0.3, null, 0.97, null] }
23+
- { layer: lulc, thresholds: [0.3, null, 0.97, null] }
24+
- { layer: major_roads_length, thresholds: [0.3, null, 0.97, null] }
25+
- { layer: poi, thresholds: [0.3, null, 0.97, null] }

workers/ohsome_quality_analyst/indicators/minimal/indicator.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010

1111

1212
class Minimal(BaseIndicator):
13-
def __init__(self, layer: Layer, feature: Feature) -> None:
14-
super().__init__(layer=layer, feature=feature)
13+
def __init__(self, layer: Layer, feature: Feature, thresholds: tuple) -> None:
14+
super().__init__(layer=layer, feature=feature, thresholds=thresholds)
1515
self.count = 0
1616

1717
async def preprocess(self) -> None:

0 commit comments

Comments
 (0)