maplibre-gl
Version:
BSD licensed community fork of mapbox-gl, a WebGL interactive maps library
146 lines (129 loc) • 4.75 kB
text/typescript
import {bindAll} from '../util/util';
import throttle from '../util/throttle';
import type Map from './map';
/*
* Adds the map's position to its page's location hash.
* Passed as an option to the map object.
*
* @returns {Hash} `this`
*/
class Hash {
_map: Map;
_updateHash: () => ReturnType<typeof setTimeout>;
_hashName: string;
constructor(hashName?: string | null) {
this._hashName = hashName && encodeURIComponent(hashName);
bindAll([
'_getCurrentHash',
'_onHashChange',
'_updateHash'
], this);
// Mobile Safari doesn't allow updating the hash more than 100 times per 30 seconds.
this._updateHash = throttle(this._updateHashUnthrottled.bind(this), 30 * 1000 / 100);
}
/*
* Map element to listen for coordinate changes
*
* @param {Object} map
* @returns {Hash} `this`
*/
addTo(map: Map) {
this._map = map;
addEventListener('hashchange', this._onHashChange, false);
this._map.on('moveend', this._updateHash);
return this;
}
/*
* Removes hash
*
* @returns {Popup} `this`
*/
remove() {
removeEventListener('hashchange', this._onHashChange, false);
this._map.off('moveend', this._updateHash);
clearTimeout(this._updateHash());
delete this._map;
return this;
}
getHashString(mapFeedback?: boolean) {
const center = this._map.getCenter(),
zoom = Math.round(this._map.getZoom() * 100) / 100,
// derived from equation: 512px * 2^z / 360 / 10^d < 0.5px
precision = Math.ceil((zoom * Math.LN2 + Math.log(512 / 360 / 0.5)) / Math.LN10),
m = Math.pow(10, precision),
lng = Math.round(center.lng * m) / m,
lat = Math.round(center.lat * m) / m,
bearing = this._map.getBearing(),
pitch = this._map.getPitch();
let hash = '';
if (mapFeedback) {
// new map feedback site has some constraints that don't allow
// us to use the same hash format as we do for the Map hash option.
hash += `/${lng}/${lat}/${zoom}`;
} else {
hash += `${zoom}/${lat}/${lng}`;
}
if (bearing || pitch) hash += (`/${Math.round(bearing * 10) / 10}`);
if (pitch) hash += (`/${Math.round(pitch)}`);
if (this._hashName) {
const hashName = this._hashName;
let found = false;
const parts = window.location.hash.slice(1).split('&').map(part => {
const key = part.split('=')[0];
if (key === hashName) {
found = true;
return `${key}=${hash}`;
}
return part;
}).filter(a => a);
if (!found) {
parts.push(`${hashName}=${hash}`);
}
return `#${parts.join('&')}`;
}
return `#${hash}`;
}
_getCurrentHash() {
// Get the current hash from location, stripped from its number sign
const hash = window.location.hash.replace('#', '');
if (this._hashName) {
// Split the parameter-styled hash into parts and find the value we need
let keyval;
hash.split('&').map(
part => part.split('=')
).forEach(part => {
if (part[0] === this._hashName) {
keyval = part;
}
});
return (keyval ? keyval[1] || '' : '').split('/');
}
return hash.split('/');
}
_onHashChange() {
const loc = this._getCurrentHash();
if (loc.length >= 3 && !loc.some(v => isNaN(v))) {
const bearing = this._map.dragRotate.isEnabled() && this._map.touchZoomRotate.isEnabled() ? +(loc[3] || 0) : this._map.getBearing();
this._map.jumpTo({
center: [+loc[2], +loc[1]],
zoom: +loc[0],
bearing,
pitch: +(loc[4] || 0)
});
return true;
}
return false;
}
_updateHashUnthrottled() {
// Replace if already present, else append the updated hash string
const location = window.location.href.replace(/(#.+)?$/, this.getHashString());
try {
window.history.replaceState(window.history.state, null, location);
} catch (SecurityError) {
// IE11 does not allow this if the page is within an iframe created
// with iframe.contentWindow.document.write(...).
// https://github.com/mapbox/mapbox-gl-js/issues/7410
}
}
}
export default Hash;