|
1 | 1 | import logging
|
| 2 | +import time |
2 | 3 | from string import Template
|
3 | 4 |
|
4 |
| -import dateutil.parser |
5 | 5 | import plotly.graph_objects as go
|
| 6 | +import requests |
6 | 7 | from geojson import Feature
|
| 8 | +from shapely import to_wkt |
| 9 | +from shapely.geometry import shape |
7 | 10 |
|
8 | 11 | from ohsome_quality_api.attributes.definitions import (
|
9 | 12 | build_attribute_filter,
|
10 | 13 | get_attribute,
|
11 | 14 | )
|
12 | 15 | from ohsome_quality_api.indicators.base import BaseIndicator
|
13 |
| -from ohsome_quality_api.ohsome import client as ohsome_client |
14 | 16 | from ohsome_quality_api.topics.models import BaseTopic as Topic
|
15 | 17 |
|
16 | 18 |
|
@@ -69,17 +71,143 @@ def __init__(
|
69 | 71 | )
|
70 | 72 |
|
71 | 73 | async def preprocess(self) -> None:
|
72 |
| - # Get attribute filter |
73 |
| - response = await ohsome_client.query( |
74 |
| - self.topic, |
75 |
| - self.feature, |
76 |
| - attribute_filter=self.attribute_filter, |
| 74 | + |
| 75 | + TRINO_HOST = "" |
| 76 | + TRINO_PORT = |
| 77 | + TRINO_USER = "" |
| 78 | + TRINO_CATALOG = "" |
| 79 | + TRINO_SCHEMA = "" |
| 80 | + |
| 81 | + URL = f"http://{TRINO_HOST}:{TRINO_PORT}/v1/statement" |
| 82 | + |
| 83 | + HEADERS = { |
| 84 | + "X-Trino-User": TRINO_USER, |
| 85 | + "X-Trino-Catalog": TRINO_CATALOG, |
| 86 | + "X-Trino-Schema": TRINO_SCHEMA, |
| 87 | + } |
| 88 | + |
| 89 | + AUTH = None |
| 90 | + |
| 91 | + QUERY_TEMPLATE = """ |
| 92 | +SELECT |
| 93 | + SUM( |
| 94 | + CASE |
| 95 | + WHEN ST_Within(ST_GeometryFromText(a.geometry), b.geometry) THEN length |
| 96 | + ELSE CAST(st_length(ST_Intersection(ST_GeometryFromText(a.geometry), b.geometry)) AS integer) |
| 97 | + END |
| 98 | + ) AS total_road_length, |
| 99 | + |
| 100 | + SUM( |
| 101 | + CASE |
| 102 | + WHEN element_at(tags, 'name') IS NULL THEN 0 |
| 103 | + WHEN ST_Within(ST_GeometryFromText(a.geometry), b.geometry) THEN length |
| 104 | + ELSE CAST(st_length(ST_Intersection(ST_GeometryFromText(a.geometry), b.geometry)) AS integer) |
| 105 | + END |
| 106 | + ) AS total_road_length_with_name, |
| 107 | +
|
| 108 | + ( |
| 109 | + SUM( |
| 110 | + CASE |
| 111 | + WHEN element_at(tags, 'name') IS NULL THEN 0 |
| 112 | + WHEN ST_Within(ST_GeometryFromText(a.geometry), b.geometry) THEN length |
| 113 | + ELSE CAST(st_length(ST_Intersection(ST_GeometryFromText(a.geometry), b.geometry)) AS integer) |
| 114 | + END |
| 115 | + ) |
| 116 | + / |
| 117 | + SUM( |
| 118 | + CASE |
| 119 | + WHEN ST_Within(ST_GeometryFromText(a.geometry), b.geometry) THEN length |
| 120 | + ELSE CAST(st_length(ST_Intersection(ST_GeometryFromText(a.geometry), b.geometry)) AS integer) |
| 121 | + END |
77 | 122 | )
|
78 |
| - timestamp = response["ratioResult"][0]["timestamp"] |
79 |
| - self.result.timestamp_osm = dateutil.parser.isoparse(timestamp) |
80 |
| - self.result.value = response["ratioResult"][0]["ratio"] |
81 |
| - self.absolute_value_1 = response["ratioResult"][0]["value"] |
82 |
| - self.absolute_value_2 = response["ratioResult"][0]["value2"] |
| 123 | + ) AS ratio |
| 124 | +
|
| 125 | +FROM contributions a, (VALUES {aoi_values}) AS b(id, geometry) |
| 126 | +WHERE 'herfort' != 'kwakye' |
| 127 | + AND status = 'latest' |
| 128 | + AND element_at(a.tags, 'highway') IS NOT NULL |
| 129 | + AND a.tags['highway'] IN ( |
| 130 | + 'motorway', 'trunk', 'motorway_link', 'trunk_link', 'primary', 'primary_link', |
| 131 | + 'secondary', 'secondary_link', 'tertiary', 'tertiary_link', 'unclassified', 'residential' |
| 132 | + ) |
| 133 | + AND (bbox.xmax >= 8.629761 AND bbox.xmin <= 8.742371) |
| 134 | + AND (bbox.ymax >= 49.379556 AND bbox.ymin <= 49.437890) |
| 135 | + AND ST_Intersects(ST_GeometryFromText(a.geometry), b.geometry) |
| 136 | +GROUP BY b.id |
| 137 | + """ |
| 138 | + |
| 139 | + def extract_geometry(feature): |
| 140 | + geometry = feature.get("geometry") |
| 141 | + if not geometry: |
| 142 | + raise ValueError("Feature does not contain a geometry") |
| 143 | + geom_shape = shape(geometry) |
| 144 | + return to_wkt(geom_shape) |
| 145 | + |
| 146 | + def format_aoi_values(geom_wkt): |
| 147 | + return f"('AOI', ST_GeometryFromText('{geom_wkt}'))" |
| 148 | + |
| 149 | + def execute_query(query): |
| 150 | + try: |
| 151 | + response = requests.post(URL, data=query, headers=HEADERS, auth=AUTH) |
| 152 | + response.raise_for_status() |
| 153 | + return response.json() |
| 154 | + except requests.exceptions.RequestException as e: |
| 155 | + print(f"Error submitting query: {e}") |
| 156 | + return None |
| 157 | + |
| 158 | + def poll_query(next_uri): |
| 159 | + """Poll the query's nextUri until results are ready.""" |
| 160 | + results = [] |
| 161 | + while next_uri: |
| 162 | + try: |
| 163 | + response = requests.get(next_uri, headers=HEADERS, auth=AUTH) |
| 164 | + response.raise_for_status() |
| 165 | + data = response.json() |
| 166 | + |
| 167 | + state = data["stats"]["state"] |
| 168 | + print(f"Query state: {state}") |
| 169 | + |
| 170 | + if state == "FINISHED": |
| 171 | + if "data" in data: |
| 172 | + results.extend(data["data"]) |
| 173 | + print("Query completed successfully!") |
| 174 | + break |
| 175 | + elif state in {"FAILED", "CANCELLED"}: |
| 176 | + print(f"Query failed or was cancelled: {data}") |
| 177 | + break |
| 178 | + |
| 179 | + next_uri = data.get("nextUri") |
| 180 | + except requests.exceptions.RequestException as e: |
| 181 | + print(f"Error polling query: {e}") |
| 182 | + break |
| 183 | + time.sleep(1) |
| 184 | + |
| 185 | + return results |
| 186 | + |
| 187 | + |
| 188 | + geom_wkt = extract_geometry(self.feature) |
| 189 | + |
| 190 | + aoi_values = format_aoi_values(geom_wkt) |
| 191 | + |
| 192 | + query = QUERY_TEMPLATE.format(aoi_values=aoi_values) |
| 193 | + |
| 194 | + initial_response = execute_query(query) |
| 195 | + if not initial_response: |
| 196 | + return |
| 197 | + next_uri = initial_response.get("nextUri") |
| 198 | + if not next_uri: |
| 199 | + print("No nextUri found. Query might have failed immediately.") |
| 200 | + print(initial_response) |
| 201 | + return |
| 202 | + |
| 203 | + response = poll_query(next_uri) |
| 204 | + |
| 205 | + |
| 206 | + # timestamp = response["ratioResult"][0]["timestamp"] |
| 207 | + # self.result.timestamp_osm = dateutil.parser.isoparse(timestamp) |
| 208 | + self.absolute_value_1 = response[0][0] |
| 209 | + self.absolute_value_2 = response[0][1] |
| 210 | + self.result.value = self.absolute_value_2 / self.absolute_value_1 |
83 | 211 |
|
84 | 212 | def calculate(self) -> None:
|
85 | 213 | # result (ratio) can be NaN if no features matching filter1
|
|
0 commit comments