Spaces:
Sleeping
Sleeping
| // @flow | |
| import {bindAll} from '../util/util'; | |
| import window from '../util/window'; | |
| 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: () => ?TimeoutID; | |
| _hashName: ?string; | |
| constructor(hashName: ?string) { | |
| 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; | |
| window.addEventListener('hashchange', this._onHashChange, false); | |
| this._map.on('moveend', this._updateHash); | |
| return this; | |
| } | |
| /* | |
| * Removes hash | |
| * | |
| * @returns {Popup} `this` | |
| */ | |
| remove() { | |
| window.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; | |