diff --git a/.travis.yml b/.travis.yml index 510eea6..62a9559 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,6 @@ install: - npm install script: - mkdir public - - npm run build + - node --stack_size=100000 $(which npm) run build - npm run lint - npm run test diff --git a/package.json b/package.json index 9db5d6a..a722fea 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "''", "scripts": { "stats": "webpack --config webpack.production.config.js --profile --json > stats.json", - "build": "node --stack_size=100000 node_modules/webpack/bin/webpack.js --config webpack.production.config.js --progress --profile --colors", + "build": "webpack --config webpack.production.config.js --progress --profile --colors", "test": "karma start --single-run", "test-watch": "karma start", "start": "webpack-dev-server --progress --profile --colors --watch-poll", @@ -28,6 +28,7 @@ "lodash.throttle": "^4.1.1", "lodash.topairs": "^4.3.0", "mapbox-gl": "^0.29.0", + "mapbox-gl-inspect": "^1.0.7", "mapbox-gl-style-spec": "^8.11.0", "mousetrap": "^1.6.0", "ol-mapbox-style": "0.0.11", diff --git a/src/components/App.jsx b/src/components/App.jsx index 76fdca5..29d5305 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -2,7 +2,6 @@ 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' @@ -52,6 +51,7 @@ export default class App extends React.Component { selectedLayerIndex: 0, sources: {}, vectorLayers: {}, + inspectModeEnabled: false, } this.layerWatcher = new LayerWatcher({ @@ -149,6 +149,12 @@ export default class App extends React.Component { this.onLayersChange(changedLayers) } + changeInspectMode() { + this.setState({ + inspectModeEnabled: !this.state.inspectModeEnabled + }) + } + mapRenderer() { const metadata = this.state.mapStyle.metadata || {} const mapProps = { @@ -169,12 +175,10 @@ 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 + return } } @@ -194,6 +198,7 @@ export default class App extends React.Component { onStyleChanged={this.onStyleChanged.bind(this)} onStyleOpen={this.onStyleChanged.bind(this)} onStyleDownload={this.onStyleDownload.bind(this)} + onInspectModeToggle={this.changeInspectMode.bind(this)} /> const layerList = } - toggleInspectionMode() { - const metadata = this.props.mapStyle.metadata || {} - const currentRenderer = metadata['maputnik:renderer'] || 'mbgljs' - - const changedRenderer = currentRenderer === 'inspection' ? 'mbgljs' : 'inspection' - - const changedStyle = { - ...this.props.mapStyle, - metadata: { - ...this.props.mapStyle.metadata, - 'maputnik:renderer': changedRenderer - } - } - - this.props.onStyleChanged(changedStyle) - } - toggleModal(modalName) { this.setState({ isOpen: { @@ -194,7 +178,7 @@ export default class Toolbar extends React.Component { Style Settings - + Inspect diff --git a/src/components/map/FeaturePropertyPopup.jsx b/src/components/map/FeaturePropertyPopup.jsx index e6e41e0..e61c1b5 100644 --- a/src/components/map/FeaturePropertyPopup.jsx +++ b/src/components/map/FeaturePropertyPopup.jsx @@ -24,7 +24,6 @@ const Panel = (props) => { } function renderFeature(feature) { - console.log(feature) return
{feature.layer['source-layer']} {renderProperties(feature)} diff --git a/src/components/map/InspectionMap.jsx b/src/components/map/InspectionMap.jsx deleted file mode 100644 index d672698..0000000 --- a/src/components/map/InspectionMap.jsx +++ /dev/null @@ -1,126 +0,0 @@ -import React from 'react' -import ReactDOM from 'react-dom' -import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js' -import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' -import colors from '../../config/colors' -import style from '../../libs/style' -import FeaturePropertyPopup from './FeaturePropertyPopup' -import { colorHighlightedLayer, generateColoredLayers } from '../../libs/stylegen' -import 'mapbox-gl/dist/mapbox-gl.css' -import '../../mapboxgl.css' - -function convertInspectStyle(mapStyle, sources, highlightedLayer) { - const coloredLayers = generateColoredLayers(sources) - - const layer = colorHighlightedLayer(highlightedLayer) - if(layer) { - coloredLayers.push(layer) - } - - const newStyle = { - ...mapStyle, - layers: [ - { - "id": "background", - "type": "background", - "paint": { - "background-color": colors.black, - } - }, - ...coloredLayers, - ] - } - return newStyle -} - -function renderPopup(features) { - var mountNode = document.createElement('div'); - ReactDOM.render(, mountNode) - return mountNode.innerHTML; -} - -export default class InspectionMap extends React.Component { - static propTypes = { - onDataChange: React.PropTypes.func, - sources: React.PropTypes.object, - originalStyle: React.PropTypes.object, - highlightedLayer: React.PropTypes.object, - style: 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, nextProps.highlightedLayer), { diff: true}) - } - - componentDidMount() { - MapboxGl.accessToken = this.props.accessToken - - const map = new MapboxGl.Map({ - container: this.container, - style: convertInspectStyle(this.props.mapStyle, this.props.sources, this.props.highlightedLayer), - hash: true, - }) - - const nav = new MapboxGl.NavigationControl(); - map.addControl(nav, 'top-right'); - - map.on("style.load", () => { - this.setState({ map }); - }) - - map.on("data", e => { - if(e.dataType !== 'tile') return - this.props.onDataChange({ - map: this.state.map - }) - }) - - map.on('click', this.displayPopup.bind(this)) - map.on('mousemove', function(e) { - var features = map.queryRenderedFeatures(e.point, { layers: this.layers }) - map.getCanvas().style.cursor = (features.length) ? 'pointer' : '' - }) - } - - displayPopup(e) { - const features = this.state.map.queryRenderedFeatures(e.point, { - layers: this.layers - }); - - if (!features.length) { - return - } - - // Populate the popup and set its coordinates - // based on the feature found. - const popup = new MapboxGl.Popup() - .setLngLat(e.lngLat) - .setHTML(renderPopup(features)) - .addTo(this.state.map) - } - - render() { - return
this.container = x} - style={{ - position: "fixed", - top: 0, - bottom: 0, - height: "100%", - width: "100%", - ...this.props.style, - }}>
- } -} diff --git a/src/components/map/MapboxGlMap.jsx b/src/components/map/MapboxGlMap.jsx index 0a2eef8..dc94896 100644 --- a/src/components/map/MapboxGlMap.jsx +++ b/src/components/map/MapboxGlMap.jsx @@ -1,24 +1,57 @@ import React from 'react' import ReactDOM from 'react-dom' import MapboxGl from 'mapbox-gl/dist/mapbox-gl.js' +import MapboxInspect from 'mapbox-gl-inspect' import FeatureLayerTable from './FeatureLayerTable' +import FeaturePropertyPopup from './FeaturePropertyPopup' import validateColor from 'mapbox-gl-style-spec/lib/validate/validate_color' +import colors from '../../config/colors' import style from '../../libs/style.js' +import { colorHighlightedLayer } from '../../libs/highlight' import 'mapbox-gl/dist/mapbox-gl.css' import '../../mapboxgl.css' -function renderPopup(features) { +function renderLayerPopup(features) { var mountNode = document.createElement('div'); ReactDOM.render(, mountNode) return mountNode.innerHTML; } +function renderPropertyPopup(features) { + var mountNode = document.createElement('div'); + ReactDOM.render(, mountNode) + return mountNode.innerHTML; +} + +function buildInspectStyle(originalMapStyle, coloredLayers, highlightedLayer) { + const backgroundLayer = { + "id": "background", + "type": "background", + "paint": { + "background-color": colors.black, + } + } + + const layer = colorHighlightedLayer(highlightedLayer) + if(layer) { + coloredLayers.push(layer) + } + + const inspectStyle = { + ...originalMapStyle, + layers: [backgroundLayer].concat(coloredLayers) + } + return inspectStyle +} + export default class MapboxGlMap extends React.Component { static propTypes = { onDataChange: React.PropTypes.func, mapStyle: React.PropTypes.object.isRequired, accessToken: React.PropTypes.string, style: React.PropTypes.object, + inspectModeEnabled: React.PropTypes.bool.isRequired, + highlightedLayer: React.PropTypes.object, } static defaultProps = { @@ -31,6 +64,7 @@ export default class MapboxGlMap extends React.Component { MapboxGl.accessToken = props.accessToken this.state = { map: null, + inspect: null, isPopupOpen: false, popupX: 0, popupY: 0, @@ -39,12 +73,22 @@ export default class MapboxGlMap extends React.Component { componentWillReceiveProps(nextProps) { MapboxGl.accessToken = nextProps.accessToken - if(!this.state.map) return - //Mapbox GL now does diffing natively so we don't need to calculate - //the necessary operations ourselves! - this.state.map.setStyle(nextProps.mapStyle, { diff: true}) + if(!nextProps.inspectModeEnabled) { + //Mapbox GL now does diffing natively so we don't need to calculate + //the necessary operations ourselves! + this.state.map.setStyle(nextProps.mapStyle, { diff: true}) + } + } + + componentDidUpdate(prevProps) { + if(this.props.inspectModeEnabled !== prevProps.inspectModeEnabled) { + this.state.inspect.toggleInspector() + } + if(this.props.inspectModeEnabled) { + this.state.inspect.render() + } } componentDidMount() { @@ -54,11 +98,29 @@ export default class MapboxGlMap extends React.Component { hash: true, }) - const nav = new MapboxGl.NavigationControl(); - map.addControl(nav, 'top-right'); + const nav = new MapboxGl.NavigationControl(); + map.addControl(nav, 'top-right'); + + const inspect = new MapboxInspect({ + popup: new MapboxGl.Popup({ + closeButton: false, + closeOnClick: false + }), + showMapPopup: true, + showInspectButton: false, + buildInspectStyle: (originalMapStyle, coloredLayers) => buildInspectStyle(originalMapStyle, coloredLayers, this.props.highlightedLayer), + renderPopup: features => { + if(this.props.inspectModeEnabled) { + return renderPropertyPopup(features) + } else { + return renderLayerPopup(features) + } + } + }) + map.addControl(inspect) map.on("style.load", () => { - this.setState({ map }); + this.setState({ map, inspect }); }) map.on("data", e => { @@ -67,24 +129,6 @@ export default class MapboxGlMap extends React.Component { map: this.state.map }) }) - - map.on('click', this.displayPopup.bind(this)); - map.on('mousemove', function(e) { - var features = map.queryRenderedFeatures(e.point, { layers: this.layers }) - map.getCanvas().style.cursor = (features.length) ? 'pointer' : '' - }) - } - - displayPopup(e) { - const features = this.state.map.queryRenderedFeatures(e.point, { - layers: this.layers - }); - - if(features.length < 1) return - const popup = new MapboxGl.Popup() - .setLngLat(e.lngLat) - .setHTML(renderPopup(features)) - .addTo(this.state.map) } render() { @@ -93,7 +137,7 @@ export default class MapboxGlMap extends React.Component { style={{ position: "fixed", top: 0, - left: 550, + right: 0, bottom: 0, height: "100%", width: "75%", diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index c1f9664..ff8f149 100644 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -83,7 +83,6 @@ class SettingsModal extends React.Component { options={[ ['mbgljs', 'MapboxGL JS'], ['ol3', 'Open Layers 3'], - ['inspection', 'Inspection Mode'], ]} value={metadata['maputnik:renderer'] || 'mbgljs'} onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')} diff --git a/src/libs/highlight.js b/src/libs/highlight.js new file mode 100644 index 0000000..5e050ef --- /dev/null +++ b/src/libs/highlight.js @@ -0,0 +1,36 @@ +import randomColor from 'randomcolor' +import Color from 'color' + +import stylegen from 'mapbox-gl-inspect/lib/stylegen' +import colors from 'mapbox-gl-inspect/lib/colors' + +export function colorHighlightedLayer(layer) { + if(!layer || layer.type === 'background' || layer.type === 'raster') return null + + function changeLayer(l) { + if(layer.filter) { + l.filter = layer.filter + } else { + delete l['filter'] + } + l.id = l.id + '_highlight' + return l + } + + const color = colors.brightColor(layer.id, 1) + const layers = [] + + if(layer.type === "fill" || layer.type === 'fill-extrusion') { + return changeLayer(stylegen.polygonLayer(color, color, layer.source, layer['source-layer'])) + } + + if(layer.type === "symbol" || layer.type === 'circle') { + return changeLayer(stylegen.circleLayer(color, layer.source, layer['source-layer'])) + } + + if(layer.type === 'line') { + return changeLayer(stylegen.lineLayer(color, layer.source, layer['source-layer'])) + } + + return null +} diff --git a/src/libs/stylegen.js b/src/libs/stylegen.js index f4decc5..e69de29 100644 --- a/src/libs/stylegen.js +++ b/src/libs/stylegen.js @@ -1,141 +0,0 @@ -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) { - const layer = { - id: `${source}_${vectorLayer}_circle`, - source: source, - type: 'circle', - paint: { - 'circle-color': color, - 'circle-radius': 2, - }, - filter: ["==", "$type", "Point"] - } - if(vectorLayer) { - layer['source-layer'] = vectorLayer - } - return layer -} - -function polygonLayer(source, vectorLayer, color, fillColor) { - const layer = { - id: `${source}_${vectorLayer}_polygon`, - source: source, - type: 'fill', - paint: { - 'fill-color': fillColor, - 'fill-antialias': true, - 'fill-outline-color': color, - }, - filter: ["==", "$type", "Polygon"] - } - if(vectorLayer) { - layer['source-layer'] = vectorLayer - } - return layer -} - -function lineLayer(source, vectorLayer, color) { - const layer = { - id: `${source}_${vectorLayer}_line`, - source: source, - layout: { - 'line-join': 'round', - 'line-cap': 'round' - }, - type: 'line', - paint: { - 'line-color': color, - }, - filter: ["==", "$type", "LineString"] - } - if(vectorLayer) { - layer['source-layer'] = vectorLayer - } - return layer -} - -export function colorHighlightedLayer(layer) { - if(!layer || layer.type === 'background' || layer.type === 'raster') return null - - function changeLayer(l) { - if(layer.filter) { - l.filter = layer.filter - } else { - delete l['filter'] - } - l.id = l.id + '_highlight' - return l - } - - const color = assignVectorLayerColor(layer.id) - const layers = [] - - if(layer.type === "fill" || layer.type === 'fill-extrusion') { - return changeLayer(polygonLayer(layer.source, layer['source-layer'], color, Color(color).alpha(0.2).string())) - } - - if(layer.type === "symbol" || layer.type === 'circle') { - return changeLayer(circleLayer(layer.source, layer['source-layer'], color)) - } - - if(layer.type === 'line') { - return changeLayer(lineLayer(layer.source, layer['source-layer'], color)) - } - - return null -} - -export function generateColoredLayers(sources) { - const polyLayers = [] - const circleLayers = [] - const lineLayers = [] - - Object.keys(sources).forEach(sourceId => { - const layers = sources[sourceId] - - // Deal with GeoJSON sources that do not have any source layers - if(!layers) { - const color = Color(assignVectorLayerColor(sourceId)) - circleLayers.push(circleLayer(sourceId, null, color.alpha(0.3).string())) - lineLayers.push(lineLayer(sourceId, null, color.alpha(0.3).string())) - polyLayers.push(polygonLayer(sourceId, null, color.alpha(0.2).string(), color.alpha(0.05).string())) - return - } - - layers.forEach(layerId => { - const color = Color(assignVectorLayerColor(layerId)) - circleLayers.push(circleLayer(sourceId, layerId, color.alpha(0.3).string())) - lineLayers.push(lineLayer(sourceId, layerId, color.alpha(0.3).string())) - polyLayers.push(polygonLayer(sourceId, layerId, color.alpha(0.2).string(), color.alpha(0.05).string())) - }) - }) - - return polyLayers.concat(lineLayers).concat(circleLayers) -} diff --git a/src/mapboxgl.css b/src/mapboxgl.css index b8e3dd9..5038759 100644 --- a/src/mapboxgl.css +++ b/src/mapboxgl.css @@ -49,3 +49,12 @@ .mapboxgl-ctrl-icon.mapboxgl-ctrl-compass > span.arrow { background-image: url("data:image/svg+xml;charset=utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%0A%09%3Cpolygon%20fill%3D%27%238e8e8e%27%20points%3D%276%2C9%2010%2C1%2014%2C9%27%2F%3E%0A%09%3Cpolygon%20fill%3D%27%23CCCCCC%27%20points%3D%276%2C11%2010%2C19%2014%2C11%20%27%2F%3E%0A%3C%2Fsvg%3E") } + +.mapboxgl-ctrl-inspect { + background-image: url('data:image/svg+xml;charset=utf8,