From f332d517f3bf03ab4d8f5d7955f3c405f7a6fa9e Mon Sep 17 00:00:00 2001 From: Lukas Martinelli Date: Sat, 24 Dec 2016 14:42:57 +0100 Subject: [PATCH] Add inspection map --- .gitignore | 1 + src/components/App.jsx | 7 +- src/components/map/InspectionMap.jsx | 79 ++++++++++++++++++++++ src/components/map/Map.jsx | 20 ------ src/components/map/MapboxGlMap.jsx | 37 ++++++++--- src/components/map/OpenLayers3Map.jsx | 26 ++++++-- src/components/modals/SettingsModal.jsx | 3 +- src/libs/layerwatcher.js | 19 ++---- src/libs/stylegen.js | 88 +++++++++++++++++++++++++ 9 files changed, 232 insertions(+), 48 deletions(-) create mode 100644 src/components/map/InspectionMap.jsx delete mode 100644 src/components/map/Map.jsx create mode 100644 src/libs/stylegen.js diff --git a/.gitignore b/.gitignore index 9908478..a399b1d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # Logs logs *.log +*.swp # Runtime data pids diff --git a/src/components/App.jsx b/src/components/App.jsx index 6e11eed..1d287e1 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -2,6 +2,7 @@ import React from 'react' import { saveAs } from 'file-saver' import Mousetrap from 'mousetrap' +import InspectionMap from './map/InspectionMap' import MapboxGlMap from './map/MapboxGlMap' import OpenLayers3Map from './map/OpenLayers3Map' import LayerList from './layers/LayerList' @@ -121,8 +122,8 @@ export default class App extends React.Component { const mapProps = { mapStyle: this.state.mapStyle, accessToken: this.state.accessToken, - onMapLoaded: (map) => { - this.layerWatcher.map = map + onDataChange: (e) => { + this.layerWatcher.analyzeMap(e.map) } } @@ -132,6 +133,8 @@ export default class App extends React.Component { // Check if OL3 code has been loaded? if(renderer === 'ol3') { return + } else if(renderer === 'inspection') { + return } else { return } diff --git a/src/components/map/InspectionMap.jsx b/src/components/map/InspectionMap.jsx new file mode 100644 index 0000000..365ebc2 --- /dev/null +++ b/src/components/map/InspectionMap.jsx @@ -0,0 +1,79 @@ +import React from 'react' +import MapboxGl from 'mapbox-gl' +import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' +import colors from '../../config/colors' +import style from '../../libs/style' +import { generateColoredLayers } from '../../libs/stylegen' + +function convertInspectStyle(mapStyle, sources) { + const newStyle = { + ...mapStyle, + layers: [ + { + "id": "background", + "type": "background", + "paint": { + "background-color": colors.black, + } + }, + ...generateColoredLayers(sources), + ] + } + return newStyle +} + +export default class InspectionMap extends React.Component { + static propTypes = { + onDataChange: React.PropTypes.func, + sources: React.PropTypes.object, + originalStyle: React.PropTypes.object, + } + + static defaultProps = { + onMapLoaded: () => {}, + onTileLoaded: () => {}, + } + + constructor(props) { + super(props) + this.state = { map: null } + } + + componentWillReceiveProps(nextProps) { + if(!this.state.map) return + + this.state.map.setStyle(convertInspectStyle(nextProps.mapStyle, this.props.sources), { diff: true}) + } + + componentDidMount() { + MapboxGl.accessToken = this.props.accessToken + + const map = new MapboxGl.Map({ + container: this.container, + style: convertInspectStyle(this.props.mapStyle, this.props.sources), + }) + + map.on("style.load", () => { + this.setState({ map }); + }) + + map.on("data", e => { + if(e.dataType !== 'tile') return + this.props.onDataChange({ + map: this.state.map + }) + }) + } + + render() { + return
this.container = x} + style={{ + position: "fixed", + top: 0, + bottom: 0, + height: "100%", + width: "100%", + }}>
+ } +} diff --git a/src/components/map/Map.jsx b/src/components/map/Map.jsx deleted file mode 100644 index 83387d4..0000000 --- a/src/components/map/Map.jsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react' - -export default class Map extends React.Component { - static propTypes = { - mapStyle: React.PropTypes.object.isRequired, - accessToken: React.PropTypes.string, - } - - render() { - return
this.container = x} - style={{ - position: "fixed", - top: 0, - bottom: 0, - height: "100%", - width: "100%", - }}>
- } -} diff --git a/src/components/map/MapboxGlMap.jsx b/src/components/map/MapboxGlMap.jsx index 5eaf3f4..86055a2 100644 --- a/src/components/map/MapboxGlMap.jsx +++ b/src/components/map/MapboxGlMap.jsx @@ -2,22 +2,25 @@ import React from 'react' import MapboxGl from 'mapbox-gl' import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' -import Map from './Map.jsx' import style from '../../libs/style.js' -export default class MapboxGlMap extends Map { +export default class MapboxGlMap extends React.Component { static propTypes = { - onMapLoaded: React.PropTypes.func, + onDataChange: React.PropTypes.func, + mapStyle: React.PropTypes.object.isRequired, + accessToken: React.PropTypes.string, } static defaultProps = { - onMapLoaded: () => {} + onMapLoaded: () => {}, + onDataChange: () => {}, } constructor(props) { super(props) this.state = { map: null } } + componentWillReceiveProps(nextProps) { if(!this.state.map) return @@ -32,11 +35,29 @@ export default class MapboxGlMap extends Map { const map = new MapboxGl.Map({ container: this.container, style: this.props.mapStyle, - }); + }) - map.on("style.load", (...args) => { - this.props.onMapLoaded(map) + map.on("style.load", () => { this.setState({ map }); - }); + }) + + map.on("data", e => { + if(e.dataType !== 'tile') return + this.props.onDataChange({ + map: this.state.map + }) + }) + } + + render() { + return
this.container = x} + style={{ + position: "fixed", + top: 0, + bottom: 0, + height: "100%", + width: "100%", + }}>
} } diff --git a/src/components/map/OpenLayers3Map.jsx b/src/components/map/OpenLayers3Map.jsx index bba2e3b..61b52bc 100644 --- a/src/components/map/OpenLayers3Map.jsx +++ b/src/components/map/OpenLayers3Map.jsx @@ -1,10 +1,16 @@ import React from 'react' -import Map from './Map' import style from '../../libs/style.js' -class OpenLayers3Map extends Map { - constructor(props) { - super(props) +class OpenLayers3Map extends React.Component { + static propTypes = { + onDataChange: React.PropTypes.func, + mapStyle: React.PropTypes.object.isRequired, + accessToken: React.PropTypes.string, + } + + static defaultProps = { + onMapLoaded: () => {}, + onDataChange: () => {}, } componentWillReceiveProps(nextProps) { @@ -63,6 +69,18 @@ class OpenLayers3Map extends Map { this.setState({ map }); }) } + + render() { + return
this.container = x} + style={{ + position: "fixed", + top: 0, + bottom: 0, + height: "100%", + width: "100%", + }}>
+ } } export default OpenLayers3Map diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index 937e51a..54fb043 100644 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -72,7 +72,8 @@ class SettingsModal extends React.Component { { - if(e.dataType !== 'tile') return - + analyzeMap(map) { + Object.keys(map.style.sourceCaches).forEach(sourceId => { //NOTE: This heavily depends on the internal API of Mapbox GL //so this breaks between Mapbox GL JS releases - this._sources[e.sourceId] = e.style.sourceCaches[e.sourceId]._source.vectorLayerIds - this.throttledAnalyzeVectorLayerFields() + this._sources[sourceId] = map.style.sourceCaches[sourceId]._source.vectorLayerIds }) + this.throttledAnalyzeVectorLayerFields(map) } - analyzeVectorLayerFields() { + analyzeVectorLayerFields(map) { Object.keys(this._sources).forEach(sourceId => { this._sources[sourceId].forEach(vectorLayerId => { const knownProperties = this._vectorLayers[vectorLayerId] || {} const params = { sourceLayer: vectorLayerId } - this._map.querySourceFeatures(sourceId, params).forEach(feature => { + map.querySourceFeatures(sourceId, params).forEach(feature => { Object.keys(feature.properties).forEach(propertyName => { const knownPropertyValues = knownProperties[propertyName] || {} knownPropertyValues[feature.properties[propertyName]] = {} diff --git a/src/libs/stylegen.js b/src/libs/stylegen.js new file mode 100644 index 0000000..fccbdf0 --- /dev/null +++ b/src/libs/stylegen.js @@ -0,0 +1,88 @@ +import randomColor from 'randomcolor' +import Color from 'color' + +function assignVectorLayerColor(layerId) { + let hue = null + if(/water|ocean|lake|sea|river/.test(layerId)) { + hue = 'blue' + } + + if(/road|highway|transport/.test(layerId)) { + hue = 'orange' + } + + if(/building/.test(layerId)) { + hue = 'yellow' + } + + if(/wood|forest|park|landcover|landuse/.test(layerId)) { + hue = 'green' + } + + return randomColor({ + luminosity: 'bright', + hue: hue, + seed: layerId, + }) +} + +function circleLayer(source, vectorLayer, color) { + return { + id: vectorLayer + Math.random(), + source: source, + 'source-layer': vectorLayer, + interactive: true, + type: 'circle', + paint: { + 'circle-color': color, + 'circle-radius': 2, + }, + filter: ["==", "$type", "Point"] + } +} + +function polygonLayer(source, vectorLayer, color) { + return { + id: vectorLayer + Math.random(), + source: source, + 'source-layer': vectorLayer, + interactive: true, + type: 'fill', + paint: { + 'fill-color': Color(color).alpha(0.15).string(), + 'fill-antialias': true, + 'fill-outline-color': color, + }, + filter: ["==", "$type", "Polygon"] + } +} + +function lineLayer(source, vectorLayer, color) { + return { + id: vectorLayer + Math.random(), + source: source, + 'source-layer': vectorLayer, + interactive: true, + layout: { + 'line-join': 'round', + 'line-cap': 'round' + }, + type: 'line', + paint: {'line-color': color}, + filter: ["==", "$type", "LineString"] + } +} + +export function generateColoredLayers(sources) { + const styleLayers = [] + Object.keys(sources).forEach(sourceId => { + const layers = sources[sourceId] + layers.forEach(layerId => { + const color = assignVectorLayerColor(layerId) + styleLayers.push(circleLayer(sourceId, layerId, color)) + styleLayers.push(lineLayer(sourceId, layerId, color)) + styleLayers.push(polygonLayer(sourceId, layerId, color)) + }) + }) + return styleLayers +}