diff --git a/api/migrations/versions/a049e5eb24dd_create_table_region.py b/api/migrations/versions/a049e5eb24dd_create_table_region.py new file mode 100644 index 0000000..e5dcf7f --- /dev/null +++ b/api/migrations/versions/a049e5eb24dd_create_table_region.py @@ -0,0 +1,36 @@ +"""create table region + +Revision ID: a049e5eb24dd +Revises: a9627f63fbed +Create Date: 2022-04-02 21:28:43.124521 + +""" +from alembic import op +import sqlalchemy as sa + +from migrations.utils import dbtype + + +# revision identifiers, used by Alembic. +revision = "a049e5eb24dd" +down_revision = "a9627f63fbed" +branch_labels = None +depends_on = None + + +def upgrade(): + op.create_table( + "region", + sa.Column( + "way_id", sa.BIGINT, autoincrement=True, primary_key=True, index=True + ), + sa.Column("zone", dbtype("zone_type")), + sa.Column("name", sa.String), + sa.Column("geometry", dbtype("GEOMETRY"), index=True), + sa.Column("directionality", sa.Integer), + sa.Column("oenway", sa.Boolean), + ) + + +def downgrade(): + op.drop_table("region") diff --git a/api/obs/api/routes/frontend.py b/api/obs/api/routes/frontend.py index fb681a0..7ccaa70 100644 --- a/api/obs/api/routes/frontend.py +++ b/api/obs/api/routes/frontend.py @@ -26,7 +26,7 @@ if app.config.FRONTEND_CONFIG: .replace("111", "{x}") .replace("222", "{y}") ], - "minzoom": 12, + "minzoom": 0, "maxzoom": 14, } ), diff --git a/frontend/config.example.json b/frontend/config.example.json index 6566c6e..2918934 100644 --- a/frontend/config.example.json +++ b/frontend/config.example.json @@ -12,7 +12,7 @@ "obsMapSource": { "type": "vector", "tiles": ["https://portal.example.com/tiles/{z}/{x}/{y}.pbf"], - "minzoom": 12, + "minzoom": 0, "maxzoom": 14 } } diff --git a/frontend/src/components/ColorMapLegend.tsx b/frontend/src/components/ColorMapLegend.tsx index f0c4d48..ca09860 100644 --- a/frontend/src/components/ColorMapLegend.tsx +++ b/frontend/src/components/ColorMapLegend.tsx @@ -59,7 +59,7 @@ export function DiscreteColorMapLegend({map}: {map: ColorMap}) { ) } -export default function ColorMapLegend({map, twoTicks = false}: {map: ColorMap, twoTicks?: boolean}) { +export default function ColorMapLegend({map, twoTicks = false, digits=2}: {map: ColorMap, twoTicks?: boolean, digits?: number}) { const min = map[0][0] const max = map[map.length - 1][0] const normalizeValue = (v) => (v - min) / (max - min) @@ -81,7 +81,7 @@ export default function ColorMapLegend({map, twoTicks = false}: {map: ColorMap, {tickValues.map(([value]) => ( - {value.toFixed(2)} + {value.toFixed(digits)} ))} diff --git a/frontend/src/mapstyles/index.js b/frontend/src/mapstyles/index.js index 30067d0..fbdad52 100644 --- a/frontend/src/mapstyles/index.js +++ b/frontend/src/mapstyles/index.js @@ -124,6 +124,58 @@ export const trackLayer = { }, } +export const getRegionLayers = (adminLevel = 6, baseColor = "#00897B", maxValue = 5000) => [{ + id: 'region', + "type": "fill", + "source": "obs", + "source-layer": "obs_regions", + "minzoom": 0, + "maxzoom": 10, + "filter": [ + "all", + ["==", "admin_level", adminLevel] + ], + "paint": { + "fill-color": baseColor, + "fill-antialias": true, + "fill-opacity": [ + "interpolate", + ["linear"], + [ + "log10", + [ + "get", + "overtaking_event_count" + ] + ], + 0, + 0, + Math.log10(maxValue), + 0.9 + ] + }, +}, +{ + id: 'region-border', + "type": "line", + "source": "obs", + "source-layer": "obs_regions", + "minzoom": 0, + "maxzoom": 10, + "filter": [ + "all", + ["==", "admin_level", adminLevel] + ], + "paint": { + "line-width": 1, + "line-color": baseColor, + }, + "layout": { + "line-join": "round", + "line-cap": "round" + } +}] + export const trackLayerRaw = produce(trackLayer, draft => { // draft.paint['line-color'] = '#81D4FA' draft.paint['line-width'][4] = 1 diff --git a/frontend/src/pages/MapPage/LayerSidebar.tsx b/frontend/src/pages/MapPage/LayerSidebar.tsx index 7da55ce..87e30b2 100644 --- a/frontend/src/pages/MapPage/LayerSidebar.tsx +++ b/frontend/src/pages/MapPage/LayerSidebar.tsx @@ -50,6 +50,7 @@ function LayerSidebar({ baseMap: { style }, obsRoads: { show: showRoads, showUntagged, attribute, maxCount }, obsEvents: { show: showEvents }, + obsRegions: {show: showRegions}, filters: { currentUser: filtersCurrentUser, dateMode, @@ -57,7 +58,7 @@ function LayerSidebar({ endDate, thresholdAfter, }, - } = mapConfig; + } = mapConfig return (
@@ -77,6 +78,28 @@ function LayerSidebar({ /> + + setMapConfigFlag('obsRegions.show', !showRegions)} + /> + + + {showRegions && ( + <> + Color regions based on event count + + + + + )} + produce(untaggedRoadsLayer, (draft) => { draft.id = "obs_roads_normal"; draft.filter = isValidAttribute(colorAttribute); + draft.minzoom = 10 draft.paint["line-width"][6] = 6; // scale bigger on zoom draft.paint["line-color"] = colorAttribute.startsWith("distance_") ? colorByDistance(colorAttribute) @@ -66,8 +61,8 @@ const getRoadsLayer = (colorAttribute, maxCount) => : colorAttribute.endsWith("zone") ? borderByZone() : "#DDD"; - draft.paint["line-opacity"][3] = 12; - draft.paint["line-opacity"][5] = 13; + // draft.paint["line-opacity"][3] = 12; + // draft.paint["line-opacity"][5] = 13; }); const getEventsLayer = () => ({ @@ -162,6 +157,9 @@ function MapPage({ login }) { layers.push(roadsLayer); } + const regionLayers = useMemo(() => getRegionLayers(), []) + layers.push(...regionLayers) + const eventsLayer = useMemo(() => getEventsLayer(), []); const eventsTextLayer = useMemo(() => getEventsTextLayer(), []); if (mapConfig.obsEvents.show) { diff --git a/frontend/src/reducers/mapConfig.ts b/frontend/src/reducers/mapConfig.ts index 2ec5398..a597fbb 100644 --- a/frontend/src/reducers/mapConfig.ts +++ b/frontend/src/reducers/mapConfig.ts @@ -27,6 +27,9 @@ export type MapConfig = { obsEvents: { show: boolean; }; + obsRegions: { + show: boolean; + }; filters: { currentUser: boolean; dateMode: "none" | "range" | "threshold"; @@ -49,6 +52,9 @@ export const initialState: MapConfig = { obsEvents: { show: false, }, + obsRegions: { + show: true, + }, filters: { currentUser: false, dateMode: "none", diff --git a/roads_import.lua b/roads_import.lua index 2c1c45b..adeb663 100644 --- a/roads_import.lua +++ b/roads_import.lua @@ -50,6 +50,9 @@ local MOTORWAY_TYPES = { "motorway_link", } +local ADMIN_LEVEL_MIN = 2 +local ADMIN_LEVEL_MAX = 8 + local ONEWAY_YES = {"yes", "true", "1"} local ONEWAY_REVERSE = {"reverse", "-1"} @@ -61,6 +64,13 @@ local roads = osm2pgsql.define_way_table('road', { { column = 'oneway', type = 'bool' }, }) +local regions = osm2pgsql.define_relation_table('region', { + { column = 'name', type = 'text' }, + { column = 'geometry', type = 'geometry' }, + { column = 'admin_level', type = 'int' }, + { column = 'tags', type = 'hstore' }, +}) + local minspeed_rural = 60 function osm2pgsql.process_way(object) @@ -131,3 +141,21 @@ function osm2pgsql.process_way(object) }) end end + +function osm2pgsql.process_relation(object) + local admin_level = tonumber(object.tags.admin_level) + if object.tags.boundary == "administrative" and admin_level and admin_level >= ADMIN_LEVEL_MIN and admin_level <= ADMIN_LEVEL_MAX then + regions:add_row({ + geometry = { create = 'area' }, + name = object.tags.name, + admin_level = admin_level, + tags = object.tags, + }) + end +end + +function osm2pgsql.select_relation_members(relation) + if relation.tags.type == 'route' then + return { ways = osm2pgsql.way_member_ids(relation) } + end +end diff --git a/tile-generator/layers/obs_events/layer.sql b/tile-generator/layers/obs_events/layer.sql index 574d67c..6c4c5cd 100644 --- a/tile-generator/layers/obs_events/layer.sql +++ b/tile-generator/layers/obs_events/layer.sql @@ -14,7 +14,9 @@ RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, dist FROM overtaking_event FULL OUTER JOIN road ON road.way_id = overtaking_event.way_id JOIN track on track.id = overtaking_event.track_id - WHERE ST_Transform(overtaking_event.geometry, 3857) && bbox + WHERE + zoom_level >= 10 AND + ST_Transform(overtaking_event.geometry, 3857) && bbox AND (user_id is NULL OR user_id = track.author_id) AND time BETWEEN COALESCE(min_time, '1900-01-01'::timestamp) AND COALESCE(max_time, '2100-01-01'::timestamp); diff --git a/tile-generator/layers/obs_regions/layer.sql b/tile-generator/layers/obs_regions/layer.sql new file mode 100644 index 0000000..fda2a44 --- /dev/null +++ b/tile-generator/layers/obs_regions/layer.sql @@ -0,0 +1,26 @@ +DROP FUNCTION IF EXISTS layer_obs_regions(geometry, int); + +CREATE OR REPLACE FUNCTION layer_obs_regions(bbox geometry, zoom_level int) +RETURNS TABLE( + region_id bigint, + geometry geometry, + name text, + admin_level int, + overtaking_event_count int +) AS $$ + + SELECT + region.relation_id::bigint as region_id, + ST_SimplifyPreserveTopology(region.geometry, ZRes(zoom_level + 2)) as geometry, + region.name as name, + region.admin_level as admin_level, + count(overtaking_event.id)::int as overtaking_event_count + FROM region + LEFT JOIN overtaking_event on ST_Within(ST_Transform(overtaking_event.geometry, 3857), region.geometry) + WHERE + zoom_level >= 4 AND + zoom_level <= 12 AND + ST_Transform(region.geometry, 3857) && bbox + GROUP BY region.relation_id, region.name, region.geometry, region.admin_level + +$$ LANGUAGE SQL IMMUTABLE; diff --git a/tile-generator/layers/obs_regions/obs_regions.yaml b/tile-generator/layers/obs_regions/obs_regions.yaml new file mode 100644 index 0000000..477bf45 --- /dev/null +++ b/tile-generator/layers/obs_regions/obs_regions.yaml @@ -0,0 +1,23 @@ +layer: + id: "obs_regions" + description: | + Statistics on administrative boundary areas ("regions") + buffer_size: 4 + fields: + overtaking_event_count: | + Number of overtaking events. + name: | + Name of the region + admin_level: | + Administrative level of the boundary, as tagged in OpenStreetMap + defaults: + srs: EPSG:3785 + datasource: + srid: 3857 + geometry_field: geometry + key_field: region_id + key_field_as_attribute: no + query: (SELECT region_id, geometry, name, admin_level, overtaking_event_count FROM layer_obs_regions(!bbox!, z(!scale_denominator!))) AS t + +schema: + - ./layer.sql diff --git a/tile-generator/layers/obs_roads/layer.sql b/tile-generator/layers/obs_roads/layer.sql index 2073150..391bfee 100644 --- a/tile-generator/layers/obs_roads/layer.sql +++ b/tile-generator/layers/obs_roads/layer.sql @@ -66,7 +66,9 @@ RETURNS TABLE( GROUP BY overtaking_event.way_id, overtaking_event.direction_reversed ) e on (e.way_id = road.way_id and (road.directionality != 0 or e.direction_reversed = r.rev)) - WHERE road.geometry && bbox + WHERE + zoom_level >= 10 AND + road.geometry && bbox GROUP BY road.name, road.way_id, diff --git a/tile-generator/openbikesensor.yaml b/tile-generator/openbikesensor.yaml index d3a7a65..3aedd7e 100644 --- a/tile-generator/openbikesensor.yaml +++ b/tile-generator/openbikesensor.yaml @@ -3,6 +3,7 @@ tileset: layers: - layers/obs_events/obs_events.yaml - layers/obs_roads/obs_roads.yaml + - layers/obs_regions/obs_regions.yaml version: 0.7.0 id: openbikesensor description: >