Implement difference between urban and rural for events and road segments.

This commit is contained in:
gluap 2022-05-02 22:00:17 +02:00
parent 8728347695
commit 66dd84982c
14 changed files with 104 additions and 15 deletions

View file

@ -0,0 +1,25 @@
"""add event zone
Revision ID: b22108ab2ffb
Revises: a9627f63fbed
Create Date: 2022-04-30 19:06:11.472579
"""
from alembic import op
import sqlalchemy as sa
from migrations.utils import dbtype
# revision identifiers, used by Alembic.
revision = 'b22108ab2ffb'
down_revision = 'a9627f63fbed'
branch_labels = None
depends_on = None
def upgrade():
op.add_column("overtaking_event", sa.Column("zone", dbtype("zone_type")), )
def downgrade():
op.drop_column("overtaking_event", "zone")

View file

@ -124,6 +124,7 @@ class OvertakingEvent(Base):
distance_stationary = Column(Float) distance_stationary = Column(Float)
course = Column(Float) course = Column(Float)
speed = Column(Float) speed = Column(Float)
zone = Column(ZoneType)
def __repr__(self): def __repr__(self):
return f"<OvertakingEvent {self.id}>" return f"<OvertakingEvent {self.id}>"

View file

@ -27,7 +27,7 @@ from obs.face.filter import (
from obs.face.osm import DataSource, DatabaseTileSource, OverpassTileSource from obs.face.osm import DataSource, DatabaseTileSource, OverpassTileSource
from obs.api.db import OvertakingEvent, RoadUsage, Track, make_session from obs.api.db import OvertakingEvent, RoadUsage, Track, make_session, ZoneType
from obs.api.app import app from obs.api.app import app
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@ -293,6 +293,7 @@ async def import_overtaking_events(session, track, overtaking_events):
distance_stationary=m["distance_stationary"], distance_stationary=m["distance_stationary"],
course=m["course"], course=m["course"],
speed=m["speed"], speed=m["speed"],
zone=m["OSM_zone"] if ('OSM_zone' in m and m['OSM_zone'] is not None) else "urban"
) )
session.add_all(event_models.values()) session.add_all(event_models.values())

View file

@ -77,6 +77,7 @@ async def export_events(req):
writer.field("direction", "N", decimal=0) writer.field("direction", "N", decimal=0)
writer.field("course", "N", decimal=4) writer.field("course", "N", decimal=4)
writer.field("speed", "N", decimal=4) writer.field("speed", "N", decimal=4)
writer.field("zone", "N", decimal=4)
async for event in events: async for event in events:
writer.point(event.longitude, event.latitude) writer.point(event.longitude, event.latitude)
@ -87,6 +88,7 @@ async def export_events(req):
way_id=event.way_id, way_id=event.way_id,
course=event.course, course=event.course,
speed=event.speed, speed=event.speed,
zone=event.zone
# "time"=event.time, # "time"=event.time,
) )
@ -107,6 +109,7 @@ async def export_events(req):
"course": event.course, "course": event.course,
"speed": event.speed, "speed": event.speed,
"time": event.time, "time": event.time,
"zone": event.zone
}, },
} }
) )

View file

@ -112,6 +112,7 @@ async def mapdetails_road(req):
"histogram": { "histogram": {
"bins": [None if math.isinf(b) else b for b in bins.tolist()], "bins": [None if math.isinf(b) else b for b in bins.tolist()],
"counts": hist.tolist(), "counts": hist.tolist(),
"zone": road.zone
}, },
"values": list(map(rounder, arr.tolist())), "values": list(map(rounder, arr.tolist())),
} }

View file

@ -47,24 +47,64 @@ export function colorByCount(attribute = 'event_count', maxCount, colormap = vir
return colormapToScale(colormap, ['case', ['to-boolean', ['get', attribute]], ['get', attribute], 0], 0, maxCount) return colormapToScale(colormap, ['case', ['to-boolean', ['get', attribute]], ['get', attribute], 0], 0, maxCount)
} }
export function colorByDistance(attribute = 'distance_overtaker_mean', fallback = '#ABC') { var steps = {'rural': [1.6,1.8,2.0,2.2],
'urban': [1.1,1.3,1.5,1.7]}
export function borderByZone() {
return ["match", ['get', 'zone'],
"rural", "brown",
"urban", "olive",
"purple"
]
}
export function colorByDistance(attribute = 'distance_overtaker_mean', fallback = '#ABC', zone='urban') {
return [ return [
'case', 'case',
['!', ['to-boolean', ['get', attribute]]], ['!', ['to-boolean', ['get', attribute]]],
fallback, fallback,
["match", ['get', 'zone'], "rural",
[ [
'step', 'step',
['get', attribute], ['get', attribute],
'rgba(150, 0, 0, 1)', 'rgba(150, 0, 0, 1)',
1.1, steps['rural'][0],
'rgba(255, 0, 0, 1)', 'rgba(255, 0, 0, 1)',
1.3, steps['rural'][1],
'rgba(255, 220, 0, 1)', 'rgba(255, 220, 0, 1)',
1.5, steps['rural'][2],
'rgba(67, 200, 0, 1)', 'rgba(67, 200, 0, 1)',
1.7, steps['rural'][3],
'rgba(67, 150, 0, 1)',
], "urban",
[
'step',
['get', attribute],
'rgba(150, 0, 0, 1)',
steps['urban'][0],
'rgba(255, 0, 0, 1)',
steps['urban'][1],
'rgba(255, 220, 0, 1)',
steps['urban'][2],
'rgba(67, 200, 0, 1)',
steps['urban'][3],
'rgba(67, 150, 0, 1)', 'rgba(67, 150, 0, 1)',
], ],
[
'step',
['get', attribute],
'rgba(150, 0, 0, 1)',
steps['urban'][0],
'rgba(255, 0, 0, 1)',
steps['urban'][1],
'rgba(255, 220, 0, 1)',
steps['urban'][2],
'rgba(67, 200, 0, 1)',
steps['urban'][3],
'rgba(67, 150, 0, 1)',
]
]
] ]
} }

View file

@ -98,7 +98,10 @@ function LayerSidebar({
) : ) :
( (
<List.Item> <List.Item>
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3].slice(2)} /> <span style={{color: "olive"}}>Urban</span>
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
<span style={{color: "brown"}}>Rural</span>
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
</List.Item> </List.Item>
)} )}
</> </>
@ -120,7 +123,10 @@ function LayerSidebar({
{showEvents && ( {showEvents && (
<> <>
<List.Item> <List.Item>
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3].slice(2)} /> <span style={{color: "olive"}}>Urban</span>
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
<span style={{color: "brown"}}>Rural</span>
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
</List.Item> </List.Item>
</> </>
)} )}

View file

@ -9,7 +9,7 @@ import {Chart} from 'components'
import {pairwise} from 'utils' import {pairwise} from 'utils'
import api from 'api' import api from 'api'
import {colorByDistance} from 'mapstyles' import {colorByDistance, borderByZone} from 'mapstyles'
import styles from './styles.module.less' import styles from './styles.module.less'
@ -75,14 +75,15 @@ function RoadStatsTable({data}) {
) )
} }
function HistogramChart({bins, counts}) { function HistogramChart({bins, counts, zone}) {
const diff = bins[1] - bins[0] const diff = bins[1] - bins[0]
const colortype = zone=="rural" ? 3:5;
const data = _.zip( const data = _.zip(
bins.slice(0, bins.length - 1).map((v) => v + diff / 2), bins.slice(0, bins.length - 1).map((v) => v + diff / 2),
counts counts
).map((value) => ({ ).map((value) => ({
value, value,
itemStyle: {color: selectFromColorMap(colorByDistance()[3].slice(2), value[0])}, itemStyle: {color: selectFromColorMap(colorByDistance()[3][colortype].slice(2), value[0]),},
})) }))
return ( return (

View file

@ -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, borderByZone, reds} from 'mapstyles'
import {useMapConfig} from 'reducers/mapConfig' import {useMapConfig} from 'reducers/mapConfig'
import RoadInfo from './RoadInfo' import RoadInfo from './RoadInfo'
@ -67,6 +67,8 @@ const getEventsLayer = () => ({
paint: { paint: {
'circle-radius': ['interpolate', ['linear'], ['zoom'], 14, 3, 17, 8], 'circle-radius': ['interpolate', ['linear'], ['zoom'], 14, 3, 17, 8],
'circle-color': colorByDistance('distance_overtaker'), 'circle-color': colorByDistance('distance_overtaker'),
'circle-stroke-color': borderByZone(),
'circle-stroke-width':['interpolate', ['linear'], ['zoom'], 14, 1, 17, 4],
}, },
minzoom: 11, minzoom: 11,
}) })

View file

@ -163,6 +163,7 @@ module.exports = function (webpackEnv) {
'/config.json': apiUrl, '/config.json': apiUrl,
'/api': apiUrl, '/api': apiUrl,
'/login': apiUrl, '/login': apiUrl,
'/tiles': apiUrl
}, },
}, },
module: { module: {

View file

@ -1,5 +1,5 @@
CREATE OR REPLACE FUNCTION layer_obs_events(bbox geometry, zoom_level int) CREATE OR REPLACE FUNCTION layer_obs_events(bbox geometry, zoom_level int)
RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, distance_stationary float, direction int, course float, speed float, way_id bigint) AS $$ RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, distance_stationary float, direction int, course float, speed float, zone zone_type, way_id bigint) AS $$
SELECT SELECT
id::bigint as event_id, id::bigint as event_id,
@ -9,6 +9,7 @@ RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, dist
(case when direction_reversed then -1 else 1 end)::int as direction, (case when direction_reversed then -1 else 1 end)::int as direction,
course, course,
speed, speed,
zone,
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 ST_Transform(overtaking_event.geometry, 3857) && bbox;

View file

@ -16,6 +16,8 @@ layer:
Direction of travel, as reported by GPS, in degree from North. Direction of travel, as reported by GPS, in degree from North.
speed: | speed: |
Speed of travel, as reported by GPS, in meters per second (?). Speed of travel, as reported by GPS, in meters per second (?).
zone: |
rural or urban
defaults: defaults:
srs: EPSG:3785 srs: EPSG:3785
datasource: datasource:
@ -23,7 +25,7 @@ layer:
geometry_field: geometry geometry_field: geometry
key_field: event_id key_field: event_id
key_field_as_attribute: no key_field_as_attribute: no
query: (SELECT event_id, geometry, distance_overtaker, distance_stationary, direction, course, speed, way_id FROM layer_obs_events(!bbox!, z(!scale_denominator!))) AS t query: (SELECT event_id, geometry, distance_overtaker, distance_stationary, direction, course, speed, zone, way_id FROM layer_obs_events(!bbox!, z(!scale_denominator!))) AS t
schema: schema:
- ./layer.sql - ./layer.sql

View file

@ -12,6 +12,7 @@ RETURNS TABLE(
overtaking_event_count int, overtaking_event_count int,
usage_count bigint, usage_count bigint,
direction int, direction int,
zone zone_type,
offset_direction int offset_direction int
) AS $$ ) AS $$
@ -27,12 +28,13 @@ RETURNS TABLE(
(select count(id) from road_usage where road_usage.way_id = road.way_id and (select count(id) from road_usage where road_usage.way_id = road.way_id and
(road.directionality != 0 or road_usage.direction_reversed = r.rev)) as usage_count, (road.directionality != 0 or road_usage.direction_reversed = r.rev)) as usage_count,
r.dir as direction, r.dir as direction,
road.zone::zone_type as zone,
case when road.directionality = 0 then r.dir else 0 end as offset_direction case when road.directionality = 0 then r.dir else 0 end as offset_direction
FROM road FROM road
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 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, road.zone;
$$ LANGUAGE SQL IMMUTABLE; $$ LANGUAGE SQL IMMUTABLE;

View file

@ -22,6 +22,8 @@ layer:
Contains -1 for events while going along the way backwards, 1 for Contains -1 for events while going along the way backwards, 1 for
forwards. Each road is emitted twice, if it has data for both directions, forwards. Each road is emitted twice, if it has data for both directions,
even if it is oneway. even if it is oneway.
zone: |
ural or urban
offset_direction: | offset_direction: |
Factor for offset to shift the line to the driving side. One of -1, 0, 1. Factor for offset to shift the line to the driving side. One of -1, 0, 1.
defaults: defaults:
@ -44,6 +46,7 @@ layer:
overtaking_event_count, overtaking_event_count,
usage_count, usage_count,
direction, direction,
zone,
offset_direction offset_direction
FROM layer_obs_roads(!bbox!, z(!scale_denominator!)) FROM layer_obs_roads(!bbox!, z(!scale_denominator!))
) AS t ) AS t