mirror of
https://github.com/a-nyx/maputnik-with-pmtiles.git
synced 2024-12-28 14:31:21 +01:00
Some more openlayers improvments as well as initial work for projection support
This commit is contained in:
parent
c1cab38c7a
commit
efe42021f1
9 changed files with 267 additions and 26 deletions
|
@ -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",
|
||||
|
|
|
@ -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 = <OpenLayersMap
|
||||
{...mapProps}
|
||||
projectionCode={this.getProjectionCode()}
|
||||
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 +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 = <Toolbar
|
||||
renderer={this._getRenderer()}
|
||||
mapState={this.state.mapState}
|
||||
mapStyle={this.state.mapStyle}
|
||||
inspectModeEnabled={this.state.mapState === "inspect"}
|
||||
|
@ -603,7 +628,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')}
|
||||
/>
|
||||
|
@ -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}
|
||||
/>
|
||||
<ExportModal
|
||||
mapStyle={this.state.mapStyle}
|
||||
|
|
|
@ -139,6 +139,7 @@ export default class Toolbar extends React.Component {
|
|||
{
|
||||
id: "inspect",
|
||||
title: "Inspect",
|
||||
disabled: this.props.renderer !== 'mbgljs',
|
||||
},
|
||||
{
|
||||
id: "filter-deuteranopia",
|
||||
|
|
|
@ -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)
|
||||
}}
|
||||
>
|
||||
<LayerIcon type={feature.layer.type} style={{
|
||||
width: 14,
|
||||
height: 14,
|
||||
paddingRight: 3
|
||||
}}/>
|
||||
{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>
|
||||
|
|
|
@ -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 <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 +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 <div
|
||||
ref={x => this.container = x}
|
||||
style={{
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
backgroundColor: '#fff',
|
||||
...this.props.style,
|
||||
}}>
|
||||
return <div className="maputnik-ol-container">
|
||||
<div
|
||||
ref={x => this.popupContainer = x}
|
||||
style={{background: "black"}}
|
||||
className="maputnik-popup"
|
||||
>
|
||||
<button
|
||||
class="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={{
|
||||
...this.props.style,
|
||||
}}>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
|||
</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>
|
||||
|
|
|
@ -109,6 +109,21 @@ class SettingsModal extends React.Component {
|
|||
onChange={this.changeMetadataProperty.bind(this, 'maputnik:renderer')}
|
||||
/>
|
||||
</InputBlock>
|
||||
|
||||
{this.props.openlayersDebugOptions.enableProjections &&
|
||||
<InputBlock label={"Projection (experimental)"} doc={"Projection of the data"}>
|
||||
<SelectInput {...inputProps}
|
||||
data-wd-key="modal-settings.maputnik:projection"
|
||||
options={[
|
||||
['EPSG:3857', '[EPSG:3857] Web Mercator'],
|
||||
['EPSG:3031', '[EPSG:3031] Antarctic Polar Stereographic (experimental)'],
|
||||
]}
|
||||
value={metadata['maputnik:projection'] || 'EPSG:3857'}
|
||||
onChange={this.changeMetadataProperty.bind(this, 'maputnik:projection')}
|
||||
/>
|
||||
</InputBlock>
|
||||
}
|
||||
|
||||
</div>
|
||||
</Modal>
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue