Skip to content

Commit

Permalink
WIP for wegue-oss#38 - permalink control - add extent and layers
Browse files Browse the repository at this point in the history
  • Loading branch information
justb4 committed Mar 25, 2020
1 parent 3728905 commit 89bd794
Show file tree
Hide file tree
Showing 5 changed files with 157 additions and 38 deletions.
8 changes: 4 additions & 4 deletions src/components/ol/Map.vue
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,15 @@ export default {
})
});
// create layers from config and add them to map
const layers = this.createLayers();
this.map.getLayers().extend(layers);
if (this.$appConfig.permalink) {
this.permalinkController = this.createPermalinkController();
this.permalinkController.apply();
this.permalinkController.setup();
}
// create layers from config and add them to map
const layers = this.createLayers();
this.map.getLayers().extend(layers);
},
methods: {
Expand Down
171 changes: 142 additions & 29 deletions src/components/ol/PermalinkController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import Projection from 'ol/proj/Projection';
import {transform} from 'ol/proj';
import {getTransform, transform} from 'ol/proj';
import UrlUtil from '../../util/Url';
import {applyTransform} from 'ol/extent';

/**
* Class holding the logic for permalinks.
Expand All @@ -20,6 +21,10 @@ export default class PermalinkController {
this.conf.paramPrefix = this.conf.paramPrefix || '';
this.conf.location = this.conf.location || 'hash';
this.conf.separator = this.conf.location === 'hash' ? '#' : '?';
this.conf.history = this.conf.history ? this.conf.history : false;
this.conf.extent = this.conf.extent ? this.conf.extent : false;
this.conf.layers = this.conf.layers ? this.conf.layers : false;
this.conf.precision = this.conf.precision ? this.conf.precision : 4;
this.urlParams = UrlUtil.getParams(this.conf.location);
}

Expand All @@ -39,90 +44,198 @@ export default class PermalinkController {
if (this.conf.history === false) {
return;
}
// restore the view state when navigating through the history, see

// restore the view state when navigating through the history (browser back/forward buttons), see
// https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onpopstate
window.addEventListener('popstate', (event) => {
if (event.state === null) {
return;
}

const view = this.map.getView();
let center = event.state.center;
if (this.projection) {
center = transform(center, this.projection, view.getProjection());
}
const state = event.state;

this.shouldUpdate = false;
view.setCenter(center);
view.setZoom(event.state.zoom);
view.setRotation(event.state.rotation);

view.setRotation(state.rotation);

// Use extent (bbox) or center+zoom based on config
if (this.conf.extent) {
this.applyExtent(state.extent);
} else {
this.applyCenter(state.center);
}

// somehow we also need zoom (or resolution) for extent:
// See: https://stackoverflow.com/questions/47770782/openlayers-fit-to-current-extent-is-zooming-out
view.setZoom(state.zoom);

if (this.conf.layers) {
this.applyLayers(new Map(state.layers.map(lid => [lid, lid])));
}
});
}

/**
* Applies map (View) rotation, center+zoom-level or extent
* from permalink params in current URL 'location'
*/
apply () {
const permalinkParams = UrlUtil.getParams(this.conf.location);
const prefix = this.conf.paramPrefix;

// try to modify center, zoom-level and rotation from permalink params in URL
const mapView = this.map.getView();
const r = `${prefix}r`;
const c = `${prefix}c`;
const e = `${prefix}e`;
const z = `${prefix}z`;
const l = `${prefix}l`;

if (permalinkParams[r]) {
mapView.setRotation(parseFloat(permalinkParams[r]));
}

// Permalink coordinates may have specific Projection like WGS84
if (permalinkParams[`${prefix}c`]) {
let centerMod = permalinkParams[`${prefix}c`].split(',').map((n) => {
// Both extent (bbox) or center+zoom supported.
if (permalinkParams[e]) {
let extent = permalinkParams[e].split(',').map((n) => {
return parseFloat(n);
});
this.applyExtent(extent);
} else if (permalinkParams[c]) {
let center = permalinkParams[c].split(',').map((n) => {
return parseFloat(n);
});
this.applyCenter(center);
}

if (this.projection) {
centerMod = transform(centerMod, this.projection, mapView.getProjection())
}
// Always set zoom, even with extent
// See: https://stackoverflow.com/questions/47770782/openlayers-fit-to-current-extent-is-zooming-out
if (permalinkParams[z]) {
mapView.setZoom(parseFloat(permalinkParams[z]));
}

mapView.setCenter(centerMod);
// Set layer(s) visible
if (permalinkParams[l]) {
this.applyLayers(new Map(permalinkParams[l].split(',').map(lid => [lid, lid])));
}
}

/**
* Make only the layers for given array of layer ids visible.
*/
applyLayers (layers) {
if (!layers) {
return;
}
this.map.getLayers().forEach((layer) => {
const layerId = layer.get('lid');
layer.setVisible(layerId === layers.get(layerId));
})
}

/**
* Position map at provided center.
*/
applyCenter (center) {
if (!center) {
return;
}
const mapView = this.map.getView();

if (permalinkParams[`${prefix}z`]) {
mapView.setZoom(parseInt(permalinkParams[`${prefix}z`]));
// Permalink coordinates may have specific Projection like WGS84
if (this.projection) {
center = transform(center, this.projection, mapView.getProjection())
}

if (permalinkParams[`${prefix}r`]) {
mapView.setRotation(parseFloat(permalinkParams[`${prefix}r`]));
mapView.setCenter(center);
}

/**
* Position map at provided map extent.
*/
applyExtent (extent) {
if (!extent) {
return;
}
const mapView = this.map.getView();
// Permalink coordinates may have specific Projection like WGS84
if (this.projection) {
extent = applyTransform(extent, getTransform(this.projection, mapView.getProjection()))
}

// Fit the map in extent
mapView.fit(extent, this.map.getSize());
}

/**
* Get the URL parameter permalink string as query or hash.
*/
getParamStr () {
const round = (num, places) => {
return +(Math.round(num + 'e+' + places) + 'e-' + places);
};

const state = this.getState();
const prefix = this.conf.paramPrefix || '';
const prefix = this.conf.paramPrefix;
const prec = this.conf.precision;

// Use extent (bbox) or center+zoom based on config
this.urlParams[`${prefix}z`] = `${round(state.zoom, prec)}`;
if (this.conf.extent) {
this.urlParams[`${prefix}e`] = state.extent.map(n => round(n, prec)).join(',');
} else {
this.urlParams[`${prefix}c`] = state.center.map(n => round(n, prec)).join(',');
}
this.urlParams[`${prefix}r`] = `${round(state.rotation, prec)}`;

this.urlParams[`${prefix}z`] = `${round(state.zoom, 4)}`;
this.urlParams[`${prefix}c`] = `${round(state.center[0], 4)},${round(state.center[1], 4)}`;
this.urlParams[`${prefix}r`] = `${round(state.rotation, 4)}`;
if (this.conf.layers) {
this.urlParams[`${prefix}l`] = state.layers.join(',');
}

return this.conf.separator + UrlUtil.toQueryString(this.urlParams);
}

/**
* Get array of visible layer id's.
*/
getLayerIds () {
return this.map.getLayers().getArray().filter(layer => !!layer.get('lid') && layer.getVisible()).map(layer => layer.get('lid'));
}

/**
* Get total State of the Map.
*/
getState () {
const mapView = this.map.getView();
let center = mapView.getCenter();
let extent = mapView.calculateExtent();

// Optionally reproject to permalink projection (e.g. WGS84 on WebMerc).
if (this.projection) {
center = transform(center, mapView.getProjection(), this.projection);
extent = applyTransform(extent, getTransform(mapView.getProjection(), this.projection));
}
return {
zoom: mapView.getZoom(),
center: center,
rotation: mapView.getRotation()
extent: extent,
rotation: mapView.getRotation(),
layers: this.getLayerIds()
};
}

/**
* Callback when Map View has changed, e.g. 'moveend'.
*/
onMapChange () {
if (!this.shouldUpdate) {
// do not update the URL when the view was changed in the 'popstate' handler
this.shouldUpdate = true;
return;
}
if (this.conf.history === true) {
window.history.pushState(this.getState(), 'map', this.getParamStr());
if (this.conf.history === false) {
return;
}
// This changes the URL in address bar.
window.history.pushState(this.getState(), 'map', this.getParamStr());
}
}
7 changes: 4 additions & 3 deletions src/factory/Layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,11 @@ export const LayerFactory = {
* @return {ol.layer.Base} OL layer instance
*/
getInstance (lConf) {
// apply LID (Layer ID) if not existent
// Generate Layer Id (lid) if not existent
if (!lConf.lid) {
var now = new Date();
lConf.lid = now.getTime();
// Make a unique layerId from Layer name and URL so contexts
// like permalinks can be reapplied.
lConf.lid = btoa(lConf.url + lConf.name).substr(0, 6);
}

// create correct layer type
Expand Down
5 changes: 4 additions & 1 deletion static/app-conf-projected.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,11 @@

"permalink": {
"location": "search",
"layers": true,
"extent": true,
"paramPrefix": "pl_",
"history": true
"history": true,
"precision": 6
},

"mapLayers": [
Expand Down
4 changes: 3 additions & 1 deletion static/app-conf.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

"permalink": {
"location": "hash",
"layers": true,
"extent": false,
"projection": "EPSG:4326",
"paramPrefix": "",
"history": true
Expand Down Expand Up @@ -98,7 +100,7 @@
"url": "https://tile.opentopomap.org/{z}/{x}/{y}.png",
"attributions": "Map data: <a href=\"https://openstreetmap.org/copyright\">©OpenStreetMap</a>-contributors, SRTM | Map representation (Kartendarstellung): © <a href=\"http://opentopomap.org/\">OpenTopoMap</a> (<a href=\"https://creativecommons.org/licenses/by-sa/3.0/\">CC-BY-SA</a>)",
"lid": "opentopomap",
"displayInLayerList": true,
"displayInLayerList": true,
"visible": false
},

Expand Down

0 comments on commit 89bd794

Please sign in to comment.