From efe42021f14933df90143b0b81611a1f07ad9fe5 Mon Sep 17 00:00:00 2001 From: orangemug Date: Wed, 29 May 2019 17:37:55 +0100 Subject: [PATCH] Some more openlayers improvments as well as initial work for projection support --- package.json | 1 + src/components/App.jsx | 30 ++++- src/components/Toolbar.jsx | 1 + src/components/map/FeatureLayerPopup.jsx | 17 ++- src/components/map/OpenLayersMap.jsx | 152 ++++++++++++++++++++--- src/components/modals/DebugModal.js | 13 +- src/components/modals/SettingsModal.jsx | 15 +++ src/styles/_map.scss | 62 ++++++++- src/styles/_popup.scss | 2 +- 9 files changed, 267 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index 6fe3fd1..7591efd 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "maputnik-design": "github:maputnik/design", "ol": "^6.0.0-beta.8", "ol-mapbox-style": "^5.0.0-beta.2", + "proj4": "^2.5.0", "prop-types": "^15.6.2", "react": "^16.5.2", "react-aria-menubutton": "^6.0.1", diff --git a/src/components/App.jsx b/src/components/App.jsx index b24cb1b..2c088cd 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -218,6 +218,11 @@ export default class App extends React.Component { showCollisionBoxes: false, showOverdrawInspector: false, }, + openlayersDebugOptions: { + // TODO: For future projection work + // enableProjections: true, + debugToolbox: true, + }, } this.layerWatcher = new LayerWatcher({ @@ -478,10 +483,16 @@ export default class App extends React.Component { return metadata['maputnik:renderer'] || 'mbgljs'; } + getProjectionCode () { + const metadata = this.state.mapStyle.metadata || {}; + return this.state.openlayersDebugOptions.enableProjections ? metadata['maputnik:projection'] : "EPSG:3857"; + } + mapRenderer() { + const metadata = this.state.mapStyle.metadata || {}; + const mapProps = { mapStyle: style.replaceAccessTokens(this.state.mapStyle, {allowFallback: true}), - options: this.state.mapboxGlDebugOptions, onDataChange: (e) => { this.layerWatcher.analyzeMap(e.map) this.fetchSources(); @@ -496,9 +507,13 @@ export default class App extends React.Component { if(renderer === 'ol') { mapElement = } else { mapElement = @@ -540,6 +555,15 @@ export default class App extends React.Component { this.setModal(modalName, !this.state.isOpen[modalName]); } + onChangeOpenlayersDebug = (key, value) => { + this.setState({ + openlayersDebugOptions: { + ...this.state.openlayersDebugOptions, + [key]: value, + } + }); + } + onChangeMaboxGlDebug = (key, value) => { this.setState({ mapboxGlDebugOptions: { @@ -555,6 +579,7 @@ export default class App extends React.Component { const metadata = this.state.mapStyle.metadata || {} const toolbar = @@ -617,6 +644,7 @@ export default class App extends React.Component { onStyleChanged={this.onStyleChanged} isOpen={this.state.isOpen.settings} onOpenToggle={this.toggleModal.bind(this, 'settings')} + openlayersDebugOptions={this.state.openlayersDebugOptions} /> - + {feature.layer.type && + + } {feature.layer.id} {feature.counter && × {feature.counter}} diff --git a/src/components/map/OpenLayersMap.jsx b/src/components/map/OpenLayersMap.jsx index 9cb5ee2..f70bd2f 100644 --- a/src/components/map/OpenLayersMap.jsx +++ b/src/components/map/OpenLayersMap.jsx @@ -3,10 +3,32 @@ import {throttle} from 'lodash'; import PropTypes from 'prop-types' import { loadJSON } from '../../libs/urlopen' +import FeatureLayerPopup from './FeatureLayerPopup'; + import 'ol/ol.css' import {apply} from 'ol-mapbox-style'; -import {Map, View} from 'ol'; +import {Map, View, Proj, Overlay} from 'ol'; +import proj4 from 'proj4'; +import {register} from 'ol/proj/proj4'; +import {get as getProjection, toLonLat} from 'ol/proj'; +import {toStringHDMS} from 'ol/coordinate'; + +// Register some projections... +proj4.defs('EPSG:3031', '+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs '); +register(proj4); + + +function renderCoords (coords) { + if (!coords || coords.length < 2) { + return null; + } + else { + return + {coords.map((coord) => String(coord).padStart(7, "\u00A0")).join(', ')} + + } +} export default class OpenLayersMap extends React.Component { static propTypes = { @@ -14,15 +36,23 @@ export default class OpenLayersMap extends React.Component { mapStyle: PropTypes.object.isRequired, accessToken: PropTypes.string, style: PropTypes.object, + onLayerSelect: PropTypes.func.isRequired, } static defaultProps = { onMapLoaded: () => {}, onDataChange: () => {}, + onLayerSelect: () => {}, } constructor(props) { super(props); + this.state = { + zoom: 0, + rotation: 0, + cursor: [], + center: [], + }; this.updateStyle = throttle(this._updateStyle.bind(this), 200); } @@ -34,33 +64,125 @@ export default class OpenLayersMap extends React.Component { apply(this.map, newMapStyle); } - componentDidUpdate() { - this.updateStyle(this.props.mapStyle); + updateProjection () { + this.projection = getProjection(this.props.projectionCode || "EPSG:3857"); + } + + componentDidUpdate(prevProps) { + if (this.props.projectionCode !== prevProps.projectionCode) { + this.updateProjection(); + this.map.setView( + new View({ + projection: this.projection, + zoom: 1, + center: [180, -90] + }) + ); + } + if (this.props.mapStyle !== prevProps.mapStyle) { + this.updateStyle(this.props.mapStyle); + } } componentDidMount() { - this.updateStyle(this.props.mapStyle); + this.overlay = new Overlay({ + element: this.popupContainer, + autoPan: true, + autoPanAnimation: { + duration: 250 + } + }); + + this.updateProjection(); const map = new Map({ target: this.container, - layers: [], + overlays: [this.overlay], view: new View({ - zoom: 2, - center: [52.5, -78.4] + projection: this.projection, + zoom: 1, + center: [180, -90], + }) + }); + + map.on('pointermove', (evt) => { + var coords = toLonLat(evt.coordinate, this.projection); + this.setState({ + cursor: [ + coords[0].toFixed(2), + coords[1].toFixed(2) + ] }) }) + + map.on('postrender', (evt) => { + const center = toLonLat(map.getView().getCenter(), this.projection); + this.setState({ + center: [ + center[0].toFixed(2), + center[1].toFixed(2), + ], + rotation: map.getView().getRotation().toFixed(2), + zoom: map.getView().getZoom().toFixed(2) + }); + }); + + + this.map = map; + this.updateStyle(this.props.mapStyle); + } + + closeOverlay = (e) => { + e.target.blur(); + this.overlay.setPosition(undefined); } render() { - return
this.container = x} - style={{ - width: "100%", - height: "100%", - backgroundColor: '#fff', - ...this.props.style, - }}> + return
+
this.popupContainer = x} + style={{background: "black"}} + className="maputnik-popup" + > + + +
+
+ Zoom level: {this.state.zoom} +
+ {this.props.debugToolbox && +
+
+ + {renderCoords(this.state.cursor)} +
+
+ + {renderCoords(this.state.center)} +
+
+ + {this.state.rotation} +
+
+ } +
this.container = x} + style={{ + ...this.props.style, + }}> +
} } diff --git a/src/components/modals/DebugModal.js b/src/components/modals/DebugModal.js index bddb48f..6f57d6e 100644 --- a/src/components/modals/DebugModal.js +++ b/src/components/modals/DebugModal.js @@ -11,6 +11,7 @@ class DebugModal extends React.Component { onChangeMaboxGlDebug: PropTypes.func.isRequired, onOpenToggle: PropTypes.func.isRequired, mapboxGlDebugOptions: PropTypes.object, + openlayersDebugOptions: PropTypes.object, } render() { @@ -33,9 +34,15 @@ class DebugModal extends React.Component { } {this.props.renderer === 'ol' && -
- No debug options available for the OpenLayers renderer -
+
    + {Object.entries(this.props.openlayersDebugOptions).map(([key, val]) => { + return
  • + +
  • + })} +
}
diff --git a/src/components/modals/SettingsModal.jsx b/src/components/modals/SettingsModal.jsx index 6d38b2d..80c93b4 100644 --- a/src/components/modals/SettingsModal.jsx +++ b/src/components/modals/SettingsModal.jsx @@ -109,6 +109,21 @@ class SettingsModal extends React.Component { onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')} /> + + {this.props.openlayersDebugOptions.enableProjections && + + + + } + } diff --git a/src/styles/_map.scss b/src/styles/_map.scss index 951c654..15e08ce 100644 --- a/src/styles/_map.scss +++ b/src/styles/_map.scss @@ -1,7 +1,13 @@ //OPENLAYERS .maputnik-layout { .ol-zoom { - top: 10px; + top: 40px; + right: 10px; + left: auto; + } + + .ol-rotate { + top: 94px; right: 10px; left: auto; } @@ -20,3 +26,57 @@ } } } + + +.maputnik-ol { + width: 100%; + height: 100%; +} + +.maputnik-ol-popup { + background: $color-black; + +} + +.maputnik-coords { + font-family: monospace; + &:before { + content: '['; + color: #888; + } + &:after { + content: ']'; + color: #888; + } +} + +.maputnik-ol-debug { + font-family: monospace; + font-size: smaller; + position: absolute; + bottom: 10px; + left: 10px; + background: rgb(28, 31, 36); + padding: 6px 8px; + border-radius: 2px; + z-indeX: 9999; +} + +.maputnik-ol-zoom { + position: absolute; + right: 10px; + top: 10px; + background: #1c1f24; + border-radius: 2px; + padding: 6px 8px; + color: $color-lowgray; + z-indeX: 9999; + font-size: 12px; + font-weight: bold; +} + +.maputnik-ol-container { + display: flex; + flex: 1; + position: relative; +} diff --git a/src/styles/_popup.scss b/src/styles/_popup.scss index dc6caf9..2338f89 100644 --- a/src/styles/_popup.scss +++ b/src/styles/_popup.scss @@ -22,7 +22,7 @@ .maputnik-popup-layer-id { padding-left: $margin-2; - padding-right: $margin-2; + padding-right: 1.6em; background-color: $color-midgray; color: $color-white; }