This example shows how the weather station mapper can be further simplified using the enhanced mapping system.
config.yaml:200-286)- name: "weather_station_bright_sky"
enable: true
class_name: weatherStation
mapping:
enable: true
joins_on: dwd_station_id
strategy:
type: knn
description: map each base way to its nearest weather station
link_on:
mapping_column: dwd_station_id
base_column: dwd_station_id
basis: nearest_by_distance
config:
base_geometry_column: geometry
enrichment_geometry_column: point
distance_sql: ST_Distance({base_geometry}::geography, {enrichment_geometry}::geography)
order_by_sql: ST_Distance({base_geometry}::geography, {enrichment_geometry}::geography)
select_columns:
- expression: |
MOD(
(DEGREES(
ST_Azimuth(
ST_StartPoint({base_geometry}),
ST_EndPoint({base_geometry})
)
) + 360)::NUMERIC,
360
)
alias: bearing_degree
table_name: dwd_station_locations_mapping
table_schema: test_osm_base_graph
weatherStationMapper.py:78-84)def get_mapping_insert_spec(self) -> MappingInsertSpec:
self.logger.info("Mapping DWD stations to links using strategy-backed SQL builder")
return MappingInsertSpec(
columns=["way_id", "dwd_station_id", "distance", "bearing_degree"],
conflict_columns=["way_id"],
update_columns=["dwd_station_id", "distance", "bearing_degree"],
)
Issue: Still requires Python method override for insert specification.
- name: "weather_station_bright_sky"
enable: true
class_name: weatherStation
mapping:
enable: true
strategy:
type: knn
description: map each base way to its nearest weather station
link_on:
mapping_column: dwd_station_id
base_column: dwd_station_id
basis: nearest_by_distance
config:
# Geometry columns
base_geometry_column: geometry
enrichment_geometry_column: point
# Distance calculation
distance_sql: ST_Distance({base_geometry}::geography, {enrichment_geometry}::geography)
order_by_sql: ST_Distance({base_geometry}::geography, {enrichment_geometry}::geography)
# Additional computed columns
select_columns:
- expression: |
MOD(
(DEGREES(
ST_Azimuth(
ST_StartPoint({base_geometry}),
ST_EndPoint({base_geometry})
)
) + 360)::NUMERIC,
360
)
alias: bearing_degree
# Insert specification (NEW - moved from Python to config!)
insert:
columns: [way_id, dwd_station_id, distance, bearing_degree]
conflict_columns: [way_id]
update_columns: [dwd_station_id, distance, bearing_degree]
table_name: dwd_station_locations_mapping
table_schema: test_osm_base_graph
base_table:
table_name: ways_base
table_schema: test_osm_base_graph
class WeatherStationMapper(DataSourceABCImpl):
def source_filter(self, data: list[dict]) -> list[dict]:
"""Custom filter for DWD stations."""
data = data[0]["sources"]
filtered = [
row for row in data
if row.get("observation_type") == "forecast" and int(row.get("last_record")[:4]) >= 2024
]
self.logger.info(f"Filtered {len(data)} → {len(filtered)} rows")
return filtered
def enrichment_db_query(self) -> None | str:
staging = self.data_source_config.storage.staging
enrichment = self.data_source_config.storage.enrichment
sql = f"""
UPDATE {enrichment.table_schema}.{enrichment.table_name} e
SET point = ST_SetSRID(
ST_MakePoint(s.lon, s.lat),
4326
)
from {staging.table_schema}.{staging.table_name} s
WHERE e.dwd_station_id = s.dwd_station_id
AND e.point IS NULL
"""
return sql
# get_mapping_insert_spec() is NO LONGER NEEDED!
# The insert spec is now in config.yaml
Result: Removed get_mapping_insert_spec() method - everything is in config!
If you’re okay with default insert behavior (no conflict handling), you can simplify further:
config:
base_geometry_column: geometry
enrichment_geometry_column: point
distance_sql: ST_Distance({base_geometry}::geography, {enrichment_geometry}::geography)
order_by_sql: ST_Distance({base_geometry}::geography, {enrichment_geometry}::geography)
select_columns:
- expression: MOD((DEGREES(ST_Azimuth(...)))::NUMERIC, 360)
alias: bearing_degree
# No insert spec = default INSERT without conflict handling
config.insert section to weather station configget_mapping_insert_spec() from weatherStationMapper.py-- Verify mapping table has all expected columns
SELECT way_id, dwd_station_id, distance, bearing_degree
FROM test_osm_base_graph.dwd_station_locations_mapping
LIMIT 10;
-- Check distance calculations
SELECT
way_id,
dwd_station_id,
distance,
bearing_degree
FROM test_osm_base_graph.dwd_station_locations_mapping
ORDER BY distance DESC
LIMIT 10;
-- Verify no duplicate way_ids (conflict handling works)
SELECT way_id, COUNT(*)
FROM test_osm_base_graph.dwd_station_locations_mapping
GROUP BY way_id
HAVING COUNT(*) > 1;
-- Should return 0 rows
| Aspect | Before | After |
|---|---|---|
| Config lines | ~30 | ~40 |
| Python lines | ~12 | 0 |
| Total complexity | Medium | Low |
| Ease of modification | Need code change | Just config |
| Consistency | Mixed (config + code) | Pure config |
Apply this pattern to other mappers:
Goal: Eventually have all mappers use pure config-based mapping!