Make road colors and untagged roads display configurable

This commit is contained in:
Paul Bienkowski 2021-12-04 13:28:35 +01:00
parent f0c715bcbc
commit 6b38540586
4 changed files with 102 additions and 63 deletions

View file

@ -27,51 +27,6 @@ export function colorByDistance(attribute = 'distance_overtaker_mean', fallback
] ]
} }
export const roadsLayer = {
id: 'obs',
type: 'line',
source: 'obs',
'source-layer': 'obs_roads',
layout: {
'line-cap': 'round',
'line-join': 'round',
},
paint: {
'line-width': [
'interpolate',
['exponential', 1.5],
['zoom'],
12,
2,
17,
['case', ['!', ['to-boolean', ['get', 'distance_overtaker_mean']]], 2, 6],
],
'line-color': colorByDistance(),
'line-opacity': [
'interpolate',
['linear'],
['zoom'],
12,
0,
13,
['case', ['!', ['to-boolean', ['get', 'distance_overtaker_mean']]], 0, 1],
14,
['case', ['!', ['to-boolean', ['get', 'distance_overtaker_mean']]], 0, 1],
15,
1,
],
'line-offset': [
'interpolate',
['exponential', 1.5],
['zoom'],
12,
['get', 'offset_direction'],
19,
['*', ['get', 'offset_direction'], 8],
],
},
minzoom: 12,
}
export const trackLayer = { export const trackLayer = {
type: 'line', type: 'line',

View file

@ -1,15 +1,25 @@
import React from 'react' import React from 'react'
import {connect} from 'react-redux' import {connect} from 'react-redux'
import {List, Select} from 'semantic-ui-react' import {List, Select, Header, Checkbox} from 'semantic-ui-react'
import * as mapConfigActions from 'reducers/mapConfig' import * as mapConfigActions from 'reducers/mapConfig'
const BASEMAP_STYLE_OPTIONS = [ const BASEMAP_STYLE_OPTIONS = [
{key: 'positron', value: 'positron', text: 'Positron'}, {value: 'positron', key: 'positron', text: 'Positron'},
{key: 'bright', value: 'bright', text: 'OSM Bright'}, {value: 'bright', key: 'bright', text: 'OSM Bright'},
] ]
function LayerSidebar({mapConfig, setBasemapStyle}) { const ROAD_ATTRIBUTE_OPTIONS = [
{value: 'distance_overtaker_mean', key: 'distance_overtaker_mean', text: 'Overtaker distance mean'},
{value: 'distance_overtaker_min', key: 'distance_overtaker_min', text: 'Overtaker distance minimum'},
{value: 'distance_overtaker_max', key: 'distance_overtaker_max', text: 'Overtaker distance maximum'},
{value: 'distance_overtaker_median', key: 'distance_overtaker_median', text: 'Overtaker distance median'},
{value: 'overtaking_event_count', key: 'overtaking_event_count', text: 'Event count'},
]
function LayerSidebar({mapConfig, setMapConfigFlag}) {
const showUntagged = mapConfig?.obsRoads?.showUntagged ?? true
return ( return (
<div> <div>
<List> <List>
@ -18,7 +28,22 @@ function LayerSidebar({mapConfig, setBasemapStyle}) {
<Select <Select
options={BASEMAP_STYLE_OPTIONS} options={BASEMAP_STYLE_OPTIONS}
value={mapConfig?.baseMap?.style ?? 'positron'} value={mapConfig?.baseMap?.style ?? 'positron'}
onChange={(_e, {value}) => setBasemapStyle(value)} onChange={(_e, {value}) => setMapConfigFlag('baseMap.style', value)}
/>
</List.Item>
<Header as='h4' dividing>OBS Roads</Header>
<List.Item>
<Checkbox label='Show untagged roads' checked={showUntagged}
onChange={() => setMapConfigFlag('obsRoads.showUntagged', !showUntagged)}
/>
</List.Item>
<List.Item>
<List.Header style={{marginBlock: 8}}>Color based on</List.Header>
<Select
fluid
options={ROAD_ATTRIBUTE_OPTIONS}
value={mapConfig?.obsRoads?.attribute ?? 'distance_overtaker_mean'}
onChange={(_e, {value}) => setMapConfigFlag('obsRoads.attribute', value)}
/> />
</List.Item> </List.Item>
</List> </List>

View file

@ -1,18 +1,72 @@
import React, {useState, useCallback} from 'react' import React, {useState, useCallback, useMemo} from 'react'
import _ from 'lodash' import _ from 'lodash'
import {Sidebar, Button} from 'semantic-ui-react' import {Button} from 'semantic-ui-react'
import {Layer, Source} from 'react-map-gl' import {Layer, Source} from 'react-map-gl'
import produce from 'immer'
import {connect} from 'react-redux'
import {Page, Map} from 'components' import {Page, Map} from 'components'
import {useConfig} from 'config' import {useConfig} from 'config'
import {colorByDistance} from 'mapstyles'
import {roadsLayer} from '../../mapstyles'
import RoadInfo from './RoadInfo' import RoadInfo from './RoadInfo'
import LayerSidebar from './LayerSidebar' import LayerSidebar from './LayerSidebar'
import styles from './styles.module.less' import styles from './styles.module.less'
export default function MapPage() {
const untaggedRoadsLayer = {
id: 'obs_roads_untagged',
type: 'line',
source: 'obs',
'source-layer': 'obs_roads',
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'],
12,
['get', 'offset_direction'],
19,
['*', ['get', 'offset_direction'], 8],
],
},
minzoom: 12,
}
const getRoadsLayer = attribute => produce(untaggedRoadsLayer, draft => {
draft.id = 'obs_roads_normal'
draft.filter = draft.filter[1] // remove '!'
draft.paint['line-width'][6] = 6
draft.paint['line-color'] = colorByDistance(attribute)
draft.paint['line-opacity'][3] = 12
draft.paint['line-opacity'][5] = 13
})
function MapPage({mapConfig}) {
const {obsMapSource} = useConfig() || {} const {obsMapSource} = useConfig() || {}
const [clickLocation, setClickLocation] = useState<{longitude: number; latitude: number} | null>(null) const [clickLocation, setClickLocation] = useState<{longitude: number; latitude: number} | null>(null)
@ -33,6 +87,10 @@ export default function MapPage() {
const [layerSidebar, setLayerSidebar] = useState(true) const [layerSidebar, setLayerSidebar] = useState(true)
const showUntagged = mapConfig?.obsRoads?.showUntagged ?? true
const roadsLayerColorAttribute = mapConfig?.obsRoads?.attribute ?? 'distance_overtaker_mean'
const roadsLayer = useMemo(() => getRoadsLayer(roadsLayerColorAttribute ), [roadsLayerColorAttribute ])
if (!obsMapSource) { if (!obsMapSource) {
return null return null
} }
@ -55,7 +113,8 @@ export default function MapPage() {
onClick={() => setLayerSidebar(layerSidebar ? false : true)} onClick={() => setLayerSidebar(layerSidebar ? false : true)}
/> />
<Source id="obs" {...obsMapSource}> <Source id="obs" {...obsMapSource}>
<Layer {...roadsLayer} /> {showUntagged && <Layer key={untaggedRoadsLayer.id} {...untaggedRoadsLayer} />}
<Layer key={roadsLayer.id} {...roadsLayer} />
</Source> </Source>
<RoadInfo {...{clickLocation}} /> <RoadInfo {...{clickLocation}} />
@ -65,3 +124,5 @@ export default function MapPage() {
</Page> </Page>
) )
} }
export default connect((state) => ({mapConfig: state.mapConfig}))(MapPage)

View file

@ -1,4 +1,5 @@
import produce from 'immer' import produce from 'immer'
import _ from 'lodash'
type BaseMapStyle = 'positron' | 'bright' type BaseMapStyle = 'positron' | 'bright'
type MapConfigState = { type MapConfigState = {
@ -13,18 +14,15 @@ const initialState: MapConfigState = {
}, },
} }
export function setBasemapStyle(style: BaseMapStyle) { export function setMapConfigFlag(flag: string, value: unknown) {
return {type: 'MAPCONFIG.SET_BASEMAP_STYLE', payload: {style}} return {type: 'MAPCONFIG.SET_FLAG', payload: {flag, value}}
} }
export default function mapConfigReducer(state = initialState, action) { export default function mapConfigReducer(state = initialState, action) {
switch (action.type) { switch (action.type) {
case 'MAPCONFIG.SET_BASEMAP_STYLE': case 'MAPCONFIG.SET_FLAG':
return produce(state, draft => { return produce(state, draft => {
if (!draft.baseMap) { _.set(draft, action.payload.flag, action.payload.value)
draft.baseMap = {}
}
draft.baseMap.style = action.payload.style
}) })
default: default: