diff options
Diffstat (limited to 'public/js/vendor/L.Control.Locate.js')
-rw-r--r-- | public/js/vendor/L.Control.Locate.js | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/public/js/vendor/L.Control.Locate.js b/public/js/vendor/L.Control.Locate.js new file mode 100644 index 0000000..8544e17 --- /dev/null +++ b/public/js/vendor/L.Control.Locate.js @@ -0,0 +1,591 @@ +/*! +Copyright (c) 2016 Dominik Moritz + +This file is part of the leaflet locate control. It is licensed under the MIT license. +You can find the project at: https://github.com/domoritz/leaflet-locatecontrol +*/ +(function (factory, window) { + // see https://github.com/Leaflet/Leaflet/blob/master/PLUGIN-GUIDE.md#module-loaders + // for details on how to structure a leaflet plugin. + + // 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') { + if (typeof window !== 'undefined' && window.L) { + module.exports = factory(L); + } else { + module.exports = factory(require('leaflet')); + } + } + + // attach your plugin to the global 'L' variable + if (typeof window !== 'undefined' && window.L){ + window.L.Control.Locate = factory(L); + } +} (function (L) { + var LDomUtilApplyClassesMethod = function(method, element, classNames) { + classNames = classNames.split(' '); + classNames.forEach(function(className) { + L.DomUtil[method].call(this, element, className); + }); + }; + + var addClasses = function(el, names) { LDomUtilApplyClassesMethod('addClass', el, names); }; + var removeClasses = function(el, names) { LDomUtilApplyClassesMethod('removeClass', el, names); }; + + var LocateControl = L.Control.extend({ + options: { + /** Position of the control */ + position: 'topleft', + /** The layer that the user's location should be drawn on. By default creates a new layer. */ + layer: undefined, + /** + * Automatically sets the map view (zoom and pan) to the user's location as it updates. + * While the map is following the user's location, the control is in the `following` state, + * which changes the style of the control and the circle marker. + * + * Possible values: + * - false: never updates the map view when location changes. + * - 'once': set the view when the location is first determined + * - 'always': always updates the map view when location changes. + * The map view follows the users location. + * - 'untilPan': (default) like 'always', except stops updating the + * view if the user has manually panned the map. + * The map view follows the users location until she pans. + */ + setView: 'untilPan', + /** Keep the current map zoom level when setting the view and only pan. */ + keepCurrentZoomLevel: false, + /** Smooth pan and zoom to the location of the marker. Only works in Leaflet 1.0+. */ + flyTo: false, + /** + * The user location can be inside and outside the current view when the user clicks on the + * control that is already active. Both cases can be configures separately. + * Possible values are: + * - 'setView': zoom and pan to the current location + * - 'stop': stop locating and remove the location marker + */ + clickBehavior: { + /** What should happen if the user clicks on the control while the location is within the current view. */ + inView: 'stop', + /** What should happen if the user clicks on the control while the location is outside the current view. */ + outOfView: 'setView', + }, + /** + * If set, save the map bounds just before centering to the user's + * location. When control is disabled, set the view back to the + * bounds that were saved. + */ + returnToPrevBounds: false, + /** + * Keep a cache of the location after the user deactivates the control. If set to false, the user has to wait + * until the locate API returns a new location before they see where they are again. + */ + cacheLocation: true, + /** If set, a circle that shows the location accuracy is drawn. */ + drawCircle: true, + /** If set, the marker at the users' location is drawn. */ + drawMarker: true, + /** The class to be used to create the marker. For example L.CircleMarker or L.Marker */ + markerClass: L.CircleMarker, + /** Accuracy circle style properties. */ + circleStyle: { + color: '#136AEC', + fillColor: '#136AEC', + fillOpacity: 0.15, + weight: 2, + opacity: 0.5 + }, + /** Inner marker style properties. Only works if your marker class supports `setStyle`. */ + markerStyle: { + color: '#136AEC', + fillColor: '#2A93EE', + fillOpacity: 0.7, + weight: 2, + opacity: 0.9, + radius: 5 + }, + /** + * Changes to accuracy circle and inner marker while following. + * It is only necessary to provide the properties that should change. + */ + followCircleStyle: {}, + followMarkerStyle: { + // color: '#FFA500', + // fillColor: '#FFB000' + }, + /** The CSS class for the icon. For example fa-location-arrow or fa-map-marker */ + icon: 'fa fa-map-marker', + iconLoading: 'fa fa-spinner fa-spin', + /** The element to be created for icons. For example span or i */ + iconElementTag: 'span', + /** Padding around the accuracy circle. */ + circlePadding: [0, 0], + /** Use metric units. */ + metric: true, + /** + * This callback can be used in case you would like to override button creation behavior. + * This is useful for DOM manipulation frameworks such as angular etc. + * This function should return an object with HtmlElement for the button (link property) and the icon (icon property). + */ + createButtonCallback: function (container, options) { + var link = L.DomUtil.create('a', 'leaflet-bar-part leaflet-bar-part-single', container); + link.title = options.strings.title; + var icon = L.DomUtil.create(options.iconElementTag, options.icon, link); + return { link: link, icon: icon }; + }, + /** This event is called in case of any location error that is not a time out error. */ + onLocationError: function(err, control) { + alert(err.message); + }, + /** + * This even is called when the user's location is outside the bounds set on the map. + * The event is called repeatedly when the location changes. + */ + onLocationOutsideMapBounds: function(control) { + control.stop(); + alert(control.options.strings.outsideMapBoundsMsg); + }, + /** Display a pop-up when the user click on the inner marker. */ + showPopup: true, + strings: { + title: "Show me where I am", + metersUnit: "meters", + feetUnit: "feet", + popup: "You are within {distance} {unit} from this point", + outsideMapBoundsMsg: "You seem located outside the boundaries of the map" + }, + /** The default options passed to leaflets locate method. */ + locateOptions: { + maxZoom: Infinity, + watch: true, // if you overwrite this, visualization cannot be updated + setView: false // have to set this to false because we have to + // do setView manually + } + }, + + initialize: function (options) { + // set default options if nothing is set (merge one step deep) + for (var i in options) { + if (typeof this.options[i] === 'object') { + L.extend(this.options[i], options[i]); + } else { + this.options[i] = options[i]; + } + } + + // extend the follow marker style and circle from the normal style + this.options.followMarkerStyle = L.extend({}, this.options.markerStyle, this.options.followMarkerStyle); + this.options.followCircleStyle = L.extend({}, this.options.circleStyle, this.options.followCircleStyle); + }, + + /** + * Add control to map. Returns the container for the control. + */ + onAdd: function (map) { + var container = L.DomUtil.create('div', + 'leaflet-control-locate leaflet-bar leaflet-control'); + + this._layer = this.options.layer || new L.LayerGroup(); + this._layer.addTo(map); + this._event = undefined; + this._prevBounds = null; + + var linkAndIcon = this.options.createButtonCallback(container, this.options); + this._link = linkAndIcon.link; + this._icon = linkAndIcon.icon; + + L.DomEvent + .on(this._link, 'click', L.DomEvent.stopPropagation) + .on(this._link, 'click', L.DomEvent.preventDefault) + .on(this._link, 'click', this._onClick, this) + .on(this._link, 'dblclick', L.DomEvent.stopPropagation); + + this._resetVariables(); + + this._map.on('unload', this._unload, this); + + return container; + }, + + /** + * This method is called when the user clicks on the control. + */ + _onClick: function() { + this._justClicked = true; + this._userPanned = false; + + if (this._active && !this._event) { + // click while requesting + this.stop(); + } else if (this._active && this._event !== undefined) { + var behavior = this._map.getBounds().contains(this._event.latlng) ? + this.options.clickBehavior.inView : this.options.clickBehavior.outOfView; + switch (behavior) { + case 'setView': + this.setView(); + break; + case 'stop': + this.stop(); + if (this.options.returnToPrevBounds) { + var f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds; + f.bind(this._map)(this._prevBounds); + } + break; + } + } else { + if (this.options.returnToPrevBounds) { + this._prevBounds = this._map.getBounds(); + } + this.start(); + } + + this._updateContainerStyle(); + }, + + /** + * Starts the plugin: + * - activates the engine + * - draws the marker (if coordinates available) + */ + start: function() { + this._activate(); + + if (this._event) { + this._drawMarker(this._map); + + // if we already have a location but the user clicked on the control + if (this.options.setView) { + this.setView(); + } + } + this._updateContainerStyle(); + }, + + /** + * Stops the plugin: + * - deactivates the engine + * - reinitializes the button + * - removes the marker + */ + stop: function() { + this._deactivate(); + + this._cleanClasses(); + this._resetVariables(); + + this._removeMarker(); + }, + + /** + * This method launches the location engine. + * It is called before the marker is updated, + * event if it does not mean that the event will be ready. + * + * Override it if you want to add more functionalities. + * It should set the this._active to true and do nothing if + * this._active is true. + */ + _activate: function() { + if (!this._active) { + this._map.locate(this.options.locateOptions); + this._active = true; + + // bind event listeners + this._map.on('locationfound', this._onLocationFound, this); + this._map.on('locationerror', this._onLocationError, this); + this._map.on('dragstart', this._onDrag, this); + } + }, + + /** + * Called to stop the location engine. + * + * Override it to shutdown any functionalities you added on start. + */ + _deactivate: function() { + this._map.stopLocate(); + this._active = false; + + if (!this.options.cacheLocation) { + this._event = undefined; + } + + // unbind event listeners + this._map.off('locationfound', this._onLocationFound, this); + this._map.off('locationerror', this._onLocationError, this); + this._map.off('dragstart', this._onDrag, this); + }, + + /** + * Zoom (unless we should keep the zoom level) and an to the current view. + */ + setView: function() { + this._drawMarker(); + if (this._isOutsideMapBounds()) { + this._event = undefined; // clear the current location so we can get back into the bounds + this.options.onLocationOutsideMapBounds(this); + } else { + if (this.options.keepCurrentZoomLevel) { + var f = this.options.flyTo ? this._map.flyTo : this._map.panTo; + f.bind(this._map)([this._event.latitude, this._event.longitude]); + } else { + var f = this.options.flyTo ? this._map.flyToBounds : this._map.fitBounds; + f.bind(this._map)(this._event.bounds, { + padding: this.options.circlePadding, + maxZoom: this.options.locateOptions.maxZoom + }); + } + } + }, + + /** + * Draw the marker and accuracy circle on the map. + * + * Uses the event retrieved from onLocationFound from the map. + */ + _drawMarker: function() { + if (this._event.accuracy === undefined) { + this._event.accuracy = 0; + } + + var radius = this._event.accuracy; + var latlng = this._event.latlng; + + // circle with the radius of the location's accuracy + if (this.options.drawCircle) { + var style = this._isFollowing() ? this.options.followCircleStyle : this.options.circleStyle; + + if (!this._circle) { + this._circle = L.circle(latlng, radius, style).addTo(this._layer); + } else { + this._circle.setLatLng(latlng).setRadius(radius).setStyle(style); + } + } + + var distance, unit; + if (this.options.metric) { + distance = radius.toFixed(0); + unit = this.options.strings.metersUnit; + } else { + distance = (radius * 3.2808399).toFixed(0); + unit = this.options.strings.feetUnit; + } + + // small inner marker + if (this.options.drawMarker) { + var mStyle = this._isFollowing() ? this.options.followMarkerStyle : this.options.markerStyle; + if (!this._marker) { + this._marker = new this.options.markerClass(latlng, mStyle).addTo(this._layer); + } else { + this._marker.setLatLng(latlng); + // If the markerClass can be updated with setStyle, update it. + if (this._marker.setStyle) { + this._marker.setStyle(mStyle); + } + } + } + + var t = this.options.strings.popup; + if (this.options.showPopup && t && this._marker) { + this._marker + .bindPopup(L.Util.template(t, {distance: distance, unit: unit})) + ._popup.setLatLng(latlng); + } + }, + + /** + * Remove the marker from map. + */ + _removeMarker: function() { + this._layer.clearLayers(); + this._marker = undefined; + this._circle = undefined; + }, + + /** + * Unload the plugin and all event listeners. + * Kind of the opposite of onAdd. + */ + _unload: function() { + this.stop(); + this._map.off('unload', this._unload, this); + }, + + /** + * Calls deactivate and dispatches an error. + */ + _onLocationError: function(err) { + // ignore time out error if the location is watched + if (err.code == 3 && this.options.locateOptions.watch) { + return; + } + + this.stop(); + this.options.onLocationError(err, this); + }, + + /** + * Stores the received event and updates the marker. + */ + _onLocationFound: function(e) { + // no need to do anything if the location has not changed + if (this._event && + (this._event.latlng.lat === e.latlng.lat && + this._event.latlng.lng === e.latlng.lng && + this._event.accuracy === e.accuracy)) { + return; + } + + if (!this._active) { + // we may have a stray event + return; + } + + this._event = e; + + this._drawMarker(); + this._updateContainerStyle(); + + switch (this.options.setView) { + case 'once': + if (this._justClicked) { + this.setView(); + } + break; + case 'untilPan': + if (!this._userPanned) { + this.setView(); + } + break; + case 'always': + this.setView(); + break; + case false: + // don't set the view + break; + } + + this._justClicked = false; + }, + + /** + * When the user drags. Need a separate even so we can bind and unbind even listeners. + */ + _onDrag: function() { + // only react to drags once we have a location + if (this._event) { + this._userPanned = true; + this._updateContainerStyle(); + this._drawMarker(); + } + }, + + /** + * Compute whether the map is following the user location with pan and zoom. + */ + _isFollowing: function() { + if (!this._active) { + return false; + } + + if (this.options.setView === 'always') { + return true; + } else if (this.options.setView === 'untilPan') { + return !this._userPanned; + } + }, + + /** + * Check if location is in map bounds + */ + _isOutsideMapBounds: function() { + if (this._event === undefined) { + return false; + } + return this._map.options.maxBounds && + !this._map.options.maxBounds.contains(this._event.latlng); + }, + + /** + * Toggles button class between following and active. + */ + _updateContainerStyle: function() { + if (!this._container) { + return; + } + + if (this._active && !this._event) { + // active but don't have a location yet + this._setClasses('requesting'); + } else if (this._isFollowing()) { + this._setClasses('following'); + } else if (this._active) { + this._setClasses('active'); + } else { + this._cleanClasses(); + } + }, + + /** + * Sets the CSS classes for the state. + */ + _setClasses: function(state) { + if (state == 'requesting') { + removeClasses(this._container, "active following"); + addClasses(this._container, "requesting"); + + removeClasses(this._icon, this.options.icon); + addClasses(this._icon, this.options.iconLoading); + } else if (state == 'active') { + removeClasses(this._container, "requesting following"); + addClasses(this._container, "active"); + + removeClasses(this._icon, this.options.iconLoading); + addClasses(this._icon, this.options.icon); + } else if (state == 'following') { + removeClasses(this._container, "requesting"); + addClasses(this._container, "active following"); + + removeClasses(this._icon, this.options.iconLoading); + addClasses(this._icon, this.options.icon); + } + }, + + /** + * Removes all classes from button. + */ + _cleanClasses: function() { + L.DomUtil.removeClass(this._container, "requesting"); + L.DomUtil.removeClass(this._container, "active"); + L.DomUtil.removeClass(this._container, "following"); + + removeClasses(this._icon, this.options.iconLoading); + addClasses(this._icon, this.options.icon); + }, + + /** + * Reinitializes state variables. + */ + _resetVariables: function() { + // whether locate is active or not + this._active = false; + + // true if the control was clicked for the first time + // we need this so we can pan and zoom once we have the location + this._justClicked = false; + + // true if the user has panned the map after clicking the control + this._userPanned = false; + } + }); + + L.control.locate = function (options) { + return new L.Control.Locate(options); + }; + + return LocateControl; +}, window)); |