Merge pull request #242 from openbikesensor/rural_urban
Implement difference between urban and rural for events and roads
This commit is contained in:
commit
2755d6b2b5
|
@ -112,6 +112,7 @@ async def mapdetails_road(req):
|
|||
"histogram": {
|
||||
"bins": [None if math.isinf(b) else b for b in bins.tolist()],
|
||||
"counts": hist.tolist(),
|
||||
"zone": road.zone
|
||||
},
|
||||
"values": list(map(rounder, arr.tolist())),
|
||||
}
|
||||
|
|
|
@ -44,27 +44,74 @@ export const reds = [
|
|||
]
|
||||
|
||||
export function colorByCount(attribute = 'event_count', maxCount, colormap = viridis) {
|
||||
return colormapToScale(colormap, ['case', ['to-boolean', ['get', attribute]], ['get', attribute], 0], 0, maxCount)
|
||||
return colormapToScale(colormap, ['case', isValidAttribute(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 isValidAttribute(attribute) {
|
||||
if (attribute.endsWith('zone')) {
|
||||
return ['in', ['get', attribute], ['literal', ['rural', 'urban']]]
|
||||
}
|
||||
return ['to-boolean', ['get', attribute]]
|
||||
}
|
||||
|
||||
export function borderByZone() {
|
||||
return ["match", ['get', 'zone'],
|
||||
"rural", "cyan",
|
||||
"urban", "blue",
|
||||
"purple"
|
||||
]
|
||||
}
|
||||
|
||||
export function colorByDistance(attribute = 'distance_overtaker_mean', fallback = '#ABC', zone='urban') {
|
||||
|
||||
return [
|
||||
'case',
|
||||
['!', ['to-boolean', ['get', attribute]]],
|
||||
['!', isValidAttribute(attribute)],
|
||||
fallback,
|
||||
["match", ['get', 'zone'], "rural",
|
||||
[
|
||||
'step',
|
||||
['get', attribute],
|
||||
'rgba(150, 0, 0, 1)',
|
||||
1.1,
|
||||
steps['rural'][0],
|
||||
'rgba(255, 0, 0, 1)',
|
||||
1.3,
|
||||
steps['rural'][1],
|
||||
'rgba(255, 220, 0, 1)',
|
||||
1.5,
|
||||
steps['rural'][2],
|
||||
'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)',
|
||||
],
|
||||
[
|
||||
'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)',
|
||||
]
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react'
|
||||
import _ from 'lodash'
|
||||
import {connect} from 'react-redux'
|
||||
import {List, Select, Input, Divider, Checkbox, Header} from 'semantic-ui-react'
|
||||
import {List, Select, Input, Divider, Label, Checkbox, Header} from 'semantic-ui-react'
|
||||
|
||||
import {
|
||||
MapConfig,
|
||||
|
@ -23,6 +23,7 @@ const ROAD_ATTRIBUTE_OPTIONS = [
|
|||
{value: 'distance_overtaker_median', key: 'distance_overtaker_median', text: 'Overtaker distance median'},
|
||||
{value: 'overtaking_event_count', key: 'overtaking_event_count', text: 'Event count'},
|
||||
{value: 'usage_count', key: 'usage_count', text: 'Usage count'},
|
||||
{value: 'zone', key: 'zone', text: 'Overtaking distance zone'}
|
||||
]
|
||||
|
||||
function LayerSidebar({
|
||||
|
@ -96,10 +97,24 @@ function LayerSidebar({
|
|||
<ColorMapLegend map={_.chunk(colorByCount('obsRoads.maxCount', mapConfig.obsRoads.maxCount, viridisSimpleHtml ).slice(3), 2)} twoTicks />
|
||||
</List.Item></>
|
||||
) :
|
||||
(
|
||||
attribute.endsWith('zone') ? (
|
||||
<>
|
||||
<List.Item>
|
||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3].slice(2)} />
|
||||
<Label size="small" style={{background: "blue",color:"white"}}>urban (1.5 m)</Label>
|
||||
<Label size="small" style={{background: "cyan", color:"black"}}>rural (2 m)</Label>
|
||||
</List.Item></>
|
||||
) :
|
||||
(
|
||||
<>
|
||||
<List.Item>
|
||||
<List.Header>Urban</List.Header>
|
||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<List.Header>Rural</List.Header>
|
||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
|
||||
</List.Item>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
@ -120,7 +135,12 @@ function LayerSidebar({
|
|||
{showEvents && (
|
||||
<>
|
||||
<List.Item>
|
||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3].slice(2)} />
|
||||
<List.Header>Urban</List.Header>
|
||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][5].slice(2)} />
|
||||
</List.Item>
|
||||
<List.Item>
|
||||
<List.Header>Rural</List.Header>
|
||||
<DiscreteColorMapLegend map={colorByDistance('distance_overtaker')[3][3].slice(2)} />
|
||||
</List.Item>
|
||||
</>
|
||||
)}
|
||||
|
|
|
@ -9,7 +9,7 @@ import {Chart} from 'components'
|
|||
import {pairwise} from 'utils'
|
||||
|
||||
import api from 'api'
|
||||
import {colorByDistance} from 'mapstyles'
|
||||
import {colorByDistance, borderByZone} from 'mapstyles'
|
||||
|
||||
import styles from './styles.module.less'
|
||||
|
||||
|
@ -34,7 +34,7 @@ const LABELS = {
|
|||
max: 'Maximum',
|
||||
mean: 'Average',
|
||||
}
|
||||
const ZONE_COLORS = {urban: 'olive', rural: 'brown', motorway: 'purple'}
|
||||
const ZONE_COLORS = {urban: 'blue', rural: 'cyan', motorway: 'purple'}
|
||||
const CARDINAL_DIRECTIONS = ['north', 'north-east', 'east', 'south-east', 'south', 'south-west', 'west', 'north-west']
|
||||
const getCardinalDirection = (bearing) =>
|
||||
bearing == null
|
||||
|
@ -75,14 +75,15 @@ function RoadStatsTable({data}) {
|
|||
)
|
||||
}
|
||||
|
||||
function HistogramChart({bins, counts}) {
|
||||
function HistogramChart({bins, counts, zone}) {
|
||||
const diff = bins[1] - bins[0]
|
||||
const colortype = zone=="rural" ? 3:5;
|
||||
const data = _.zip(
|
||||
bins.slice(0, bins.length - 1).map((v) => v + diff / 2),
|
||||
counts
|
||||
).map((value) => ({
|
||||
value,
|
||||
itemStyle: {color: selectFromColorMap(colorByDistance()[3].slice(2), value[0])},
|
||||
itemStyle: {color: selectFromColorMap(colorByDistance()[3][colortype].slice(2), value[0]),},
|
||||
}))
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,7 @@ import produce from 'immer'
|
|||
|
||||
import {Page, Map} from 'components'
|
||||
import {useConfig} from 'config'
|
||||
import {colorByDistance, colorByCount, reds} from 'mapstyles'
|
||||
import {colorByDistance, colorByCount, borderByZone, reds, isValidAttribute} from 'mapstyles'
|
||||
import {useMapConfig} from 'reducers/mapConfig'
|
||||
|
||||
import RoadInfo from './RoadInfo'
|
||||
|
@ -40,20 +40,23 @@ const untaggedRoadsLayer = {
|
|||
minzoom: 12,
|
||||
}
|
||||
|
||||
const getUntaggedRoadsLayer = (colorAttribute, maxCount) =>
|
||||
produce(untaggedRoadsLayer, (draft) => {
|
||||
draft.filter = ['!', isValidAttribute(colorAttribute)]
|
||||
})
|
||||
|
||||
|
||||
const getRoadsLayer = (colorAttribute, maxCount) =>
|
||||
produce(untaggedRoadsLayer, (draft) => {
|
||||
draft.id = 'obs_roads_normal'
|
||||
if (colorAttribute.endsWith('_count')) {
|
||||
// delete draft.filter
|
||||
draft.filter = ['to-boolean', ['get', colorAttribute]]
|
||||
} else {
|
||||
draft.filter = draft.filter[1] // remove '!'
|
||||
}
|
||||
draft.filter = isValidAttribute(colorAttribute)
|
||||
draft.paint['line-width'][6] = 6 // scale bigger on zoom
|
||||
draft.paint['line-color'] = colorAttribute.startsWith('distance_')
|
||||
? colorByDistance(colorAttribute)
|
||||
: colorAttribute.endsWith('_count')
|
||||
? colorByCount(colorAttribute, maxCount)
|
||||
: colorAttribute.endsWith('zone')
|
||||
? borderByZone()
|
||||
: '#DDD'
|
||||
draft.paint['line-opacity'][3] = 12
|
||||
draft.paint['line-opacity'][5] = 13
|
||||
|
@ -128,8 +131,9 @@ export default function MapPage() {
|
|||
|
||||
const layers = []
|
||||
|
||||
const untaggedRoadsLayerCustom = useMemo(() => getUntaggedRoadsLayer(attribute), [attribute])
|
||||
if (mapConfig.obsRoads.show && mapConfig.obsRoads.showUntagged) {
|
||||
layers.push(untaggedRoadsLayer)
|
||||
layers.push(untaggedRoadsLayerCustom)
|
||||
}
|
||||
|
||||
const roadsLayer = useMemo(() => getRoadsLayer(attribute, maxCount), [attribute, maxCount])
|
||||
|
|
|
@ -11,7 +11,8 @@ type RoadAttribute =
|
|||
| "distance_overtaker_max"
|
||||
| "distance_overtaker_median"
|
||||
| "overtaking_event_count"
|
||||
| "usage_count";
|
||||
| "usage_count"
|
||||
| "zone";
|
||||
|
||||
export type MapConfig = {
|
||||
baseMap: {
|
||||
|
|
|
@ -163,6 +163,7 @@ module.exports = function (webpackEnv) {
|
|||
'/config.json': apiUrl,
|
||||
'/api': apiUrl,
|
||||
'/login': apiUrl,
|
||||
'/tiles': apiUrl
|
||||
},
|
||||
},
|
||||
module: {
|
||||
|
|
|
@ -61,6 +61,8 @@ local roads = osm2pgsql.define_way_table('road', {
|
|||
{ column = 'oneway', type = 'bool' },
|
||||
})
|
||||
|
||||
local minspeed_rural = 60
|
||||
|
||||
function osm2pgsql.process_way(object)
|
||||
if object.tags.highway and contains(HIGHWAY_TYPES, object.tags.highway) then
|
||||
local tags = object.tags
|
||||
|
@ -81,13 +83,30 @@ function osm2pgsql.process_way(object)
|
|||
zone = "urban"
|
||||
elseif string.match(zone, "motorway") then
|
||||
zone = "motorway"
|
||||
elseif contains(URBAN_TYPES, tags.highway) then
|
||||
elseif string.match(zone, "30") then
|
||||
zone = "urban"
|
||||
else
|
||||
zone = "urban"
|
||||
end
|
||||
end
|
||||
if not tags["zone:traffic"] then
|
||||
if contains(URBAN_TYPES, tags.highway) then
|
||||
zone = "urban"
|
||||
elseif contains(MOTORWAY_TYPES, tags.highway) then
|
||||
zone = "motorway"
|
||||
elseif (tags.maxspeed) and (tonumber(string.match(tags.maxspeed, '[%d]*'))) and tonumber(string.match(tags.maxspeed, '[%d]*')) > minspeed_rural then
|
||||
zone = "rural"
|
||||
elseif (tags["maxspeed:forward"]) and (tonumber(string.match(tags["maxspeed:forward"], '[%d]*'))) and tonumber(string.match(tags["maxspeed:forward"], '[%d]*')) > minspeed_rural then
|
||||
zone = "rural"
|
||||
elseif (tags["maxspeed:backward"]) and (tonumber(string.match(tags["maxspeed:backward"], '[%d]*'))) and tonumber(string.match(tags["maxspeed:backward"], '[%d]*')) > minspeed_rural then
|
||||
zone = "rural"
|
||||
elseif tags['source:maxspeed'] and string.match(tags['source:maxspeed'], "rural") then
|
||||
zone = "rural"
|
||||
elseif tags['source:maxspeed'] and string.match(tags['source:maxspeed'], "urban") then
|
||||
zone = "urban"
|
||||
else
|
||||
-- we can't figure it out
|
||||
zone = nil
|
||||
zone = "urban"
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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
|
||||
id::bigint as event_id,
|
||||
|
@ -9,8 +9,10 @@ RETURNS TABLE(event_id bigint, geometry geometry, distance_overtaker float, dist
|
|||
(case when direction_reversed then -1 else 1 end)::int as direction,
|
||||
course,
|
||||
speed,
|
||||
way_id::bigint as way_id
|
||||
CASE WHEN road.zone IS NULL THEN 'urban' else road.zone END as zone,
|
||||
overtaking_event.way_id::bigint as way_id
|
||||
FROM overtaking_event
|
||||
FULL OUTER JOIN road ON (road.way_id = overtaking_event.way_id)
|
||||
WHERE ST_Transform(overtaking_event.geometry, 3857) && bbox;
|
||||
|
||||
$$ LANGUAGE SQL IMMUTABLE;
|
||||
|
|
|
@ -16,6 +16,8 @@ layer:
|
|||
Direction of travel, as reported by GPS, in degree from North.
|
||||
speed: |
|
||||
Speed of travel, as reported by GPS, in meters per second (?).
|
||||
zone: |
|
||||
rural or urban
|
||||
defaults:
|
||||
srs: EPSG:3785
|
||||
datasource:
|
||||
|
@ -23,7 +25,7 @@ layer:
|
|||
geometry_field: geometry
|
||||
key_field: event_id
|
||||
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:
|
||||
- ./layer.sql
|
||||
|
|
|
@ -12,6 +12,7 @@ RETURNS TABLE(
|
|||
overtaking_event_count int,
|
||||
usage_count bigint,
|
||||
direction int,
|
||||
zone zone_type,
|
||||
offset_direction int
|
||||
) AS $$
|
||||
|
||||
|
@ -27,12 +28,13 @@ RETURNS TABLE(
|
|||
(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,
|
||||
r.dir as direction,
|
||||
road.zone::zone_type as zone,
|
||||
case when road.directionality = 0 then r.dir else 0 end as offset_direction
|
||||
FROM road
|
||||
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))
|
||||
-- WHERE road.name = 'Merzhauser Straße'
|
||||
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;
|
||||
|
|
|
@ -22,6 +22,8 @@ layer:
|
|||
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,
|
||||
even if it is oneway.
|
||||
zone: |
|
||||
ural or urban
|
||||
offset_direction: |
|
||||
Factor for offset to shift the line to the driving side. One of -1, 0, 1.
|
||||
defaults:
|
||||
|
@ -44,6 +46,7 @@ layer:
|
|||
overtaking_event_count,
|
||||
usage_count,
|
||||
direction,
|
||||
zone,
|
||||
offset_direction
|
||||
FROM layer_obs_roads(!bbox!, z(!scale_denominator!))
|
||||
) AS t
|
||||
|
|
Loading…
Reference in a new issue