ember-leaflet
Version:
Easy and declarative mapping for ember.
764 lines (668 loc) • 19.3 kB
JavaScript
import { assert } from '@ember/debug';
import { inject as service } from '@ember/service';
import BaseLayer from 'ember-leaflet/components/base-layer';
import { action } from '@ember/object';
import TileLayer from './tile-layer';
import WmsTileLayer from './wms-tile-layer';
import MarkerLayer from './marker-layer';
import CircleLayer from './circle-layer';
import CircleMarkerLayer from './circle-marker-layer';
import ImageLayer from './image-layer';
import VideoLayer from './video-layer';
import PolylineLayer from './polyline-layer';
import PolygonLayer from './polygon-layer';
import GeojsonLayer from './geojson-layer';
import RectangleLayer from './rectangle-layer';
/**
* The central class of ember-leaflet — it is used to create a map on a page and manipulate it.
*
* @class LeafletMap
* @extends BaseLayer
*
* @yield {TileLayer} tile-layer
* @yield {WmsTileLayer} wms-tile-layer
* @yield {MarkerLayer} marker-layer
* @yield {CircleLayer} circle-layer
* @yield {CircleMarkerLayer} circle-marker-layer
* @yield {ImageLayer} image-layer
* @yield {VideoLayer} video-layer
* @yield {PolylineLayer} polyline-layer
* @yield {PolygonLayer} polygon-layer
* @yield {GeojsonLayer} geojson-layer
* @yield {RectangleLayer} rectangle-layer
*/
export default class LeafletMap extends BaseLayer {
emberLeaflet;
mergeComponents(obj) {
if (!this.mergedComponents) {
this.mergedComponents = obj;
} else {
Object.assign(this.mergedComponents, obj);
}
}
leafletOptions = [
...this.leafletOptions,
// Map state options
/**
* Initial geographic center of the map
*
* @argument center
* @type {LatLng}
*/
'center',
/**
* Initial map zoom level
*
* @argument zoom
* @type {Number}
*/
'zoom',
/**
* Minimum zoom level of the map.
*
* @argument minZoom
* @type {Number}
*/
'minZoom',
/**
* Maximum zoom level of the map.
*
* @argument maxZoom
* @type {Number}
*/
'maxZoom',
/**
* When this option is set, the map restricts the view to the given geographical bounds,
* ouncing the user back if the user tries to pan outside the view.
*
* @argument maxBounds
* @type {LatLngBounds}
*/
'maxBounds',
/**
* The Coordinate Reference System to use. Don't change this if you're not sure what it means.
*
* @argument crs
* @type {CRS}
*/
'crs',
// Interaction options
/**
* Set it to false if you don't want popups to close when user clicks the map.
* Defaults to `true`.
*
* @argument closePopupOnClick
* @type {Boolean}
*/
'closePopupOnClick',
/**
* Forces the map's zoom level to always be a multiple of this.
* By default, the zoom level snaps to the nearest integer;
* lower values (e.g. 0.5 or 0.1) allow for greater granularity.
* A value of 0 means the zoom level will not be snapped.
* Defaults to `1`.
*
* @argument zoomSnap
* @type {Number}
*/
'zoomSnap',
/**
* Controls how much the map's zoom level will change after a zoomIn(), zoomOut(),
* pressing + or - on the keyboard, or using the zoom controls.
* Values smaller than 1 (e.g. 0.5) allow for greater granularity.
* Defaults to `1`.
*
* @argument zoomDelta
* @type {Number}
*/
'zoomDelta',
/**
* Whether the map automatically handles browser window resize to update itself.
* Defaults to `true`.
*
* @argument trackResize
* @type {Boolean}
*/
'trackResize',
/**
* Whether the map can be zoomed to a rectangular area specified by dragging
* the mouse while pressing the shift key.
* Defaults to `true`.
*
* @argument trackResize
* @type {Boolean}
*/
'boxZoom',
/**
* Whether the map can be zoomed in by double clicking on it and zoomed out
* by double clicking while holding shift. If passed 'center', double-click zoom
* will zoom to the center of the view regardless of where the mouse was.
* Defaults to `true`.
*
* @argument doubleClickZoom
* @type {Boolean|String}
*/
'doubleClickZoom',
/**
* Whether the map be draggable with mouse/touch or not.
* Defaults to `true`.
*
* @argument dragging
* @type {Boolean}
*/
'dragging',
// Touch interaction options
/**
* Enables mobile hacks for supporting instant taps (fixing 200ms click delay
* on iOS/Android) and touch holds (fired as contextmenu events).
* Defaults to `true`.
*
* @argument tap
* @type {Boolean}
*/
'tap',
/**
* The max number of pixels a user can shift his finger during touch for it to be
* considered a valid tap.
* Defaults to `15`.
*
* @argument tapTolerance
* @type {Number}
*/
'tapTolerance',
/**
* Whether the map be draggable with mouse/touch or not.
* Defaults to `*`.
*
* @argument touchZoom
* @type {Boolean|String}
*/
'touchZoom',
/**
* Set it to false if you don't want the map to zoom beyond min/max zoom
* and then bounce back when pinch-zooming.
* Defaults to `true`.
*
* @argument bounceAtZoomLimits
* @type {Boolean}
*/
'bounceAtZoomLimits',
// Mouse wheel options
/**
* Whether the map can be zoomed by using the mouse wheel. If passed 'center',
* it will zoom to the center of the view regardless of where the mouse was.
* Defaults to `true`.
*
* @argument scrollWheelZoom
* @type {Boolean|String}
*/
'scrollWheelZoom',
/**
* Limits the rate at which a wheel can fire (in milliseconds).
* By default user can't zoom via wheel more often than once per 40 ms.
* Defaults to `40`.
*
* @argument wheelDebounceTime
* @type {Number}
*/
'wheelDebounceTime',
/**
* How many scroll pixels mean a change of one full zoom level.
* Smaller values will make wheel-zooming faster (and vice versa).
* Defaults to `60`.
*
* @argument wheelPxPerZoomLevel
* @type {Number}
*/
'wheelPxPerZoomLevel',
// Keyboard navigation options
/**
* Makes the map focusable and allows users to navigate the map with keyboard arrows and +/- keys.
* Defaults to `true`.
*
* @argument keyboard
* @type {Boolean}
*/
'keyboard',
/**
* Amount of pixels to pan when pressing an arrow key.
* Defaults to `80`.
*
* @argument keyboardPanDelta
* @type {Number}
*/
'keyboardPanDelta',
// Panning Inertia Options
/**
* If enabled, panning of the map will have an inertia effect where
* the map builds momentum while dragging and continues moving in the
* same direction for some time. Feels especially nice on touch devices.
* Enabled by default unless running on old Android devices.
* Defaults to `*`.
*
* @argument inertia
* @type {Boolean}
*/
'inertia',
/**
* The rate with which the inertial movement slows down, in pixels/second².
* Defaults to `3000`.
*
* @argument inertiaDeceleration
* @type {Number}
*/
'inertiaDeceleration',
/**
* Max speed of the inertial movement, in pixels/second.
* Defaults to `Infinity`.
*
* @argument inertiaMaxSpeed
* @type {Number}
*/
'inertiaMaxSpeed',
/**
* Defaults to `0.2`.
*
* @argument easeLinearity
* @type {Number}
*/
'easeLinearity',
/**
* With this option enabled, the map tracks when you pan to another "copy"
* of the world and seamlessly jumps to the original one so that all overlays
* like markers and vector layers are still visible.
* Defaults to `false`.
*
* @argument worldCopyJump
* @type {Boolean}
*/
'worldCopyJump',
/**
* If maxBounds is set, this option will control how solid the bounds are when
* dragging the map around. The default value of 0.0 allows the user to drag outside
* the bounds at normal speed, higher values will slow down map dragging outside bounds,
* and 1.0 makes the bounds fully solid, preventing the user from dragging outside the bounds.
* Defaults to `0.0`.
*
* @argument maxBoundsViscosity
* @type {Number}
*/
'maxBoundsViscosity',
// Control options
/**
* Whether a attribution control is added to the map by default.
* Defaults to `true`.
*
* @argument attributionControl
* @type {Boolean}
*/
'attributionControl',
/**
* Whether a zoom control is added to the map by default.
* Defaults to `true`.
*
* @argument zoomControl
* @type {Boolean}
*/
'zoomControl',
// Animation options
/**
* Whether the map zoom animation is enabled. By default it's
* enabled in all browsers that support CSS3 Transitions except Android.
* Defaults to `true`.
*
* @argument zoomAnimation
* @type {Boolean}
*/
'zoomAnimation',
/**
* Won't animate zoom if the zoom difference exceeds this value.
* Defaults to `4`.
*
* @argument zoomAnimationThreshold
* @type {Number}
*/
'zoomAnimationThreshold',
/**
* Whether the tile fade animation is enabled. By default it's
* enabled in all browsers that support CSS3 Transitions except Android.
* Defaults to `true`.
*
* @argument fadeAnimation
* @type {Boolean}
*/
'fadeAnimation',
/**
* Whether markers animate their zoom with the zoom animation, if disabled they will
* disappear for the length of the animation. By default it's enabled in all
* browsers that support CSS3 Transitions except Android.
* Defaults to `true`.
*
* @argument markerZoomAnimation
* @type {Boolean}
*/
'markerZoomAnimation',
/**
* Defines the maximum size of a CSS translation transform.
* The default value should not be changed unless a web browser positions
* layers in the wrong place after doing a large panBy.
* Defaults to `2^23`.
*
* @argument transform3DLimit
* @type {Boolean}
*/
'transform3DLimit'
];
// Events this map can respond to.
leafletEvents = [
...this.leafletEvents,
// Layer events
/**
* Fired when the base layer is changed through the layers control.
*
* @argument onBaselayerchange
* @type {Function}
*/
'baselayerchange',
/**
* Fired when an overlay is selected through the layers control.
*
* @argument onOverlayadd
* @type {Function}
*/
'overlayadd',
/**
* Fired when an overlay is deselected through the layers control.
*
* @argument onOverlayremove
* @type {Function}
*/
'overlayremove',
/**
* Fired when a new layer is added to the map.
*
* @argument onLayeradd
* @type {Function}
*/
'layeradd',
/**
* Fired when some layer is removed from the map
*
* @argument onLayerremove
* @type {Function}
*/
'layerremove',
// Map state change events
/**
* Fired when the number of zoomlevels on the map is changed due to adding or removing a layer.
*
* @argument onZoomlevelschange
* @type {Function}
*/
'zoomlevelschange',
/**
* Fired when the map is resized.
*
* @argument onResize
* @type {Function}
*/
'resize',
/**
* Fired when the map is destroyed with remove method.
*
* @argument onUnload
* @type {Function}
*/
'unload',
/**
* Fired when the map needs to redraw its content
* (this usually happens on map zoom or load).
* Very useful for creating custom overlays.
*
* @argument onViewreset
* @type {Function}
*/
'viewreset',
/**
* Fired when the map is initialized (when its center and zoom are set for the first time).
*
* @argument onLoad
* @type {Function}
*/
'load',
/**
* Fired when the map zoom is about to change (e.g. before zoom animation).
*
* @argument onZoomstart
* @type {Function}
*/
'zoomstart',
/**
* Fired when the view of the map starts changing (e.g. user starts dragging the map).
*
* @argument onMovestart
* @type {Function}
*/
'movestart',
/**
* Fired repeatedly during any change in zoom level, including zoom and fly animations.
*
* @argument onZoom
* @type {Function}
*/
'zoom',
/**
* Fired repeatedly during any movement of the map, including pan and fly animations.
*
* @argument onMove
* @type {Function}
*/
'move',
/**
* Fired when the map has changed, after any animations.
*
* @argument onZoomend
* @type {Function}
*/
'zoomend',
/**
* Fired when the center of the map stops changing
* (e.g. user stopped dragging the map).
*
* @argument onMoveend
* @type {Function}
*/
'moveend',
// Popup events
/**
* Fired when the map starts autopanning when opening a popup.
*
* @argument onAutopanstart
* @type {Function}
*/
'autopanstart',
// Location events
/**
* Fired when geolocation (using the locate method) failed.
*
* @argument onLocationerror
* @type {Function}
*/
'locationerror',
/**
* Fired when geolocation (using the locate method) went successfully.
*
* @argument onLocationfound
* @type {Function}
*/
'locationfound',
// Interaction events
/**
* Fired when the user clicks (or taps) the map.
*
* @argument onClick
* @type {Function}
*/
'click',
/**
* Fired when the user double-clicks (or double-taps) the map.
*
* @argument onDblclick
* @type {Function}
*/
'dblclick',
/**
* Fired when the user pushes the mouse button on the map.
*
* @argument onMousedown
* @type {Function}
*/
'mousedown',
/**
* Fired when the user releases the mouse button on the map.
*
* @argument onMouseup
* @type {Function}
*/
'mouseup',
/**
* Fired when the mouse enters the map.
*
* @argument onMouseover
* @type {Function}
*/
'mouseover',
/**
* Fired when the mouse leaves the map.
*
* @argument onMouseout
* @type {Function}
*/
'mouseout',
/**
* Fired while the mouse moves over the map.
*
* @argument onMousemove
* @type {Function}
*/
'mousemove',
/**
* Fired when the user pushes the right mouse button on the map,
* prevents default browser context menu from showing if there are
* listeners on this event. Also fired on mobile when the user holds
* a single touch for a second (also called long press).
*
* @argument onContextmenu
* @type {Function}
*/
'contextmenu',
/**
* Fired when the user presses a key from the keyboard that produces
* a character value while the map is focused.
*
* @argument onKeypress
* @type {Function}
*/
'keypress',
/**
* Fired when the user presses a key from the keyboard while the map is focused.
* Unlike the keypress event, the keydown event is fired for keys that produce
* a character value and for keys that do not produce a character value.
*
* @argument onKeydown
* @type {Function}
*/
'keydown',
/**
* Fired when the user releases a key from the keyboard while the map is focused.
*
* @argument onKeyup
* @type {Function}
*/
'keyup',
/**
* Fired before mouse click on the map (sometimes useful when you want something to happen on click before any existing click handlers start running).
*
* @argument onPreclick
* @type {Function}
*/
'preclick',
// Other events
/**
* Fired at least once per zoom animation. For continuous zoom, like pinch zooming,
* fired once per frame during zoom.
*
* @argument onZoomanim
* @type {Function}
*/
'zoomanim'
];
leafletDescriptors = [
...this.leafletDescriptors,
'zoom:setZoom:zoomPanOptions',
'minZoom',
'maxZoom',
'center:panTo:zoomPanOptions',
'bounds:fitBounds:fitBoundsOptions',
'maxBounds'
];
componentsToYield = [
...this.componentsToYield,
...this.emberLeaflet.components,
{ name: 'tile-layer', as: 'tile', component: TileLayer },
{ name: 'wms-tile-layer', as: 'wms-tile', component: WmsTileLayer },
{ name: 'marker-layer', as: 'marker', component: MarkerLayer },
{ name: 'circle-layer', as: 'circle', component: CircleLayer },
{ name: 'circle-marker-layer', as: 'circle-marker', component: CircleMarkerLayer },
{ name: 'image-layer', as: 'image', component: ImageLayer },
{ name: 'video-layer', as: 'video', component: VideoLayer },
{ name: 'polyline-layer', as: 'polyline', component: PolylineLayer },
{ name: 'polygon-layer', as: 'polygon', component: PolygonLayer },
{ name: 'geojson-layer', as: 'geojson', component: GeojsonLayer },
{ name: 'rectangle-layer', as: 'rectangle', component: RectangleLayer }
];
// required to supress glimmer component error message for acessing bounds property
bounds = undefined;
get center() {
if (this.args.center) {
return this.args.center;
} else if (!this.fastboot?.isFastBoot) {
let [lat, lng] = [this.args.lat, this.args.lng];
return this.L.latLng(lat, lng);
}
return null;
}
// By default all layers try to register in a container layer.
// It is not the case of the map itself as it is the topmost container.
registerWithParent() {}
unregisterWithParent() {}
createLayer(element) {
let options = this.options;
// Don't set center and zoom right now.
// Let base layer bind the events first
delete options.center;
delete options.zoom;
return this.L.map(element, options);
}
// Manually call `remove` method in the case of the root map layer.
willDestroyParent() {
let layer = this._layer;
super.willDestroyParent(...arguments);
layer.remove();
}
didCreateLayer() {
// after base layer bound the events, we can now set the map's view
assert(
'You must provide either valid `bounds` or a `center` (or `lat`/`lng`) and a `zoom` value.',
(this.args.bounds && !this.center && this.args.zoom === undefined) ||
(!this.args.bounds && this.center && this.args.zoom !== undefined)
);
if (this.args.bounds) {
this._layer.fitBounds(this.args.bounds, Object.assign({ reset: true }, this.args.fitBoundsOptions));
} else {
this._layer.setView(this.center, this.args.zoom, Object.assign({ reset: true }, this.args.zoomPanOptions));
}
}
}