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