Merge pull request #524 from orangemug/feature/ol-improvements

Further OpenLayers improvements
This commit is contained in:
Orange Mug 2019-06-01 17:39:40 +01:00 committed by GitHub
commit 827bd5fa24
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 259 additions and 41 deletions

5
package-lock.json generated
View file

@ -8356,6 +8356,11 @@
"integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=",
"dev": true
},
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
"lodash.isequal": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",

View file

@ -32,6 +32,7 @@
"lodash.capitalize": "^4.2.1",
"lodash.clamp": "^4.0.3",
"lodash.clonedeep": "^4.5.0",
"lodash.get": "^4.4.2",
"lodash.isequal": "^4.5.0",
"lodash.throttle": "^4.1.1",
"mapbox-gl": "^0.53.1",

View file

@ -2,6 +2,7 @@ import autoBind from 'react-autobind';
import React from 'react'
import cloneDeep from 'lodash.clonedeep'
import clamp from 'lodash.clamp'
import get from 'lodash.get'
import {arrayMove} from 'react-sortable-hoc'
import url from 'url'
@ -218,6 +219,9 @@ export default class App extends React.Component {
showCollisionBoxes: false,
showOverdrawInspector: false,
},
openlayersDebugOptions: {
debugToolbox: false,
},
}
this.layerWatcher = new LayerWatcher({
@ -276,6 +280,27 @@ export default class App extends React.Component {
})
}
onChangeMetadataProperty = (property, value) => {
// If we're changing renderer reset the map state.
if (
property === 'maputnik:renderer' &&
value !== get(this.state.mapStyle, ['metadata', 'maputnik:renderer'], 'mbgljs')
) {
this.setState({
mapState: 'map'
});
}
const changedStyle = {
...this.state.mapStyle,
metadata: {
...this.state.mapStyle.metadata,
[property]: value
}
}
this.onStyleChanged(changedStyle)
}
onStyleChanged = (newStyle, save=true) => {
const errors = validate(newStyle, latest)
@ -479,9 +504,10 @@ export default class App extends React.Component {
}
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 +522,12 @@ export default class App extends React.Component {
if(renderer === 'ol') {
mapElement = <OpenLayersMap
{...mapProps}
debugToolbox={this.state.openlayersDebugOptions.debugToolbox}
onLayerSelect={this.onLayerSelect}
/>
} else {
mapElement = <MapboxGlMap {...mapProps}
options={this.state.mapboxGlDebugOptions}
inspectModeEnabled={this.state.mapState === "inspect"}
highlightedLayer={this.state.mapStyle.layers[this.state.selectedLayerIndex]}
onLayerSelect={this.onLayerSelect} />
@ -540,6 +569,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 +593,7 @@ export default class App extends React.Component {
const metadata = this.state.mapStyle.metadata || {}
const toolbar = <Toolbar
renderer={this._getRenderer()}
mapState={this.state.mapState}
mapStyle={this.state.mapStyle}
inspectModeEnabled={this.state.mapState === "inspect"}
@ -603,7 +642,9 @@ export default class App extends React.Component {
<DebugModal
renderer={this._getRenderer()}
mapboxGlDebugOptions={this.state.mapboxGlDebugOptions}
openlayersDebugOptions={this.state.openlayersDebugOptions}
onChangeMaboxGlDebug={this.onChangeMaboxGlDebug}
onChangeOpenlayersDebug={this.onChangeOpenlayersDebug}
isOpen={this.state.isOpen.debug}
onOpenToggle={this.toggleModal.bind(this, 'debug')}
/>
@ -615,8 +656,10 @@ export default class App extends React.Component {
<SettingsModal
mapStyle={this.state.mapStyle}
onStyleChanged={this.onStyleChanged}
onChangeMetadataProperty={this.onChangeMetadataProperty}
isOpen={this.state.isOpen.settings}
onOpenToggle={this.toggleModal.bind(this, 'settings')}
openlayersDebugOptions={this.state.openlayersDebugOptions}
/>
<ExportModal
mapStyle={this.state.mapStyle}

View file

@ -114,6 +114,7 @@ export default class Toolbar extends React.Component {
onToggleModal: PropTypes.func,
onSetMapState: PropTypes.func,
mapState: PropTypes.string,
renderer: PropTypes.string,
}
state = {
@ -139,6 +140,7 @@ export default class Toolbar extends React.Component {
{
id: "inspect",
title: "Inspect",
disabled: this.props.renderer !== 'mbgljs',
},
{
id: "filter-deuteranopia",

View file

@ -34,6 +34,11 @@ class FeatureLayerPopup extends React.Component {
}
_getFeatureColor(feature, zoom) {
// Guard because openlayers won't have this
if (!feature.layer.paint) {
return;
}
try {
const paintProps = feature.layer.paint;
let propName;
@ -105,11 +110,13 @@ class FeatureLayerPopup extends React.Component {
this.props.onLayerSelect(feature.layer.id)
}}
>
{feature.layer.type &&
<LayerIcon type={feature.layer.type} style={{
width: 14,
height: 14,
paddingRight: 3
}}/>
}
{feature.layer.id}
{feature.counter && <span> × {feature.counter}</span>}
</label>

View file

@ -3,10 +3,26 @@ 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 {toLonLat} from 'ol/proj';
import {toStringHDMS} from 'ol/coordinate';
function renderCoords (coords) {
if (!coords || coords.length < 2) {
return null;
}
else {
return <span className="maputnik-coords">
{coords.map((coord) => String(coord).padStart(7, "\u00A0")).join(', ')}
</span>
}
}
export default class OpenLayersMap extends React.Component {
static propTypes = {
@ -14,15 +30,24 @@ export default class OpenLayersMap extends React.Component {
mapStyle: PropTypes.object.isRequired,
accessToken: PropTypes.string,
style: PropTypes.object,
onLayerSelect: PropTypes.func.isRequired,
debugToolbox: PropTypes.bool.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,34 +59,109 @@ export default class OpenLayersMap extends React.Component {
apply(this.map, newMapStyle);
}
componentDidUpdate() {
componentDidUpdate(prevProps) {
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
}
});
const map = new Map({
target: this.container,
layers: [],
overlays: [this.overlay],
view: new View({
zoom: 2,
center: [52.5, -78.4]
zoom: 1,
center: [180, -90],
})
});
map.on('pointermove', (evt) => {
var coords = toLonLat(evt.coordinate);
this.setState({
cursor: [
coords[0].toFixed(2),
coords[1].toFixed(2)
]
})
})
map.on('postrender', (evt) => {
const center = toLonLat(map.getView().getCenter());
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 <div
return <div className="maputnik-ol-container">
<div
ref={x => this.popupContainer = x}
style={{background: "black"}}
className="maputnik-popup"
>
<button
className="mapboxgl-popup-close-button"
onClick={this.closeOverlay}
aria-label="Close popup"
>
×
</button>
<FeatureLayerPopup
features={this.state.selectedFeatures || []}
onLayerSelect={this.props.onLayerSelect}
/>
</div>
<div className="maputnik-ol-zoom">
Zoom level: {this.state.zoom}
</div>
{this.props.debugToolbox &&
<div className="maputnik-ol-debug">
<div>
<label>cursor: </label>
<span>{renderCoords(this.state.cursor)}</span>
</div>
<div>
<label>center: </label>
<span>{renderCoords(this.state.center)}</span>
</div>
<div>
<label>rotation: </label>
<span>{this.state.rotation}</span>
</div>
</div>
}
<div
className="maputnik-ol"
ref={x => this.container = x}
style={{
width: "100%",
height: "100%",
backgroundColor: '#fff',
...this.props.style,
}}>
</div>
</div>
}
}

View file

@ -9,8 +9,10 @@ class DebugModal extends React.Component {
isOpen: PropTypes.bool.isRequired,
renderer: PropTypes.string.isRequired,
onChangeMaboxGlDebug: PropTypes.func.isRequired,
onChangeOpenlayersDebug: PropTypes.func.isRequired,
onOpenToggle: PropTypes.func.isRequired,
mapboxGlDebugOptions: PropTypes.object,
openlayersDebugOptions: PropTypes.object,
}
render() {
@ -33,9 +35,15 @@ class DebugModal extends React.Component {
</ul>
}
{this.props.renderer === 'ol' &&
<div>
No debug options available for the OpenLayers renderer
</div>
<ul>
{Object.entries(this.props.openlayersDebugOptions).map(([key, val]) => {
return <li key={key}>
<label>
<input type="checkbox" checked={val} onClick={(e) => this.props.onChangeOpenlayersDebug(key, e.target.checked)} /> {key}
</label>
</li>
})}
</ul>
}
</div>
</Modal>

View file

@ -11,6 +11,7 @@ class SettingsModal extends React.Component {
static propTypes = {
mapStyle: PropTypes.object.isRequired,
onStyleChanged: PropTypes.func.isRequired,
onChangeMetadataProperty: PropTypes.func.isRequired,
isOpen: PropTypes.bool.isRequired,
onOpenToggle: PropTypes.func.isRequired,
}
@ -23,19 +24,9 @@ class SettingsModal extends React.Component {
this.props.onStyleChanged(changedStyle)
}
changeMetadataProperty(property, value) {
const changedStyle = {
...this.props.mapStyle,
metadata: {
...this.props.mapStyle.metadata,
[property]: value
}
}
this.props.onStyleChanged(changedStyle)
}
render() {
const metadata = this.props.mapStyle.metadata || {}
const {onChangeMetadataProperty} = this.props;
const inputProps = { }
return <Modal
data-wd-key="modal-settings"
@ -78,7 +69,7 @@ class SettingsModal extends React.Component {
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:mapbox_access_token"
value={metadata['maputnik:mapbox_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
onChange={onChangeMetadataProperty.bind(this, "maputnik:mapbox_access_token")}
/>
</InputBlock>
@ -86,7 +77,7 @@ class SettingsModal extends React.Component {
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:openmaptiles_access_token"
value={metadata['maputnik:openmaptiles_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
onChange={onChangeMetadataProperty.bind(this, "maputnik:openmaptiles_access_token")}
/>
</InputBlock>
@ -94,7 +85,7 @@ class SettingsModal extends React.Component {
<StringInput {...inputProps}
data-wd-key="modal-settings.maputnik:thunderforest_access_token"
value={metadata['maputnik:thunderforest_access_token']}
onChange={this.changeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
onChange={onChangeMetadataProperty.bind(this, "maputnik:thunderforest_access_token")}
/>
</InputBlock>
@ -106,9 +97,10 @@ class SettingsModal extends React.Component {
['ol', 'Open Layers (experimental)'],
]}
value={metadata['maputnik:renderer'] || 'mbgljs'}
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')}
onChange={onChangeMetadataProperty.bind(this, 'maputnik:renderer')}
/>
</InputBlock>
</div>
</Modal>
}

View file

@ -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;
}

View file

@ -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;
}