Add administrative boundary import and display regional event count

This commit is contained in:
Paul Bienkowski 2022-04-03 16:06:34 +02:00 committed by gluap
parent c1ccec9664
commit 9e9a80a0be
No known key found for this signature in database
14 changed files with 224 additions and 27 deletions

View 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")

View file

@ -26,7 +26,7 @@ if app.config.FRONTEND_CONFIG:
.replace("111", "{x}")
.replace("222", "{y}")
],
"minzoom": 12,
"minzoom": 0,
"maxzoom": 14,
}
),

View file

@ -12,7 +12,7 @@
"obsMapSource": {
"type": "vector",
"tiles": ["https://portal.example.com/tiles/{z}/{x}/{y}.pbf"],
"minzoom": 12,
"minzoom": 0,
"maxzoom": 14
}
}

View file

@ -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,
</svg>
{tickValues.map(([value]) => (
<span className={styles.tick} key={value} style={{left: normalizeValue(value) * 100 + '%'}}>
{value.toFixed(2)}
{value.toFixed(digits)}
</span>
))}
</div>

View file

@ -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

View file

@ -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 (
<div>
@ -77,6 +78,28 @@ function LayerSidebar({
/>
</List.Item>
<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>
<Checkbox
toggle

View file

@ -6,17 +6,10 @@ import { Layer, Source } from "react-map-gl";
import produce from "immer";
import classNames from "classnames";
import type { Location } from "types";
import { Page, Map } from "components";
import { useConfig } from "config";
import {
colorByDistance,
colorByCount,
borderByZone,
reds,
isValidAttribute,
} from "mapstyles";
import { useMapConfig } from "reducers/mapConfig";
import {Page, Map} from 'components'
import {useConfig} from 'config'
import {colorByDistance, borderByZone, colorByCount, reds, isValidAttribute} from 'mapstyles'
import {useMapConfig} from 'reducers/mapConfig'
import RoadInfo from "./RoadInfo";
import LayerSidebar from "./LayerSidebar";
@ -27,19 +20,20 @@ const untaggedRoadsLayer = {
type: "line",
source: "obs",
"source-layer": "obs_roads",
minzoom: 12,
filter: ["!", ["to-boolean", ["get", "distance_overtaker_mean"]]],
layout: {
"line-cap": "round",
"line-join": "round",
},
paint: {
"line-width": ["interpolate", ["exponential", 1.5], ["zoom"], 12, 2, 17, 2],
"line-color": "#ABC",
"line-opacity": ["interpolate", ["linear"], ["zoom"], 14, 0, 15, 1],
"line-offset": [
"interpolate",
["exponential", 1.5],
["zoom"],
'line-width': ['interpolate', ['exponential', 1.5], ['zoom'], 12, 2, 17, 2],
'line-color': '#ABC',
'line-opacity': ['interpolate', ['linear'], ['zoom'], 14, 0, 15, 1],
'line-offset': [
'interpolate',
['exponential', 1.5],
['zoom'],
12,
["get", "offset_direction"],
19,
@ -58,6 +52,7 @@ const getRoadsLayer = (colorAttribute, maxCount) =>
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) {

View file

@ -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",

View file

@ -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

View file

@ -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);

View 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;

View 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

View file

@ -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,

View file

@ -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: >