332 lines
11 KiB
JavaScript
332 lines
11 KiB
JavaScript
// Following https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md
|
|
(function (factory, window) {
|
|
|
|
// define an AMD module that relies on 'leaflet'
|
|
if (typeof define === 'function' && define.amd) {
|
|
define(['leaflet'], factory);
|
|
|
|
// define a Common JS module that relies on 'leaflet'
|
|
} else if (typeof exports === 'object') {
|
|
module.exports = factory(require('leaflet'));
|
|
}
|
|
|
|
// attach your plugin to the global 'L' variable
|
|
if (typeof window !== 'undefined' && window.L) {
|
|
window.L.Control.MiniMap = factory(L);
|
|
window.L.control.minimap = function (layer, options) {
|
|
return new window.L.Control.MiniMap(layer, options);
|
|
};
|
|
}
|
|
}(function (L) {
|
|
|
|
var MiniMap = L.Control.extend({
|
|
options: {
|
|
position: 'bottomright',
|
|
toggleDisplay: false,
|
|
zoomLevelOffset: -5,
|
|
zoomLevelFixed: false,
|
|
centerFixed: false,
|
|
zoomAnimation: false,
|
|
autoToggleDisplay: false,
|
|
minimized: false,
|
|
width: 150,
|
|
height: 150,
|
|
collapsedWidth: 19,
|
|
collapsedHeight: 19,
|
|
aimingRectOptions: {color: '#ff7800', weight: 1, clickable: false},
|
|
shadowRectOptions: {color: '#000000', weight: 1, clickable: false, opacity: 0, fillOpacity: 0},
|
|
strings: {hideText: 'Hide MiniMap', showText: 'Show MiniMap'},
|
|
mapOptions: {} // Allows definition / override of Leaflet map options.
|
|
},
|
|
|
|
// layer is the map layer to be shown in the minimap
|
|
initialize: function (layer, options) {
|
|
L.Util.setOptions(this, options);
|
|
// Make sure the aiming rects are non-clickable even if the user tries to set them clickable (most likely by forgetting to specify them false)
|
|
this.options.aimingRectOptions.clickable = false;
|
|
this.options.shadowRectOptions.clickable = false;
|
|
this._layer = layer;
|
|
},
|
|
|
|
onAdd: function (map) {
|
|
|
|
this._mainMap = map;
|
|
|
|
// Creating the container and stopping events from spilling through to the main map.
|
|
this._container = L.DomUtil.create('div', 'leaflet-control-minimap');
|
|
this._container.style.width = this.options.width + 'px';
|
|
this._container.style.height = this.options.height + 'px';
|
|
L.DomEvent.disableClickPropagation(this._container);
|
|
L.DomEvent.on(this._container, 'mousewheel', L.DomEvent.stopPropagation);
|
|
|
|
var mapOptions = {
|
|
attributionControl: false,
|
|
dragging: !this.options.centerFixed,
|
|
zoomControl: false,
|
|
zoomAnimation: this.options.zoomAnimation,
|
|
autoToggleDisplay: this.options.autoToggleDisplay,
|
|
touchZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
|
|
scrollWheelZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
|
|
doubleClickZoom: this.options.centerFixed ? 'center' : !this._isZoomLevelFixed(),
|
|
boxZoom: !this._isZoomLevelFixed(),
|
|
crs: map.options.crs
|
|
};
|
|
mapOptions = L.Util.extend(this.options.mapOptions, mapOptions); // merge with priority of the local mapOptions object.
|
|
|
|
this._miniMap = new L.Map(this._container, mapOptions);
|
|
|
|
this._miniMap.addLayer(this._layer);
|
|
|
|
// These bools are used to prevent infinite loops of the two maps notifying each other that they've moved.
|
|
this._mainMapMoving = false;
|
|
this._miniMapMoving = false;
|
|
|
|
// Keep a record of this to prevent auto toggling when the user explicitly doesn't want it.
|
|
this._userToggledDisplay = false;
|
|
this._minimized = false;
|
|
|
|
if (this.options.toggleDisplay) {
|
|
this._addToggleButton();
|
|
}
|
|
|
|
this._miniMap.whenReady(L.Util.bind(function () {
|
|
this._aimingRect = L.rectangle(this._mainMap.getBounds(), this.options.aimingRectOptions).addTo(this._miniMap);
|
|
this._shadowRect = L.rectangle(this._mainMap.getBounds(), this.options.shadowRectOptions).addTo(this._miniMap);
|
|
this._mainMap.on('moveend', this._onMainMapMoved, this);
|
|
this._mainMap.on('move', this._onMainMapMoving, this);
|
|
this._miniMap.on('movestart', this._onMiniMapMoveStarted, this);
|
|
this._miniMap.on('move', this._onMiniMapMoving, this);
|
|
this._miniMap.on('moveend', this._onMiniMapMoved, this);
|
|
}, this));
|
|
|
|
return this._container;
|
|
},
|
|
|
|
addTo: function (map) {
|
|
L.Control.prototype.addTo.call(this, map);
|
|
|
|
var center = this.options.centerFixed || this._mainMap.getCenter();
|
|
this._miniMap.setView(center, this._decideZoom(true));
|
|
this._setDisplay(this.options.minimized);
|
|
return this;
|
|
},
|
|
|
|
onRemove: function (map) {
|
|
this._mainMap.off('moveend', this._onMainMapMoved, this);
|
|
this._mainMap.off('move', this._onMainMapMoving, this);
|
|
this._miniMap.off('moveend', this._onMiniMapMoved, this);
|
|
|
|
this._miniMap.removeLayer(this._layer);
|
|
},
|
|
|
|
changeLayer: function (layer) {
|
|
this._miniMap.removeLayer(this._layer);
|
|
this._layer = layer;
|
|
this._miniMap.addLayer(this._layer);
|
|
},
|
|
|
|
_addToggleButton: function () {
|
|
this._toggleDisplayButton = this.options.toggleDisplay ? this._createButton(
|
|
'', this._toggleButtonInitialTitleText(), ('leaflet-control-minimap-toggle-display leaflet-control-minimap-toggle-display-' +
|
|
this.options.position), this._container, this._toggleDisplayButtonClicked, this) : undefined;
|
|
|
|
this._toggleDisplayButton.style.width = this.options.collapsedWidth + 'px';
|
|
this._toggleDisplayButton.style.height = this.options.collapsedHeight + 'px';
|
|
},
|
|
|
|
_toggleButtonInitialTitleText: function () {
|
|
if (this.options.minimized) {
|
|
return this.options.strings.showText;
|
|
} else {
|
|
return this.options.strings.hideText;
|
|
}
|
|
},
|
|
|
|
_createButton: function (html, title, className, container, fn, context) {
|
|
var link = L.DomUtil.create('a', className, container);
|
|
link.innerHTML = html;
|
|
link.href = '#';
|
|
link.title = title;
|
|
|
|
var stop = L.DomEvent.stopPropagation;
|
|
|
|
L.DomEvent
|
|
.on(link, 'click', stop)
|
|
.on(link, 'mousedown', stop)
|
|
.on(link, 'dblclick', stop)
|
|
.on(link, 'click', L.DomEvent.preventDefault)
|
|
.on(link, 'click', fn, context);
|
|
|
|
return link;
|
|
},
|
|
|
|
_toggleDisplayButtonClicked: function () {
|
|
this._userToggledDisplay = true;
|
|
if (!this._minimized) {
|
|
this._minimize();
|
|
} else {
|
|
this._restore();
|
|
}
|
|
},
|
|
|
|
_setDisplay: function (minimize) {
|
|
if (minimize !== this._minimized) {
|
|
if (!this._minimized) {
|
|
this._minimize();
|
|
} else {
|
|
this._restore();
|
|
}
|
|
}
|
|
},
|
|
|
|
_minimize: function () {
|
|
// hide the minimap
|
|
if (this.options.toggleDisplay) {
|
|
this._container.style.width = this.options.collapsedWidth + 'px';
|
|
this._container.style.height = this.options.collapsedHeight + 'px';
|
|
this._toggleDisplayButton.className += (' minimized-' + this.options.position);
|
|
this._toggleDisplayButton.title = this.options.strings.showText;
|
|
} else {
|
|
this._container.style.display = 'none';
|
|
}
|
|
this._minimized = true;
|
|
},
|
|
|
|
_restore: function () {
|
|
if (this.options.toggleDisplay) {
|
|
this._container.style.width = this.options.width + 'px';
|
|
this._container.style.height = this.options.height + 'px';
|
|
this._toggleDisplayButton.className = this._toggleDisplayButton.className
|
|
.replace('minimized-' + this.options.position, '');
|
|
this._toggleDisplayButton.title = this.options.strings.hideText;
|
|
} else {
|
|
this._container.style.display = 'block';
|
|
}
|
|
this._minimized = false;
|
|
},
|
|
|
|
_onMainMapMoved: function (e) {
|
|
if (!this._miniMapMoving) {
|
|
var center = this.options.centerFixed || this._mainMap.getCenter();
|
|
|
|
this._mainMapMoving = true;
|
|
this._miniMap.setView(center, this._decideZoom(true));
|
|
this._setDisplay(this._decideMinimized());
|
|
} else {
|
|
this._miniMapMoving = false;
|
|
}
|
|
this._aimingRect.setBounds(this._mainMap.getBounds());
|
|
},
|
|
|
|
_onMainMapMoving: function (e) {
|
|
this._aimingRect.setBounds(this._mainMap.getBounds());
|
|
},
|
|
|
|
_onMiniMapMoveStarted: function (e) {
|
|
if (!this.options.centerFixed) {
|
|
var lastAimingRect = this._aimingRect.getBounds();
|
|
var sw = this._miniMap.latLngToContainerPoint(lastAimingRect.getSouthWest());
|
|
var ne = this._miniMap.latLngToContainerPoint(lastAimingRect.getNorthEast());
|
|
this._lastAimingRectPosition = {sw: sw, ne: ne};
|
|
}
|
|
},
|
|
|
|
_onMiniMapMoving: function (e) {
|
|
if (!this.options.centerFixed) {
|
|
if (!this._mainMapMoving && this._lastAimingRectPosition) {
|
|
this._shadowRect.setBounds(new L.LatLngBounds(this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.sw), this._miniMap.containerPointToLatLng(this._lastAimingRectPosition.ne)));
|
|
this._shadowRect.setStyle({opacity: 1, fillOpacity: 0.3});
|
|
}
|
|
}
|
|
},
|
|
|
|
_onMiniMapMoved: function (e) {
|
|
if (!this._mainMapMoving) {
|
|
this._miniMapMoving = true;
|
|
this._mainMap.setView(this._miniMap.getCenter(), this._decideZoom(false));
|
|
this._shadowRect.setStyle({opacity: 0, fillOpacity: 0});
|
|
} else {
|
|
this._mainMapMoving = false;
|
|
}
|
|
},
|
|
|
|
_isZoomLevelFixed: function () {
|
|
var zoomLevelFixed = this.options.zoomLevelFixed;
|
|
return this._isDefined(zoomLevelFixed) && this._isInteger(zoomLevelFixed);
|
|
},
|
|
|
|
_decideZoom: function (fromMaintoMini) {
|
|
if (!this._isZoomLevelFixed()) {
|
|
if (fromMaintoMini) {
|
|
return this._mainMap.getZoom() + this.options.zoomLevelOffset;
|
|
} else {
|
|
var currentDiff = this._miniMap.getZoom() - this._mainMap.getZoom();
|
|
var proposedZoom = this._miniMap.getZoom() - this.options.zoomLevelOffset;
|
|
var toRet;
|
|
|
|
if (currentDiff > this.options.zoomLevelOffset && this._mainMap.getZoom() < this._miniMap.getMinZoom() - this.options.zoomLevelOffset) {
|
|
// This means the miniMap is zoomed out to the minimum zoom level and can't zoom any more.
|
|
if (this._miniMap.getZoom() > this._lastMiniMapZoom) {
|
|
// This means the user is trying to zoom in by using the minimap, zoom the main map.
|
|
toRet = this._mainMap.getZoom() + 1;
|
|
// Also we cheat and zoom the minimap out again to keep it visually consistent.
|
|
this._miniMap.setZoom(this._miniMap.getZoom() - 1);
|
|
} else {
|
|
// Either the user is trying to zoom out past the mini map's min zoom or has just panned using it, we can't tell the difference.
|
|
// Therefore, we ignore it!
|
|
toRet = this._mainMap.getZoom();
|
|
}
|
|
} else {
|
|
// This is what happens in the majority of cases, and always if you configure the min levels + offset in a sane fashion.
|
|
toRet = proposedZoom;
|
|
}
|
|
this._lastMiniMapZoom = this._miniMap.getZoom();
|
|
return toRet;
|
|
}
|
|
} else {
|
|
if (fromMaintoMini) {
|
|
return this.options.zoomLevelFixed;
|
|
} else {
|
|
return this._mainMap.getZoom();
|
|
}
|
|
}
|
|
},
|
|
|
|
_decideMinimized: function () {
|
|
if (this._userToggledDisplay) {
|
|
return this._minimized;
|
|
}
|
|
|
|
if (this.options.autoToggleDisplay) {
|
|
if (this._mainMap.getBounds().contains(this._miniMap.getBounds())) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return this._minimized;
|
|
},
|
|
|
|
_isInteger: function (value) {
|
|
return typeof value === 'number';
|
|
},
|
|
|
|
_isDefined: function (value) {
|
|
return typeof value !== 'undefined';
|
|
}
|
|
});
|
|
|
|
L.Map.mergeOptions({
|
|
miniMapControl: false
|
|
});
|
|
|
|
L.Map.addInitHook(function () {
|
|
if (this.options.miniMapControl) {
|
|
this.miniMapControl = (new MiniMap()).addTo(this);
|
|
}
|
|
});
|
|
|
|
return MiniMap;
|
|
|
|
}, window));
|