Add administrative boundary import and display regional event count
This commit is contained in:
parent
a3d548cd4b
commit
f429ed32f3
36
api/migrations/versions/a049e5eb24dd_create_table_region.py
Normal file
36
api/migrations/versions/a049e5eb24dd_create_table_region.py
Normal file
|
@ -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")
|
|
@ -26,7 +26,7 @@ if app.config.FRONTEND_CONFIG:
|
||||||
.replace("111", "{x}")
|
.replace("111", "{x}")
|
||||||
.replace("222", "{y}")
|
.replace("222", "{y}")
|
||||||
],
|
],
|
||||||
"minzoom": 12,
|
"minzoom": 0,
|
||||||
"maxzoom": 14,
|
"maxzoom": 14,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"obsMapSource": {
|
"obsMapSource": {
|
||||||
"type": "vector",
|
"type": "vector",
|
||||||
"tiles": ["https://portal.example.com/tiles/{z}/{x}/{y}.pbf"],
|
"tiles": ["https://portal.example.com/tiles/{z}/{x}/{y}.pbf"],
|
||||||
"minzoom": 12,
|
"minzoom": 0,
|
||||||
"maxzoom": 14
|
"maxzoom": 14
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 min = map[0][0]
|
||||||
const max = map[map.length - 1][0]
|
const max = map[map.length - 1][0]
|
||||||
const normalizeValue = (v) => (v - min) / (max - min)
|
const normalizeValue = (v) => (v - min) / (max - min)
|
||||||
|
@ -81,7 +81,7 @@ export default function ColorMapLegend({map, twoTicks = false}: {map: ColorMap,
|
||||||
</svg>
|
</svg>
|
||||||
{tickValues.map(([value]) => (
|
{tickValues.map(([value]) => (
|
||||||
<span className={styles.tick} key={value} style={{left: normalizeValue(value) * 100 + '%'}}>
|
<span className={styles.tick} key={value} style={{left: normalizeValue(value) * 100 + '%'}}>
|
||||||
{value.toFixed(2)}
|
{value.toFixed(digits)}
|
||||||
</span>
|
</span>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -77,6 +77,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 => {
|
export const trackLayerRaw = produce(trackLayer, draft => {
|
||||||
// draft.paint['line-color'] = '#81D4FA'
|
// draft.paint['line-color'] = '#81D4FA'
|
||||||
draft.paint['line-width'][4] = 1
|
draft.paint['line-width'][4] = 1
|
||||||
|
|
|
@ -36,6 +36,7 @@ function LayerSidebar({
|
||||||
baseMap: {style},
|
baseMap: {style},
|
||||||
obsRoads: {show: showRoads, showUntagged, attribute, maxCount},
|
obsRoads: {show: showRoads, showUntagged, attribute, maxCount},
|
||||||
obsEvents: {show: showEvents},
|
obsEvents: {show: showEvents},
|
||||||
|
obsRegions: {show: showRegions},
|
||||||
} = mapConfig
|
} = mapConfig
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -50,6 +51,28 @@ function LayerSidebar({
|
||||||
/>
|
/>
|
||||||
</List.Item>
|
</List.Item>
|
||||||
<Divider />
|
<Divider />
|
||||||
|
<List.Item>
|
||||||
|
<Checkbox
|
||||||
|
toggle
|
||||||
|
size="small"
|
||||||
|
id="obsRegions.show"
|
||||||
|
style={{float: 'right'}}
|
||||||
|
checked={showRegions}
|
||||||
|
onChange={() => setMapConfigFlag('obsRegions.show', !showRegions)}
|
||||||
|
/>
|
||||||
|
<label htmlFor="obsRegions.show">
|
||||||
|
<Header as="h4">Regions</Header>
|
||||||
|
</label>
|
||||||
|
</List.Item>
|
||||||
|
{showRegions && (
|
||||||
|
<>
|
||||||
|
<List.Item>Color regions based on event count</List.Item>
|
||||||
|
<List.Item>
|
||||||
|
<ColorMapLegend twoTicks map={[[0, "#00897B00"], [5000, "#00897BFF"]]} digits={0} />
|
||||||
|
</List.Item>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
<Divider />
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<Checkbox
|
<Checkbox
|
||||||
toggle
|
toggle
|
||||||
|
|
|
@ -6,7 +6,7 @@ import produce from 'immer'
|
||||||
|
|
||||||
import {Page, Map} from 'components'
|
import {Page, Map} from 'components'
|
||||||
import {useConfig} from 'config'
|
import {useConfig} from 'config'
|
||||||
import {colorByDistance, colorByCount, reds} from 'mapstyles'
|
import {colorByDistance, colorByCount, getRegionLayers} from 'mapstyles'
|
||||||
import {useMapConfig} from 'reducers/mapConfig'
|
import {useMapConfig} from 'reducers/mapConfig'
|
||||||
|
|
||||||
import RoadInfo from './RoadInfo'
|
import RoadInfo from './RoadInfo'
|
||||||
|
@ -18,6 +18,7 @@ const untaggedRoadsLayer = {
|
||||||
type: 'line',
|
type: 'line',
|
||||||
source: 'obs',
|
source: 'obs',
|
||||||
'source-layer': 'obs_roads',
|
'source-layer': 'obs_roads',
|
||||||
|
minzoom: 12,
|
||||||
filter: ['!', ['to-boolean', ['get', 'distance_overtaker_mean']]],
|
filter: ['!', ['to-boolean', ['get', 'distance_overtaker_mean']]],
|
||||||
layout: {
|
layout: {
|
||||||
'line-cap': 'round',
|
'line-cap': 'round',
|
||||||
|
@ -26,7 +27,7 @@ const untaggedRoadsLayer = {
|
||||||
paint: {
|
paint: {
|
||||||
'line-width': ['interpolate', ['exponential', 1.5], ['zoom'], 12, 2, 17, 2],
|
'line-width': ['interpolate', ['exponential', 1.5], ['zoom'], 12, 2, 17, 2],
|
||||||
'line-color': '#ABC',
|
'line-color': '#ABC',
|
||||||
'line-opacity': ['interpolate', ['linear'], ['zoom'], 14, 0, 15, 1],
|
// 'line-opacity': ['interpolate', ['linear'], ['zoom'], 14, 0, 15, 1],
|
||||||
'line-offset': [
|
'line-offset': [
|
||||||
'interpolate',
|
'interpolate',
|
||||||
['exponential', 1.5],
|
['exponential', 1.5],
|
||||||
|
@ -49,14 +50,15 @@ const getRoadsLayer = (colorAttribute, maxCount) =>
|
||||||
} else {
|
} else {
|
||||||
draft.filter = draft.filter[1] // remove '!'
|
draft.filter = draft.filter[1] // remove '!'
|
||||||
}
|
}
|
||||||
|
draft.minzoom = 10
|
||||||
draft.paint['line-width'][6] = 6 // scale bigger on zoom
|
draft.paint['line-width'][6] = 6 // scale bigger on zoom
|
||||||
draft.paint['line-color'] = colorAttribute.startsWith('distance_')
|
draft.paint['line-color'] = colorAttribute.startsWith('distance_')
|
||||||
? colorByDistance(colorAttribute)
|
? colorByDistance(colorAttribute)
|
||||||
: colorAttribute.endsWith('_count')
|
: colorAttribute.endsWith('_count')
|
||||||
? colorByCount(colorAttribute, maxCount)
|
? colorByCount(colorAttribute, maxCount)
|
||||||
: '#DDD'
|
: '#DDD'
|
||||||
draft.paint['line-opacity'][3] = 12
|
// draft.paint['line-opacity'][3] = 12
|
||||||
draft.paint['line-opacity'][5] = 13
|
// draft.paint['line-opacity'][5] = 13
|
||||||
})
|
})
|
||||||
|
|
||||||
const getEventsLayer = () => ({
|
const getEventsLayer = () => ({
|
||||||
|
@ -137,6 +139,9 @@ export default function MapPage() {
|
||||||
layers.push(roadsLayer)
|
layers.push(roadsLayer)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regionLayers = useMemo(() => getRegionLayers(), [])
|
||||||
|
layers.push(...regionLayers)
|
||||||
|
|
||||||
const eventsLayer = useMemo(() => getEventsLayer(), [])
|
const eventsLayer = useMemo(() => getEventsLayer(), [])
|
||||||
const eventsTextLayer = useMemo(() => getEventsTextLayer(), [])
|
const eventsTextLayer = useMemo(() => getEventsTextLayer(), [])
|
||||||
if (mapConfig.obsEvents.show) {
|
if (mapConfig.obsEvents.show) {
|
||||||
|
|
|
@ -26,6 +26,9 @@ export type MapConfig = {
|
||||||
obsEvents: {
|
obsEvents: {
|
||||||
show: boolean;
|
show: boolean;
|
||||||
};
|
};
|
||||||
|
obsRegions: {
|
||||||
|
show: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialState: MapConfig = {
|
export const initialState: MapConfig = {
|
||||||
|
@ -41,6 +44,9 @@ export const initialState: MapConfig = {
|
||||||
obsEvents: {
|
obsEvents: {
|
||||||
show: false,
|
show: false,
|
||||||
},
|
},
|
||||||
|
obsRegions: {
|
||||||
|
show: true,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
type MapConfigAction = {
|
type MapConfigAction = {
|
||||||
|
|
|
@ -50,6 +50,9 @@ local MOTORWAY_TYPES = {
|
||||||
"motorway_link",
|
"motorway_link",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local ADMIN_LEVEL_MIN = 2
|
||||||
|
local ADMIN_LEVEL_MAX = 8
|
||||||
|
|
||||||
local ONEWAY_YES = {"yes", "true", "1"}
|
local ONEWAY_YES = {"yes", "true", "1"}
|
||||||
local ONEWAY_REVERSE = {"reverse", "-1"}
|
local ONEWAY_REVERSE = {"reverse", "-1"}
|
||||||
|
|
||||||
|
@ -61,6 +64,14 @@ local roads = osm2pgsql.define_way_table('road', {
|
||||||
{ column = 'oneway', type = 'bool' },
|
{ 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' },
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
function osm2pgsql.process_way(object)
|
function osm2pgsql.process_way(object)
|
||||||
if object.tags.highway and contains(HIGHWAY_TYPES, object.tags.highway) then
|
if object.tags.highway and contains(HIGHWAY_TYPES, object.tags.highway) then
|
||||||
local tags = object.tags
|
local tags = object.tags
|
||||||
|
@ -112,3 +123,21 @@ function osm2pgsql.process_way(object)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
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
|
||||||
|
|
|
@ -11,6 +11,8 @@ RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, dist
|
||||||
speed,
|
speed,
|
||||||
way_id::bigint as way_id
|
way_id::bigint as way_id
|
||||||
FROM overtaking_event
|
FROM overtaking_event
|
||||||
WHERE ST_Transform(overtaking_event.geometry, 3857) && bbox;
|
WHERE
|
||||||
|
zoom_level >= 10 AND
|
||||||
|
ST_Transform(overtaking_event.geometry, 3857) && bbox;
|
||||||
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE;
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
26
tile-generator/layers/obs_regions/layer.sql
Normal file
26
tile-generator/layers/obs_regions/layer.sql
Normal file
|
@ -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;
|
23
tile-generator/layers/obs_regions/obs_regions.yaml
Normal file
23
tile-generator/layers/obs_regions/obs_regions.yaml
Normal file
|
@ -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
|
|
@ -32,7 +32,9 @@ RETURNS TABLE(
|
||||||
LEFT JOIN (VALUES (-1, TRUE), (1, FALSE), (0, FALSE)) AS r(dir, rev) ON (abs(r.dir) != road.directionality)
|
LEFT JOIN (VALUES (-1, TRUE), (1, FALSE), (0, FALSE)) AS r(dir, rev) ON (abs(r.dir) != road.directionality)
|
||||||
FULL OUTER JOIN overtaking_event ON (road.way_id = overtaking_event.way_id and (road.directionality != 0 or overtaking_event.direction_reversed = r.rev))
|
FULL OUTER JOIN overtaking_event ON (road.way_id = overtaking_event.way_id and (road.directionality != 0 or overtaking_event.direction_reversed = r.rev))
|
||||||
-- WHERE road.name = 'Merzhauser Straße'
|
-- WHERE road.name = 'Merzhauser Straße'
|
||||||
WHERE road.geometry && bbox
|
WHERE
|
||||||
|
zoom_level >= 10 AND
|
||||||
|
road.geometry && bbox
|
||||||
GROUP BY road.name, road.way_id, road.geometry, road.directionality, r.dir, r.rev;
|
GROUP BY road.name, road.way_id, road.geometry, road.directionality, r.dir, r.rev;
|
||||||
|
|
||||||
$$ LANGUAGE SQL IMMUTABLE;
|
$$ LANGUAGE SQL IMMUTABLE;
|
||||||
|
|
|
@ -3,6 +3,7 @@ tileset:
|
||||||
layers:
|
layers:
|
||||||
- layers/obs_events/obs_events.yaml
|
- layers/obs_events/obs_events.yaml
|
||||||
- layers/obs_roads/obs_roads.yaml
|
- layers/obs_roads/obs_roads.yaml
|
||||||
|
- layers/obs_regions/obs_regions.yaml
|
||||||
version: 0.6.2
|
version: 0.6.2
|
||||||
id: openbikesensor
|
id: openbikesensor
|
||||||
description: >
|
description: >
|
||||||
|
|
Loading…
Reference in a new issue