From: Tom Hughes Date: Thu, 17 Jan 2013 17:44:10 +0000 (+0000) Subject: Update to Leaflet 0.5 X-Git-Tag: live~5727 X-Git-Url: https://git.openstreetmap.org./rails.git/commitdiff_plain/6bde69a840794523e54bdf1a3f99f0c413c0d974 Update to Leaflet 0.5 --- diff --git a/Vendorfile b/Vendorfile index d7779f8f7..d11ca37fc 100644 --- a/Vendorfile +++ b/Vendorfile @@ -1,27 +1,27 @@ folder 'vendor/assets' do folder 'leaflet' do - from 'https://github.com/CloudMade/Leaflet.git' do + from 'git://github.com/Leaflet/Leaflet.git', :tag => 'v0.5' do file 'leaflet.css', 'dist/leaflet.css' file 'leaflet.ie.css', 'dist/leaflet.ie.css' file 'leaflet.js', 'dist/leaflet-src.js' folder 'images', 'dist/images' end - from 'https://github.com/tripbirds/leaflet-locationfilter.git' do + from 'git://github.com/kajic/leaflet-locationfilter.git' do file 'leaflet.locationfilter.css', 'src/locationfilter.css' file 'leaflet.locationfilter.js', 'src/locationfilter.js' folder 'img', 'src/img' end - from 'https://github.com/kartena/Leaflet.Pancontrol.git' do + from 'git://github.com/kartena/Leaflet.Pancontrol.git' do file 'leaflet.pan.js', 'src/L.Control.Pan.js' end - from 'https://github.com/kartena/Leaflet.zoomslider.git' do + from 'git://github.com/kartena/Leaflet.zoomslider.git' do file 'leaflet.zoom.js', 'src/L.Control.Zoomslider.js' end - from 'https://github.com/jfirebaugh/leaflet-osm.git' do + from 'git://github.com/jfirebaugh/leaflet-osm.git' do file 'leaflet.osm.js', 'leaflet-osm.js' end end diff --git a/app/assets/javascripts/map.js.erb b/app/assets/javascripts/map.js.erb index 291633f53..a18292aaa 100644 --- a/app/assets/javascripts/map.js.erb +++ b/app/assets/javascripts/map.js.erb @@ -1,5 +1,5 @@ // Leaflet extensions -L.LatLngBounds.include({ +L.extend(L.LatLngBounds.prototype, { getSouthLat: function () { return this._southWest.lat; }, @@ -32,7 +32,7 @@ L.LatLngBounds.include({ } }); -L.Bounds.include({ +L.extend(L.Bounds.prototype, { getWidth: function () { return this.max.x - this.min.x; }, diff --git a/vendor/assets/leaflet/images/layers.png b/vendor/assets/leaflet/images/layers.png index 642e2787f..ef90a080b 100644 Binary files a/vendor/assets/leaflet/images/layers.png and b/vendor/assets/leaflet/images/layers.png differ diff --git a/vendor/assets/leaflet/images/marker-icon.png b/vendor/assets/leaflet/images/marker-icon.png index 7614b7c7f..e2e9f757f 100644 Binary files a/vendor/assets/leaflet/images/marker-icon.png and b/vendor/assets/leaflet/images/marker-icon.png differ diff --git a/vendor/assets/leaflet/images/marker-icon@2x.png b/vendor/assets/leaflet/images/marker-icon@2x.png new file mode 100644 index 000000000..0015b6495 Binary files /dev/null and b/vendor/assets/leaflet/images/marker-icon@2x.png differ diff --git a/vendor/assets/leaflet/images/marker-shadow.png b/vendor/assets/leaflet/images/marker-shadow.png index a64f6a672..d1e773c71 100644 Binary files a/vendor/assets/leaflet/images/marker-shadow.png and b/vendor/assets/leaflet/images/marker-shadow.png differ diff --git a/vendor/assets/leaflet/images/zoom-in.png b/vendor/assets/leaflet/images/zoom-in.png deleted file mode 100644 index 4a98a35d9..000000000 Binary files a/vendor/assets/leaflet/images/zoom-in.png and /dev/null differ diff --git a/vendor/assets/leaflet/images/zoom-out.png b/vendor/assets/leaflet/images/zoom-out.png deleted file mode 100644 index 427616669..000000000 Binary files a/vendor/assets/leaflet/images/zoom-out.png and /dev/null differ diff --git a/vendor/assets/leaflet/leaflet.css b/vendor/assets/leaflet/leaflet.css index 938a1be54..ea3da3907 100644 --- a/vendor/assets/leaflet/leaflet.css +++ b/vendor/assets/leaflet/leaflet.css @@ -15,6 +15,7 @@ .leaflet-layer { position: absolute; left: 0; + top: 0; } .leaflet-container { overflow: hidden; @@ -184,25 +185,61 @@ /* general typography */ .leaflet-container { font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; -} + } -/* zoom control */ +/* general toolbar styles */ -.leaflet-control-zoom { - -webkit-border-radius: 7px; - border-radius: 7px; +.leaflet-bar { + box-shadow: 0 0 8px rgba(0,0,0,0.4); + border: 1px solid #888; + -webkit-border-radius: 5px; + border-radius: 5px; + } +.leaflet-bar-part { + background-color: rgba(255, 255, 255, 0.8); + border-bottom: 1px solid #aaa; } -.leaflet-control-zoom { - padding: 5px; - background: rgba(0, 0, 0, 0.25); +.leaflet-bar-part-top { + -webkit-border-radius: 4px 4px 0 0; + border-radius: 4px 4px 0 0; + } +.leaflet-bar-part-bottom { + -webkit-border-radius: 0 0 4px 4px; + border-radius: 0 0 4px 4px; + border-bottom: none; + } + +.leaflet-touch .leaflet-bar { + -webkit-border-radius: 10px; + border-radius: 10px; + } +.leaflet-touch .leaflet-bar-part { + border-bottom: 4px solid rgba(0,0,0,0.3); + } +.leaflet-touch .leaflet-bar-part-top { + -webkit-border-radius: 7px 7px 0 0; + border-radius: 7px 7px 0 0; + } +.leaflet-touch .leaflet-bar-part-bottom { + -webkit-border-radius: 0 0 7px 7px; + border-radius: 0 0 7px 7px; + border-bottom: none; + } + + +/* zoom control */ + +.leaflet-container .leaflet-control-zoom { + margin-left: 13px; + margin-top: 12px; } .leaflet-control-zoom a { - width: 19px; - height: 19px; - background-color: rgba(255, 255, 255, 0.75); - -webkit-border-radius: 4px; - border-radius: 4px; + width: 22px; + height: 22px; + text-align: center; + text-decoration: none; + color: black; } .leaflet-control-zoom a, .leaflet-control-layers-toggle { @@ -212,24 +249,37 @@ } .leaflet-control-zoom a:hover { background-color: #fff; - } -.leaflet-touch .leaflet-control-zoom a { - width: 27px; - height: 27px; + color: #777; } .leaflet-control-zoom-in { - background-image: url(images/zoom-in.png); - margin-bottom: 5px; + font: bold 18px/24px Arial, Helvetica, sans-serif; } .leaflet-control-zoom-out { - background-image: url(images/zoom-out.png); + font: bold 23px/20px Tahoma, Verdana, sans-serif; + } +.leaflet-control-zoom a.leaflet-control-zoom-disabled { + cursor: default; + background-color: rgba(255, 255, 255, 0.8); + color: #bbb; } +.leaflet-touch .leaflet-control-zoom a { + width: 30px; + height: 30px; + } +.leaflet-touch .leaflet-control-zoom-in { + font-size: 24px; + line-height: 29px; + } +.leaflet-touch .leaflet-control-zoom-out { + font-size: 28px; + line-height: 24px; + } /* layers control */ .leaflet-control-layers { - box-shadow: 0 1px 7px #999; + box-shadow: 0 1px 7px rgba(0,0,0,0.4); background: #f8f8f9; -webkit-border-radius: 8px; border-radius: 8px; @@ -297,27 +347,33 @@ border: 2px solid #777; border-top: none; color: black; - line-height: 1; - font-size: 10px; - padding-bottom: 2px; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; text-shadow: 1px 1px 1px #fff; background-color: rgba(255, 255, 255, 0.5); + box-shadow: 0 -1px 5px rgba(0, 0, 0, 0.2); + white-space: nowrap; + overflow: hidden; } .leaflet-control-scale-line:not(:first-child) { border-top: 2px solid #777; - padding-top: 1px; border-bottom: none; margin-top: -2px; + box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2); } .leaflet-control-scale-line:not(:first-child):not(:last-child) { border-bottom: 2px solid #777; } -.leaflet-touch .leaflet-control-attribution, .leaflet-touch .leaflet-control-layers { +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-control-zoom { box-shadow: none; } -.leaflet-touch .leaflet-control-layers { - border: 5px solid #bbb; +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-control-zoom { + border: 4px solid rgba(0,0,0,0.3); } @@ -363,8 +419,7 @@ .leaflet-popup-content-wrapper, .leaflet-popup-tip { background: white; - box-shadow: 0 3px 14px rgba(0,0,0,0.35); - -webkit-box-shadow: 0 3px 18px rgba(0,0,0,0.33); + box-shadow: 0 3px 14px rgba(0,0,0,0.4); } .leaflet-container a.leaflet-popup-close-button { position: absolute; @@ -378,6 +433,7 @@ color: #c3c3c3; text-decoration: none; font-weight: bold; + background: transparent; } .leaflet-container a.leaflet-popup-close-button:hover { color: #999; diff --git a/vendor/assets/leaflet/leaflet.ie.css b/vendor/assets/leaflet/leaflet.ie.css index 5b65c400a..b59c8144e 100644 --- a/vendor/assets/leaflet/leaflet.ie.css +++ b/vendor/assets/leaflet/leaflet.ie.css @@ -3,11 +3,11 @@ height: 1px; } .lvml { - behavior: url(#default#VML); - display: inline-block; + behavior: url(#default#VML); + display: inline-block; position: absolute; } - + .leaflet-control { display: inline; } @@ -17,7 +17,7 @@ _width: 27px; margin: 0 auto; _margin-top: -3px; - + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; } @@ -25,11 +25,15 @@ margin-top: -1px; } .leaflet-popup-content-wrapper, .leaflet-popup-tip { - border: 1px solid #bbb; + border: 1px solid #999; + } +.leaflet-popup-content-wrapper { + zoom: 1; } -.leaflet-control-zoom { - filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#3F000000',EndColorStr='#3F000000'); +.leaflet-control-zoom, +.leaflet-control-layers { + border: 3px solid #999; } .leaflet-control-zoom a { background-color: #eee; @@ -39,9 +43,15 @@ } .leaflet-control-layers-toggle { } -.leaflet-control-attribution, .leaflet-control-layers { +.leaflet-control-attribution, +.leaflet-control-layers, +.leaflet-control-scale-line { background: white; } .leaflet-zoom-box { filter: alpha(opacity=50); - } \ No newline at end of file + } +.leaflet-control-attribution { + border-top: 1px solid #bbb; + border-left: 1px solid #bbb; + } diff --git a/vendor/assets/leaflet/leaflet.js b/vendor/assets/leaflet/leaflet.js index 5503592a0..2da1987df 100644 --- a/vendor/assets/leaflet/leaflet.js +++ b/vendor/assets/leaflet/leaflet.js @@ -1,9 +1,11 @@ /* - Copyright (c) 2010-2012, CloudMade, Vladimir Agafonkin - Leaflet is an open-source JavaScript library for mobile-friendly interactive maps. - http://leafletjs.com + Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com + (c) 2010-2013, Vladimir Agafonkin, CloudMade */ -(function (window, undefined) { +(function (window, document, undefined) {/* + * The L namespace contains all Leaflet classes and functions. + * This code allows you to handle any possible namespace conflicts. + */ var L, originalL; @@ -21,11 +23,11 @@ if (typeof exports !== undefined + '') { window.L = L; } -L.version = '0.4.4'; +L.version = '0.5'; /* - * L.Util is a namespace for various utility functions. + * L.Util contains various utility functions used throughout Leaflet code. */ L.Util = { @@ -103,14 +105,14 @@ L.Util = { return obj.options; }, - getParamString: function (obj) { + getParamString: function (obj, existingUrl) { var params = []; for (var i in obj) { if (obj.hasOwnProperty(i)) { params.push(i + '=' + obj[i]); } } - return '?' + params.join('&'); + return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); }, template: function (str, data) { @@ -123,6 +125,10 @@ L.Util = { }); }, + isArray: function (obj) { + return (Object.prototype.toString.call(obj) === '[object Array]'); + }, + emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=' }; @@ -186,18 +192,26 @@ L.setOptions = L.Util.setOptions; /* - * Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration! + * L.Class powers the OOP facilities of the library. + * Thanks to John Resig and Dean Edwards for inspiration! */ L.Class = function () {}; -L.Class.extend = function (/*Object*/ props) /*-> Class*/ { +L.Class.extend = function (props) { // extended class with the new prototype var NewClass = function () { + + // call the constructor if (this.initialize) { this.initialize.apply(this, arguments); } + + // call all constructor hooks + if (this._initHooks) { + this.callInitHooks(); + } }; // instantiate class without calling constructor @@ -236,6 +250,25 @@ L.Class.extend = function (/*Object*/ props) /*-> Class*/ { // mix given properties into the prototype L.extend(proto, props); + proto._initHooks = []; + + var parent = this; + // add method for calling all hooks + proto.callInitHooks = function () { + + if (this._initHooksCalled) { return; } + + if (parent.prototype.callInitHooks) { + parent.prototype.callInitHooks.call(this); + } + + this._initHooksCalled = true; + + for (var i = 0, len = proto._initHooks.length; i < len; i++) { + proto._initHooks[i].call(this); + } + }; + return NewClass; }; @@ -245,13 +278,26 @@ L.Class.include = function (props) { L.extend(this.prototype, props); }; +// merge new default options to the Class L.Class.mergeOptions = function (options) { L.extend(this.prototype.options, options); }; +// add a constructor hook +L.Class.addInitHook = function (fn) { // (Function) || (String, args...) + var args = Array.prototype.slice.call(arguments, 1); + + var init = typeof fn === 'function' ? fn : function () { + this[fn].apply(this, args); + }; + + this.prototype._initHooks = this.prototype._initHooks || []; + this.prototype._initHooks.push(init); +}; + /* - * L.Mixin.Events adds custom events functionality to Leaflet classes + * L.Mixin.Events is used to add custom events functionality to Leaflet classes. */ var key = '_leaflet_events'; @@ -352,26 +398,29 @@ L.Mixin.Events.off = L.Mixin.Events.removeEventListener; L.Mixin.Events.fire = L.Mixin.Events.fireEvent; +/* + * L.Browser handles different browser and feature detections for internal Leaflet use. + */ + (function () { var ie = !!window.ActiveXObject, - // http://tanalin.com/en/articles/ie-version-js/ ie6 = ie && !window.XMLHttpRequest, ie7 = ie && !document.querySelector, // terrible browser detection to work around Safari / iOS / Android browser bugs - // see TileLayer._addTile and debug/hacks/jitter.html - ua = navigator.userAgent.toLowerCase(), - webkit = ua.indexOf("webkit") !== -1, - chrome = ua.indexOf("chrome") !== -1, - android = ua.indexOf("android") !== -1, - android23 = ua.search("android [23]") !== -1, + webkit = ua.indexOf('webkit') !== -1, + chrome = ua.indexOf('chrome') !== -1, + android = ua.indexOf('android') !== -1, + android23 = ua.search('android [23]') !== -1, mobile = typeof orientation !== undefined + '', - msTouch = (window.navigator && window.navigator.msPointerEnabled && window.navigator.msMaxTouchPoints), - retina = (('devicePixelRatio' in window && window.devicePixelRatio > 1) || - ('matchMedia' in window && window.matchMedia("(min-resolution:144dpi)").matches)), + msTouch = window.navigator && window.navigator.msPointerEnabled && + window.navigator.msMaxTouchPoints, + retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || + ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && + window.matchMedia('(min-resolution:144dpi)').matches), doc = document.documentElement, ie3d = ie && ('transition' in doc.style), @@ -411,6 +460,7 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent; L.Browser = { + ie: ie, ie6: ie6, ie7: ie7, webkit: webkit, @@ -526,6 +576,11 @@ L.Point.prototype = { return Math.sqrt(x * x + y * y); }, + equals: function (point) { + return point.x === this.x && + point.y === this.y; + }, + toString: function () { return 'Point(' + L.Util.formatNum(this.x) + ', ' + @@ -537,7 +592,7 @@ L.point = function (x, y, round) { if (x instanceof L.Point) { return x; } - if (x instanceof Array) { + if (L.Util.isArray(x)) { return new L.Point(x[0], x[1]); } if (isNaN(x)) { @@ -551,18 +606,17 @@ L.point = function (x, y, round) { * L.Bounds represents a rectangular area on the screen in pixel coordinates. */ -L.Bounds = L.Class.extend({ - - initialize: function (a, b) { //(Point, Point) or Point[] - if (!a) { return; } +L.Bounds = function (a, b) { //(Point, Point) or Point[] + if (!a) { return; } - var points = b ? [a, b] : a; + var points = b ? [a, b] : a; - for (var i = 0, len = points.length; i < len; i++) { - this.extend(points[i]); - } - }, + for (var i = 0, len = points.length; i < len; i++) { + this.extend(points[i]); + } +}; +L.Bounds.prototype = { // extend the bounds to contain the given point extend: function (point) { // (Point) point = L.point(point); @@ -593,6 +647,10 @@ L.Bounds = L.Class.extend({ return new L.Point(this.max.x, this.min.y); }, + getSize: function () { + return this.max.subtract(this.min); + }, + contains: function (obj) { // (Bounds) or (Point) -> Boolean var min, max; @@ -631,7 +689,7 @@ L.Bounds = L.Class.extend({ isValid: function () { return !!(this.min && this.max); } -}); +}; L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) if (!a || a instanceof L.Bounds) { @@ -645,33 +703,33 @@ L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[]) * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix. */ -L.Transformation = L.Class.extend({ - initialize: function (/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) { - this._a = a; - this._b = b; - this._c = c; - this._d = d; - }, +L.Transformation = function (a, b, c, d) { + this._a = a; + this._b = b; + this._c = c; + this._d = d; +}; - transform: function (point, scale) { +L.Transformation.prototype = { + transform: function (point, scale) { // (Point, Number) -> Point return this._transform(point.clone(), scale); }, // destructive transform (faster) - _transform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ { + _transform: function (point, scale) { scale = scale || 1; point.x = scale * (this._a * point.x + this._b); point.y = scale * (this._c * point.y + this._d); return point; }, - untransform: function (/*Point*/ point, /*Number*/ scale) /*-> Point*/ { + untransform: function (point, scale) { scale = scale || 1; return new L.Point( (point.x / scale - this._b) / this._a, (point.y / scale - this._d) / this._c); } -}); +}; /* @@ -711,6 +769,11 @@ L.DomUtil = { do { top += el.offsetTop || 0; left += el.offsetLeft || 0; + + //add borders + top += parseInt(L.DomUtil.getStyle(el, "borderTopWidth"), 10) || 0; + left += parseInt(L.DomUtil.getStyle(el, "borderLeftWidth"), 10) || 0; + pos = L.DomUtil.getStyle(el, 'position'); if (el.offsetParent === docBody && pos === 'absolute') { break; } @@ -776,7 +839,7 @@ L.DomUtil = { document.selection.empty(); } if (!this._onselectstart) { - this._onselectstart = document.onselectstart; + this._onselectstart = document.onselectstart || null; document.onselectstart = L.Util.falseFn; } }, @@ -897,8 +960,11 @@ L.DomUtil = { L.DomUtil.TRANSFORM = L.DomUtil.testProp( ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']); +// webkitTransition comes first because some browser versions that drop vendor prefix don't do +// the same for the transitionend event, in particular the Android 4.1 stock browser + L.DomUtil.TRANSITION = L.DomUtil.testProp( - ['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']); + ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']); L.DomUtil.TRANSITION_END = L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ? @@ -906,10 +972,10 @@ L.DomUtil.TRANSITION_END = /* - CM.LatLng represents a geographical point with latitude and longtitude coordinates. -*/ + * L.LatLng represents a geographical point with latitude and longitude coordinates. + */ -L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean]) +L.LatLng = function (rawLat, rawLng) { // (Number, Number) var lat = parseFloat(rawLat), lng = parseFloat(rawLng); @@ -917,11 +983,6 @@ L.LatLng = function (rawLat, rawLng, noWrap) { // (Number, Number[, Boolean]) throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')'); } - if (noWrap !== true) { - lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90 - lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180 - } - this.lat = lat; this.lng = lng; }; @@ -938,17 +999,21 @@ L.LatLng.prototype = { obj = L.latLng(obj); - var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng)); + var margin = Math.max( + Math.abs(this.lat - obj.lat), + Math.abs(this.lng - obj.lng)); + return margin <= L.LatLng.MAX_MARGIN; }, - toString: function (precision) { // -> String + toString: function (precision) { // (Number) -> String return 'LatLng(' + L.Util.formatNum(this.lat, precision) + ', ' + L.Util.formatNum(this.lng, precision) + ')'; }, // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula + // TODO move to projection code, LatLng shouldn't know about Earth distanceTo: function (other) { // (LatLng) -> Number other = L.latLng(other); @@ -964,20 +1029,31 @@ L.LatLng.prototype = { var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2); return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + }, + + wrap: function (a, b) { // (Number, Number) -> LatLng + var lng = this.lng; + + a = a || -180; + b = b || 180; + + lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a); + + return new L.LatLng(this.lat, lng); } }; -L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Number, Boolean) +L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number) if (a instanceof L.LatLng) { return a; } - if (a instanceof Array) { + if (L.Util.isArray(a)) { return new L.LatLng(a[0], a[1]); } if (isNaN(a)) { return a; } - return new L.LatLng(a, b, c); + return new L.LatLng(a, b); }; @@ -986,20 +1062,20 @@ L.latLng = function (a, b, c) { // (LatLng) or ([Number, Number]) or (Number, Nu * L.LatLngBounds represents a rectangular area on the map in geographical coordinates. */ -L.LatLngBounds = L.Class.extend({ - initialize: function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) - if (!southWest) { return; } +L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[]) + if (!southWest) { return; } - var latlngs = northEast ? [southWest, northEast] : southWest; + var latlngs = northEast ? [southWest, northEast] : southWest; - for (var i = 0, len = latlngs.length; i < len; i++) { - this.extend(latlngs[i]); - } - }, + for (var i = 0, len = latlngs.length; i < len; i++) { + this.extend(latlngs[i]); + } +}; +L.LatLngBounds.prototype = { // extend the bounds to contain the given point or bounds extend: function (obj) { // (LatLng) or (LatLngBounds) - if (typeof obj[0] === 'number' || obj instanceof L.LatLng) { + if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) { obj = L.latLng(obj); } else { obj = L.latLngBounds(obj); @@ -1007,8 +1083,8 @@ L.LatLngBounds = L.Class.extend({ if (obj instanceof L.LatLng) { if (!this._southWest && !this._northEast) { - this._southWest = new L.LatLng(obj.lat, obj.lng, true); - this._northEast = new L.LatLng(obj.lat, obj.lng, true); + this._southWest = new L.LatLng(obj.lat, obj.lng); + this._northEast = new L.LatLng(obj.lat, obj.lng); } else { this._southWest.lat = Math.min(obj.lat, this._southWest.lat); this._southWest.lng = Math.min(obj.lng, this._southWest.lng); @@ -1050,11 +1126,11 @@ L.LatLngBounds = L.Class.extend({ }, getNorthWest: function () { - return new L.LatLng(this._northEast.lat, this._southWest.lng, true); + return new L.LatLng(this._northEast.lat, this._southWest.lng); }, getSouthEast: function () { - return new L.LatLng(this._southWest.lat, this._northEast.lng, true); + return new L.LatLng(this._southWest.lat, this._northEast.lng); }, contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean @@ -1112,7 +1188,7 @@ L.LatLngBounds = L.Class.extend({ isValid: function () { return !!(this._southWest && this._northEast); } -}); +}; //TODO International date line? @@ -1131,6 +1207,9 @@ L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng) L.Projection = {}; +/* + * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default. + */ L.Projection.SphericalMercator = { MAX_LATITUDE: 85.0511287798, @@ -1152,12 +1231,14 @@ L.Projection.SphericalMercator = { lng = point.x * d, lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d; - // TODO refactor LatLng wrapping - return new L.LatLng(lat, lng, true); + return new L.LatLng(lat, lng); } }; +/* + * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple. + */ L.Projection.LonLat = { project: function (latlng) { @@ -1165,11 +1246,14 @@ L.Projection.LonLat = { }, unproject: function (point) { - return new L.LatLng(point.y, point.x, true); + return new L.LatLng(point.y, point.x); } }; +/* + * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet. + */ L.CRS = { latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point @@ -1196,13 +1280,24 @@ L.CRS = { }; +/* + * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps. + */ L.CRS.Simple = L.extend({}, L.CRS, { projection: L.Projection.LonLat, - transformation: new L.Transformation(1, 0, 1, 0) + transformation: new L.Transformation(1, 0, -1, 0), + + scale: function (zoom) { + return Math.pow(2, zoom); + } }); +/* + * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping + * and is used by Leaflet by default. + */ L.CRS.EPSG3857 = L.extend({}, L.CRS, { code: 'EPSG:3857', @@ -1222,6 +1317,9 @@ L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, { }); +/* + * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists. + */ L.CRS.EPSG4326 = L.extend({}, L.CRS, { code: 'EPSG:4326', @@ -1258,7 +1356,7 @@ L.Map = L.Class.extend({ this._initContainer(id); this._initLayout(); - this._initHooks(); + this.callInitHooks(); this._initEvents(); if (options.maxBounds) { @@ -1381,11 +1479,9 @@ L.Map = L.Class.extend({ this._layers[id] = layer; // TODO getMaxZoom, getMinZoom in ILayer (instead of options) - if (layer.options && !isNaN(layer.options.maxZoom)) { - this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom); - } - if (layer.options && !isNaN(layer.options.minZoom)) { - this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom); + if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) { + this._zoomBoundLayers[id] = layer; + this._updateZoomLevels(); } // TODO looks ugly, refactor!!! @@ -1411,6 +1507,10 @@ L.Map = L.Class.extend({ layer.onRemove(this); delete this._layers[id]; + if (this._zoomBoundLayers[id]) { + delete this._zoomBoundLayers[id]; + this._updateZoomLevels(); + } // TODO looks ugly, refactor if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) { @@ -1648,7 +1748,6 @@ L.Map = L.Class.extend({ _initLayout: function () { var container = this._container; - container.innerHTML = ''; L.DomUtil.addClass(container, 'leaflet-container'); if (L.Browser.touch) { @@ -1697,19 +1796,11 @@ L.Map = L.Class.extend({ return L.DomUtil.create('div', className, container || this._panes.objectsPane); }, - _initializers: [], - - _initHooks: function () { - var i, len; - for (i = 0, len = this._initializers.length; i < len; i++) { - this._initializers[i].call(this); - } - }, - _initLayers: function (layers) { - layers = layers ? (layers instanceof Array ? layers : [layers]) : []; + layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : []; this._layers = {}; + this._zoomBoundLayers = {}; this._tileLayersNum = 0; var i, len; @@ -1768,6 +1859,30 @@ L.Map = L.Class.extend({ L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset)); }, + _updateZoomLevels: function () { + var i, + minZoom = Infinity, + maxZoom = -Infinity; + + for (i in this._zoomBoundLayers) { + if (this._zoomBoundLayers.hasOwnProperty(i)) { + var layer = this._zoomBoundLayers[i]; + if (!isNaN(layer.options.minZoom)) { + minZoom = Math.min(minZoom, layer.options.minZoom); + } + if (!isNaN(layer.options.maxZoom)) { + maxZoom = Math.max(maxZoom, layer.options.maxZoom); + } + } + } + + if (i === undefined) { // we have no tilelayers + this._layersMaxZoom = this._layersMinZoom = undefined; + } else { + this._layersMaxZoom = maxZoom; + this._layersMinZoom = minZoom; + } + }, // map events @@ -1888,21 +2003,15 @@ L.Map = L.Class.extend({ } }); -L.Map.addInitHook = function (fn) { - var args = Array.prototype.slice.call(arguments, 1); - - var init = typeof fn === 'function' ? fn : function () { - this[fn].apply(this, args); - }; - - this.prototype._initializers.push(init); -}; - L.map = function (id, options) { return new L.Map(id, options); }; +/* + * Mercator projection that takes into account that the Earth is not a perfect sphere. + * Less popular than spherical mercator; used by projections like EPSG:3395. + */ L.Projection.Mercator = { MAX_LATITUDE: 85.0840591556, @@ -1952,7 +2061,7 @@ L.Projection.Mercator = { phi += dphi; } - return new L.LatLng(phi * d, lng, true); + return new L.LatLng(phi * d, lng); } }; @@ -2056,7 +2165,7 @@ L.TileLayer = L.Class.extend({ }, onRemove: function (map) { - map._panes.tilePane.removeChild(this._container); + this._container.parentNode.removeChild(this._container); map.off({ 'viewreset': this._resetCallback, @@ -2141,8 +2250,8 @@ L.TileLayer = L.Class.extend({ _setAutoZIndex: function (pane, compare) { - var layers = pane.getElementsByClassName('leaflet-layer'), - edgeZIndex = -compare(Infinity, -Infinity), // -Ifinity for max, Infinity for min + var layers = pane.children, + edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min zIndex, i, len; for (i = 0, len = layers.length; i < len; i++) { @@ -2219,7 +2328,7 @@ L.TileLayer = L.Class.extend({ this._initContainer(); }, - _update: function (e) { + _update: function () { if (!this._map) { return; } @@ -2440,9 +2549,8 @@ L.TileLayer = L.Class.extend({ return this._createTile(); }, - _resetTile: function (tile) { - // Override if data stored on a tile needs to be cleaned up before reuse - }, + // Override if data stored on a tile needs to be cleaned up before reuse + _resetTile: function (/*tile*/) {}, _createTile: function () { var tile = this._tileImg.cloneNode(false); @@ -2465,7 +2573,7 @@ L.TileLayer = L.Class.extend({ } }, - _tileOnLoad: function (e) { + _tileOnLoad: function () { var layer = this._layer; //Only if we are loading an actual image @@ -2481,7 +2589,7 @@ L.TileLayer = L.Class.extend({ layer._tileLoaded(); }, - _tileOnError: function (e) { + _tileOnError: function () { var layer = this._layer; layer.fire('tileerror', { @@ -2503,6 +2611,10 @@ L.tileLayer = function (url, options) { }; +/* + * L.TileLayer.WMS is used for putting WMS tile layers on the map. + */ + L.TileLayer.WMS = L.TileLayer.extend({ defaultWmsParams: { @@ -2549,6 +2661,8 @@ L.TileLayer.WMS = L.TileLayer.extend({ getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String + this._adjustTilePoint(tilePoint); + var map = this._map, crs = map.options.crs, tileSize = this.options.tileSize, @@ -2563,7 +2677,7 @@ L.TileLayer.WMS = L.TileLayer.extend({ url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)}); - return url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox; + return url + L.Util.getParamString(this.wmsParams, url) + "&bbox=" + bbox; }, setParams: function (params, noRedraw) { @@ -2583,6 +2697,11 @@ L.tileLayer.wms = function (url, options) { }; +/* + * L.TileLayer.Canvas is a class that you can use as a base for creating + * dynamically drawn Canvas-based tile layers. + */ + L.TileLayer.Canvas = L.TileLayer.extend({ options: { async: false @@ -2628,7 +2747,7 @@ L.TileLayer.Canvas = L.TileLayer.extend({ } }, - drawTile: function (tile, tilePoint) { + drawTile: function (/*tile, tilePoint*/) { // override with rendering code }, @@ -2643,6 +2762,10 @@ L.tileLayer.canvas = function (options) { }; +/* + * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds). + */ + L.ImageOverlay = L.Class.extend({ includes: L.Mixin.Events, @@ -2742,8 +2865,7 @@ L.ImageOverlay = L.Class.extend({ topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center), size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft), - currentSize = map.latLngToLayerPoint(se)._subtract(map.latLngToLayerPoint(nw)), - origin = topLeft._add(size._subtract(currentSize)._divideBy(2)); + origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale))); image.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') '; @@ -2752,7 +2874,7 @@ L.ImageOverlay = L.Class.extend({ _reset: function () { var image = this._image, topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()), - size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft); + size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft); L.DomUtil.setPosition(image, topLeft); @@ -2774,14 +2896,20 @@ L.imageOverlay = function (url, bounds, options) { }; +/* + * L.Icon is an image-based icon class that you can use with L.Marker for custom markers. + */ + L.Icon = L.Class.extend({ options: { /* iconUrl: (String) (required) + iconRetinaUrl: (String) (optional, used for retina devices if detected) iconSize: (Point) (can be set through CSS) iconAnchor: (Point) (centered by default, can be set in CSS with negative margins) popupAnchor: (Point) (if not specified, popup opens in the anchor point) shadowUrl: (Point) (no shadow by default) + shadowRetinaUrl: (String) (optional, used for retina devices if detected) shadowSize: (Point) shadowAnchor: (Point) */ @@ -2859,6 +2987,9 @@ L.Icon = L.Class.extend({ }, _getIconUrl: function (name) { + if (L.Browser.retina && this.options[name + 'RetinaUrl']) { + return this.options[name + 'RetinaUrl']; + } return this.options[name + 'Url']; } }); @@ -2868,6 +2999,9 @@ L.icon = function (options) { }; +/* + * L.Icon.Default is the blue marker icon used by default in Leaflet. + */ L.Icon.Default = L.Icon.extend({ @@ -2886,6 +3020,10 @@ L.Icon.Default = L.Icon.extend({ return this.options[key]; } + if (L.Browser.retina && name === 'icon') { + name += '@2x'; + } + var path = L.Icon.Default.imagePath; if (!path) { @@ -2977,12 +3115,14 @@ L.Marker = L.Class.extend({ this.update(); - this.fire('move', { latlng: this._latlng }); + return this.fire('move', { latlng: this._latlng }); }, setZIndexOffset: function (offset) { this.options.zIndexOffset = offset; this.update(); + + return this; }, setIcon: function (icon) { @@ -2996,13 +3136,17 @@ L.Marker = L.Class.extend({ this._initIcon(); this.update(); } + + return this; }, update: function () { - if (!this._icon) { return; } + if (this._icon) { + var pos = this._map.latLngToLayerPoint(this._latlng).round(); + this._setPos(pos); + } - var pos = this._map.latLngToLayerPoint(this._latlng).round(); - this._setPos(pos); + return this; }, _initIcon: function () { @@ -3094,12 +3238,13 @@ L.Marker = L.Class.extend({ }, _initInteraction: function () { - if (!this.options.clickable) { - return; - } + + if (!this.options.clickable) { return; } + + // TODO refactor into something shared with Map/Path/etc. to DRY it up var icon = this._icon, - events = ['dblclick', 'mousedown', 'mouseover', 'mouseout']; + events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu']; L.DomUtil.addClass(icon, 'leaflet-clickable'); L.DomEvent.on(icon, 'click', this._onMouseClick, this); @@ -3119,20 +3264,31 @@ L.Marker = L.Class.extend({ _onMouseClick: function (e) { var wasDragged = this.dragging && this.dragging.moved(); + if (this.hasEventListeners(e.type) || wasDragged) { L.DomEvent.stopPropagation(e); } + if (wasDragged) { return; } - if (this._map.dragging && this._map.dragging.moved()) { return; } + + if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; } + this.fire(e.type, { originalEvent: e }); }, _fireMouseEvent: function (e) { + this.fire(e.type, { originalEvent: e }); + + // TODO proper custom event propagation + // this line will always be called if marker is in a FeatureGroup + if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) { + L.DomEvent.preventDefault(e); + } if (e.type !== 'mousedown') { L.DomEvent.stopPropagation(e); } @@ -3166,6 +3322,11 @@ L.marker = function (latlng, options) { }; +/* + * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon) + * to use with L.Marker. + */ + L.DivIcon = L.Icon.extend({ options: { iconSize: new L.Point(12, 12), // also can be set through CSS @@ -3205,6 +3366,9 @@ L.divIcon = function (options) { }; +/* + * L.Popup is used for displaying popups on the map. + */ L.Map.mergeOptions({ closePopupOnClick: true @@ -3221,13 +3385,15 @@ L.Popup = L.Class.extend({ closeButton: true, offset: new L.Point(0, 6), autoPanPadding: new L.Point(5, 5), - className: '' + className: '', + zoomAnimation: true }, initialize: function (options, source) { L.setOptions(this, options); this._source = source; + this._animated = L.Browser.any3d && this.options.zoomAnimation; }, onAdd: function (map) { @@ -3247,7 +3413,7 @@ L.Popup = L.Class.extend({ map.on('viewreset', this._updatePosition, this); - if (L.Browser.any3d) { + if (this._animated) { map.on('zoomanim', this._zoomAnimation, this); } @@ -3316,7 +3482,8 @@ L.Popup = L.Class.extend({ _initLayout: function () { var prefix = 'leaflet-popup', - containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-animated', + containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' + + (this._animated ? 'animated' : 'hide'), container = this._container = L.DomUtil.create('div', containerClass), closeButton; @@ -3402,15 +3569,15 @@ L.Popup = L.Class.extend({ if (!this._map) { return; } var pos = this._map.latLngToLayerPoint(this._latlng), - is3d = L.Browser.any3d, + animated = this._animated, offset = this.options.offset; - if (is3d) { + if (animated) { L.DomUtil.setPosition(this._container, pos); } - this._containerBottom = -offset.y - (is3d ? 0 : pos.y); - this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (is3d ? 0 : pos.x); + this._containerBottom = -offset.y - (animated ? 0 : pos.y); + this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x); //Bottom position the popup in case the height of the popup changes (images loading etc) this._container.style.bottom = this._containerBottom + 'px'; @@ -3432,7 +3599,7 @@ L.Popup = L.Class.extend({ layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom); - if (L.Browser.any3d) { + if (this._animated) { layerPos._add(L.DomUtil.getPosition(this._container)); } @@ -3472,7 +3639,7 @@ L.popup = function (options, source) { /* - * Popup extension to L.Marker, adding openPopup & bindPopup methods. + * Popup extension to L.Marker, adding popup-related methods. */ L.Marker.include({ @@ -3533,6 +3700,9 @@ L.Marker.include({ }); +/* + * Adds popup-related methods to L.Map. + */ L.Map.include({ openPopup: function (popup) { @@ -3555,7 +3725,8 @@ L.Map.include({ /* - * L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer. + * L.LayerGroup is a class to combine several layers into one so that + * you can manipulate the group (e.g. add/remove it) as one layer. */ L.LayerGroup = L.Class.extend({ @@ -3638,6 +3809,10 @@ L.LayerGroup = L.Class.extend({ method.call(context, this._layers[i]); } } + }, + + setZIndex: function (zIndex) { + return this.invoke('setZIndex', zIndex); } }); @@ -3647,7 +3822,8 @@ L.layerGroup = function (layers) { /* - * L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers. + * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods + * shared between a group of interactive layers (like vectors or markers). */ L.FeatureGroup = L.LayerGroup.extend({ @@ -3667,7 +3843,7 @@ L.FeatureGroup = L.LayerGroup.extend({ L.LayerGroup.prototype.addLayer.call(this, layer); if (this._popupContent && layer.bindPopup) { - layer.bindPopup(this._popupContent); + layer.bindPopup(this._popupContent, this._popupOptions); } return this.fire('layeradd', {layer: layer}); @@ -3686,9 +3862,10 @@ L.FeatureGroup = L.LayerGroup.extend({ return this.fire('layerremove', {layer: layer}); }, - bindPopup: function (content) { + bindPopup: function (content, options) { this._popupContent = content; - return this.invoke('bindPopup', content); + this._popupOptions = options; + return this.invoke('bindPopup', content, options); }, setStyle: function (style) { @@ -3775,6 +3952,8 @@ L.Path = L.Class.extend({ this._map._pathRoot.appendChild(this._container); } + this.fire('add'); + map.on({ 'viewreset': this.projectLatlngs, 'moveend': this._updatePath @@ -3789,6 +3968,8 @@ L.Path = L.Class.extend({ onRemove: function (map) { map._pathRoot.removeChild(this._container); + // Need to fire remove event before we set _map to null as the event hooks might need the object + this.fire('remove'); this._map = null; if (L.Browser.vml) { @@ -3797,8 +3978,6 @@ L.Path = L.Class.extend({ this._fill = null; } - this.fire('remove'); - map.off({ 'viewreset': this.projectLatlngs, 'moveend': this._updatePath @@ -3841,6 +4020,10 @@ L.Map.include({ }); +/* + * Extends L.Path with SVG-specific rendering code. + */ + L.Path.SVG_NS = 'http://www.w3.org/2000/svg'; L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect); @@ -4002,13 +4185,12 @@ L.Map.include({ } }, - _animatePathZoom: function (opt) { - var scale = this.getZoomScale(opt.zoom), - offset = this._getCenterOffset(opt.center), - translate = offset.multiplyBy(-scale)._add(this._pathViewport.min); + _animatePathZoom: function (e) { + var scale = this.getZoomScale(e.zoom), + offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min); this._pathRoot.style[L.DomUtil.TRANSFORM] = - L.DomUtil.getTranslateString(translate) + ' scale(' + scale + ') '; + L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') '; this._pathZooming = true; }, @@ -4054,7 +4236,7 @@ L.Map.include({ /* - * Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method. + * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods. */ L.Path.include({ @@ -4082,7 +4264,7 @@ L.Path.include({ if (this._popup) { this._popup = null; this - .off('click', this.openPopup) + .off('click', this._openPopup) .off('remove', this.closePopup); this._popupHandlersAdded = false; @@ -4193,11 +4375,15 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ stroke.weight = options.weight + 'px'; stroke.color = options.color; stroke.opacity = options.opacity; + if (options.dashArray) { - stroke.dashStyle = options.dashArray.replace(/ *, */g, ' '); + stroke.dashStyle = options.dashArray instanceof Array ? + options.dashArray.join(' ') : + options.dashArray.replace(/ *, */g, ' '); } else { stroke.dashStyle = ''; } + } else if (stroke) { container.removeChild(stroke); this._stroke = null; @@ -4210,6 +4396,7 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ } fill.color = options.fillColor || options.color; fill.opacity = options.fillOpacity; + } else if (fill) { container.removeChild(fill); this._fill = null; @@ -4277,6 +4464,10 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : .off('viewreset', this.projectLatlngs, this) .off('moveend', this._updatePath, this); + if (this.options.clickable) { + this._map.off('click', this._onClick, this); + } + this._requestUpdate(); this._map = null; @@ -4344,16 +4535,12 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : this._updateStyle(); if (options.fill) { - if (options.fillOpacity < 1) { - ctx.globalAlpha = options.fillOpacity; - } + ctx.globalAlpha = options.fillOpacity; ctx.fill(); } if (options.stroke) { - if (options.opacity < 1) { - ctx.globalAlpha = options.opacity; - } + ctx.globalAlpha = options.opacity; ctx.stroke(); } @@ -4372,7 +4559,12 @@ L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : _onClick: function (e) { if (this._containsPoint(e.layerPoint)) { - this.fire('click', e); + this.fire('click', { + latlng: e.latlng, + layerPoint: e.layerPoint, + containerPoint: e.containerPoint, + originalEvent: e + }); } } }); @@ -4426,6 +4618,8 @@ L.Map.include((L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? {} * and polylines (clipping, simplification, distances, etc.) */ +/*jshint bitwise:false */ // allow bitwise oprations for this file + L.LineUtil = { // Simplify polyline with vertex reduction and Douglas-Peucker simplification. @@ -4517,16 +4711,11 @@ L.LineUtil = { return reducedPoints; }, - /*jshint bitwise:false */ // temporarily allow bitwise oprations - // Cohen-Sutherland line clipping algorithm. // Used to avoid rendering parts of a polyline that are not currently visible. clipSegment: function (a, b, bounds, useLastCode) { - var min = bounds.min, - max = bounds.max, - - codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), + var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds), codeB = this._getBitCode(b, bounds), codeOut, p, newCode; @@ -4592,8 +4781,6 @@ L.LineUtil = { return code; }, - /*jshint bitwise:true */ - // square distance (to avoid unnecessary Math.sqrt calls) _sqDist: function (p1, p2) { var dx = p2.x - p1.x, @@ -4630,20 +4817,15 @@ L.LineUtil = { }; +/* + * L.Polygon is used to display polylines on a map. + */ + L.Polyline = L.Path.extend({ initialize: function (latlngs, options) { L.Path.prototype.initialize.call(this, options); this._latlngs = this._convertLatLngs(latlngs); - - // TODO refactor: move to Polyline.Edit.js - if (L.Handler.PolyEdit) { - this.editing = new L.Handler.PolyEdit(this); - - if (this.options.editable) { - this.editing.enable(); - } - } }, options: { @@ -4682,7 +4864,7 @@ L.Polyline = L.Path.extend({ return this.redraw(); }, - spliceLatLngs: function (index, howMany) { + spliceLatLngs: function () { // (Number index, Number howMany) var removed = [].splice.apply(this._latlngs, arguments); this._convertLatLngs(this._latlngs); this.redraw(); @@ -4722,27 +4904,10 @@ L.Polyline = L.Path.extend({ return bounds; }, - // TODO refactor: move to Polyline.Edit.js - onAdd: function (map) { - L.Path.prototype.onAdd.call(this, map); - - if (this.editing && this.editing.enabled()) { - this.editing.addHooks(); - } - }, - - onRemove: function (map) { - if (this.editing && this.editing.enabled()) { - this.editing.removeHooks(); - } - - L.Path.prototype.onRemove.call(this, map); - }, - _convertLatLngs: function (latlngs) { var i, len; for (i = 0, len = latlngs.length; i < len; i++) { - if (latlngs[i] instanceof Array && typeof latlngs[i][0] !== 'number') { + if (L.Util.isArray(latlngs[i]) && typeof latlngs[i][0] !== 'number') { return; } latlngs[i] = L.latLng(latlngs[i]); @@ -4826,10 +4991,10 @@ L.polyline = function (latlngs, options) { /* - * L.PolyUtil contains utilify functions for polygons (clipping, etc.). + * L.PolyUtil contains utility functions for polygons (clipping, etc.). */ -/*jshint bitwise:false */ // allow bitwise oprations here +/*jshint bitwise:false */ // allow bitwise operations here L.PolyUtil = {}; @@ -4838,9 +5003,7 @@ L.PolyUtil = {}; * Used to avoid rendering parts of a polygon that are not currently visible. */ L.PolyUtil.clipPolygon = function (points, bounds) { - var min = bounds.min, - max = bounds.max, - clippedPoints, + var clippedPoints, edges = [1, 4, 2, 8], i, j, k, a, b, @@ -4883,8 +5046,6 @@ L.PolyUtil.clipPolygon = function (points, bounds) { return points; }; -/*jshint bitwise:true */ - /* * L.Polygon is used to display polygons on a map. @@ -4898,7 +5059,7 @@ L.Polygon = L.Polyline.extend({ initialize: function (latlngs, options) { L.Polyline.prototype.initialize.call(this, latlngs, options); - if (latlngs && (latlngs[0] instanceof Array) && (typeof latlngs[0][0] !== 'number')) { + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { this._latlngs = this._convertLatLngs(latlngs[0]); this._holes = latlngs.slice(1); } @@ -4913,7 +5074,7 @@ L.Polygon = L.Polyline.extend({ if (!this._holes) { return; } - var i, j, len, len2, hole; + var i, j, len, len2; for (i = 0, len = this._holes.length; i < len; i++) { this._holePoints[i] = []; @@ -5003,7 +5164,7 @@ L.polygon = function (latlngs, options) { /* - * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds + * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object. */ L.Rectangle = L.Polygon.extend({ @@ -5021,8 +5182,7 @@ L.Rectangle = L.Polygon.extend({ latLngBounds.getSouthWest(), latLngBounds.getNorthWest(), latLngBounds.getNorthEast(), - latLngBounds.getSouthEast(), - latLngBounds.getSouthWest() + latLngBounds.getSouthEast() ]; } }); @@ -5060,7 +5220,7 @@ L.Circle = L.Path.extend({ projectLatlngs: function () { var lngRadius = this._getLngRadius(), - latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius, true), + latlng2 = new L.LatLng(this._latlng.lat, this._latlng.lng - lngRadius), point2 = this._map.latLngToLayerPoint(latlng2); this._point = this._map.latLngToLayerPoint(this._latlng); @@ -5150,6 +5310,11 @@ L.CircleMarker = L.Circle.extend({ projectLatlngs: function () { this._point = this._map.latLngToLayerPoint(this._latlng); }, + + _updateStyle : function () { + L.Circle.prototype._updateStyle.call(this); + this.setRadius(this.options.radius); + }, setRadius: function (radius) { this._radius = radius; @@ -5162,6 +5327,9 @@ L.circleMarker = function (latlng, options) { }; +/* + * Extends L.Polyline to be able to manually detect clicks on Canvas-rendered polylines. + */ L.Polyline.include(!L.Path.CANVAS ? {} : { _containsPoint: function (p, closed) { @@ -5191,6 +5359,9 @@ L.Polyline.include(!L.Path.CANVAS ? {} : { }); +/* + * Extends L.Polygon to be able to manually detect clicks on Canvas-rendered polygons. + */ L.Polygon.include(!L.Path.CANVAS ? {} : { _containsPoint: function (p) { @@ -5228,7 +5399,7 @@ L.Polygon.include(!L.Path.CANVAS ? {} : { /* - * Circle canvas specific drawing parts. + * Extends L.Circle with Canvas-specific code. */ L.Circle.include(!L.Path.CANVAS ? {} : { @@ -5247,7 +5418,12 @@ L.Circle.include(!L.Path.CANVAS ? {} : { }); +/* + * L.GeoJSON turns any GeoJSON data into a Leaflet layer. + */ + L.GeoJSON = L.FeatureGroup.extend({ + initialize: function (geojson, options) { L.setOptions(this, options); @@ -5259,12 +5435,15 @@ L.GeoJSON = L.FeatureGroup.extend({ }, addData: function (geojson) { - var features = geojson instanceof Array ? geojson : geojson.features, + var features = L.Util.isArray(geojson) ? geojson : geojson.features, i, len; if (features) { for (i = 0, len = features.length; i < len; i++) { - this.addData(features[i]); + // Only add this if geometry or geometries are set and not null + if (features[i].geometries || features[i].geometry) { + this.addData(features[i]); + } } return this; } @@ -5276,6 +5455,7 @@ L.GeoJSON = L.FeatureGroup.extend({ var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer); layer.feature = geojson; + layer.defaultOptions = layer.options; this.resetStyle(layer); if (options.onEachFeature) { @@ -5288,6 +5468,9 @@ L.GeoJSON = L.FeatureGroup.extend({ resetStyle: function (layer) { var style = this.options.style; if (style) { + // reset any custom styles + L.Util.extend(layer.options, layer.defaultOptions); + this._setLayerStyle(layer, style); } }, @@ -5340,13 +5523,17 @@ L.extend(L.GeoJSON, { latlngs = this.coordsToLatLngs(coords, 1); return new L.MultiPolyline(latlngs); - case "MultiPolygon": + case 'MultiPolygon': latlngs = this.coordsToLatLngs(coords, 2); return new L.MultiPolygon(latlngs); - case "GeometryCollection": + case 'GeometryCollection': for (i = 0, len = geometry.geometries.length; i < len; i++) { - layer = this.geometryToLayer(geometry.geometries[i], pointToLayer); + layer = this.geometryToLayer({ + geometry: geometry.geometries[i], + type: 'Feature', + properties: geojson.properties + }, pointToLayer); layers.push(layer); } return new L.FeatureGroup(layers); @@ -5360,7 +5547,7 @@ L.extend(L.GeoJSON, { var lat = parseFloat(coords[reverse ? 0 : 1]), lng = parseFloat(coords[reverse ? 1 : 0]); - return new L.LatLng(lat, lng, true); + return new L.LatLng(lat, lng); }, coordsToLatLngs: function (coords, levelsDeep, reverse) { // (Array, Number, Boolean) -> Array @@ -5390,7 +5577,7 @@ L.geoJson = function (geojson, options) { */ L.DomEvent = { - /* inpired by John Resig, Dean Edwards and YUI addEvent implementations */ + /* inspired by John Resig, Dean Edwards and YUI addEvent implementations */ addListener: function (obj, type, fn, context) { // (HTMLElement, String, Function[, Object]) var id = L.stamp(fn), @@ -5405,10 +5592,12 @@ L.DomEvent = { if (L.Browser.msTouch && type.indexOf('touch') === 0) { return this.addMsTouchListener(obj, type, handler, id); - } else if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { - return this.addDoubleTapListener(obj, handler, id); + } + if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { + this.addDoubleTapListener(obj, handler, id); + } - } else if ('addEventListener' in obj) { + if ('addEventListener' in obj) { if (type === 'mousewheel') { obj.addEventListener('DOMMouseScroll', handler, false); @@ -5486,8 +5675,11 @@ L.DomEvent = { var stop = L.DomEvent.stopPropagation; + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.addListener(el, L.Draggable.START[i], stop); + } + return L.DomEvent - .addListener(el, L.Draggable.START, stop) .addListener(el, 'click', stop) .addListener(el, 'dblclick', stop); }, @@ -5547,9 +5739,8 @@ L.DomEvent = { return (related !== el); }, - /*jshint noarg:false */ _getEvent: function () { // evil magic for IE - + /*jshint noarg:false */ var e = window.event; if (!e) { var caller = arguments.callee.caller; @@ -5563,7 +5754,6 @@ L.DomEvent = { } return e; } - /*jshint noarg:false */ }; L.DomEvent.on = L.DomEvent.addListener; @@ -5578,9 +5768,17 @@ L.Draggable = L.Class.extend({ includes: L.Mixin.Events, statics: { - START: L.Browser.touch ? 'touchstart' : 'mousedown', - END: L.Browser.touch ? 'touchend' : 'mouseup', - MOVE: L.Browser.touch ? 'touchmove' : 'mousemove', + START: L.Browser.touch ? ['touchstart', 'mousedown'] : ['mousedown'], + END: { + mousedown: 'mouseup', + touchstart: 'touchend', + MSPointerDown: 'touchend' + }, + MOVE: { + mousedown: 'mousemove', + touchstart: 'touchmove', + MSPointerDown: 'touchmove' + }, TAP_TOLERANCE: 15 }, @@ -5593,14 +5791,18 @@ L.Draggable = L.Class.extend({ enable: function () { if (this._enabled) { return; } - L.DomEvent.on(this._dragStartTarget, L.Draggable.START, this._onDown, this); + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.on(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } this._enabled = true; }, disable: function () { if (!this._enabled) { return; } - L.DomEvent.off(this._dragStartTarget, L.Draggable.START, this._onDown); + for (var i = L.Draggable.START.length - 1; i >= 0; i--) { + L.DomEvent.off(this._dragStartTarget, L.Draggable.START[i], this._onDown, this); + } this._enabled = false; this._moved = false; }, @@ -5648,8 +5850,8 @@ L.Draggable = L.Class.extend({ }, this), 1000); } - L.DomEvent.on(document, L.Draggable.MOVE, this._onMove, this); - L.DomEvent.on(document, L.Draggable.END, this._onUp, this); + L.DomEvent.on(document, L.Draggable.MOVE[e.type], this._onMove, this); + L.DomEvent.on(document, L.Draggable.END[e.type], this._onUp, this); }, _onMove: function (e) { @@ -5710,8 +5912,12 @@ L.Draggable = L.Class.extend({ this._restoreCursor(); } - L.DomEvent.off(document, L.Draggable.MOVE, this._onMove); - L.DomEvent.off(document, L.Draggable.END, this._onUp); + for (var i in L.Draggable.MOVE) { + if (L.Draggable.MOVE.hasOwnProperty(i)) { + L.DomEvent.off(document, L.Draggable.MOVE[i], this._onMove); + L.DomEvent.off(document, L.Draggable.END[i], this._onUp); + } + } if (this._moved) { // ensure drag is not fired after dragend @@ -5750,8 +5956,9 @@ L.Draggable = L.Class.extend({ /* - * L.Handler classes are used internally to inject interaction features to classes like Map and Marker. - */ + L.Handler is a base class for handler classes that are used internally to inject + interaction features like dragging to classes like Map and Marker. +*/ L.Handler = L.Class.extend({ initialize: function (map) { @@ -5779,7 +5986,7 @@ L.Handler = L.Class.extend({ /* - * L.Handler.MapDrag is used internally by L.Map to make the map draggable. + * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default. */ L.Map.mergeOptions({ @@ -5794,7 +6001,7 @@ L.Map.mergeOptions({ longPress: true, // TODO refactor, move to CRS - worldCopyJump: true + worldCopyJump: false }); L.Map.Drag = L.Handler.extend({ @@ -5863,6 +6070,7 @@ L.Map.Drag = L.Handler.extend({ }, _onViewReset: function () { + // TODO fix hardcoded Earth values var pxCenter = this._map.getSize()._divideBy(2), pxWorldCenter = this._map.latLngToLayerPoint(new L.LatLng(0, 0)); @@ -5872,8 +6080,7 @@ L.Map.Drag = L.Handler.extend({ _onPreDrag: function () { // TODO refactor to be able to adjust map pane position after zoom - var map = this._map, - worldWidth = this._worldWidth, + var worldWidth = this._worldWidth, halfWidth = Math.round(worldWidth / 2), dx = this._initialWorldOffset, x = this._draggable._newPos.x, @@ -5889,9 +6096,7 @@ L.Map.Drag = L.Handler.extend({ options = map.options, delay = +new Date() - this._lastTime, - noInertia = !options.inertia || - delay > options.inertiaThreshold || - !this._positions[0]; + noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; if (noInertia) { map.fire('moveend'); @@ -5900,18 +6105,19 @@ L.Map.Drag = L.Handler.extend({ var direction = this._lastPos.subtract(this._positions[0]), duration = (this._lastTime + delay - this._times[0]) / 1000, + ease = options.easeLinearity, - speedVector = direction.multiplyBy(options.easeLinearity / duration), + speedVector = direction.multiplyBy(ease / duration), speed = speedVector.distanceTo(new L.Point(0, 0)), limitedSpeed = Math.min(options.inertiaMaxSpeed, speed), limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed), - decelerationDuration = limitedSpeed / (options.inertiaDeceleration * options.easeLinearity), + decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease), offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round(); L.Util.requestAnimFrame(function () { - map.panBy(offset, decelerationDuration, options.easeLinearity); + map.panBy(offset, decelerationDuration, ease); }); } @@ -5932,7 +6138,7 @@ L.Map.addInitHook('addHandler', 'dragging', L.Map.Drag); /* - * L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming. + * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default. */ L.Map.mergeOptions({ @@ -5955,12 +6161,13 @@ L.Map.DoubleClickZoom = L.Handler.extend({ L.Map.addInitHook('addHandler', 'doubleClickZoom', L.Map.DoubleClickZoom); + /* * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map. */ L.Map.mergeOptions({ - scrollWheelZoom: !L.Browser.touch || L.Browser.msTouch + scrollWheelZoom: true }); L.Map.ScrollWheelZoom = L.Handler.extend({ @@ -5994,9 +6201,10 @@ L.Map.ScrollWheelZoom = L.Handler.extend({ _performZoom: function () { var map = this._map, - delta = Math.round(this._delta), + delta = this._delta, zoom = map.getZoom(); + delta = delta > 0 ? Math.ceil(delta) : Math.round(delta); delta = Math.max(Math.min(delta, 4), -4); delta = map._limitZoom(zoom + delta) - zoom; @@ -6026,6 +6234,10 @@ L.Map.ScrollWheelZoom = L.Handler.extend({ L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); +/* + * Extends the event handling code with double tap support for mobile browsers. + */ + L.extend(L.DomEvent, { _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart', @@ -6061,7 +6273,9 @@ L.extend(L.DomEvent, { doubleTap = (delta > 0 && delta <= delay); last = now; } + function onTouchEnd(e) { + /*jshint forin:false */ if (L.Browser.msTouch) { var idx = trackedTouches.indexOf(e.pointerId); if (idx === -1) { @@ -6075,14 +6289,13 @@ L.extend(L.DomEvent, { //Work around .type being readonly with MSPointer* events var newTouch = { }, prop; + for (var i in touch) { - if (true) { //Make JSHint happy, we want to copy all properties - prop = touch[i]; - if (typeof prop === 'function') { - newTouch[i] = prop.bind(touch); - } else { - newTouch[i] = prop; - } + prop = touch[i]; + if (typeof prop === 'function') { + newTouch[i] = prop.bind(touch); + } else { + newTouch[i] = prop; } } touch = newTouch; @@ -6119,6 +6332,10 @@ L.extend(L.DomEvent, { }); +/* + * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices. + */ + L.extend(L.DomEvent, { _msTouches: [], @@ -6357,7 +6574,7 @@ L.Map.TouchZoom = L.Handler.extend({ L.DomUtil.getScaleString(this._scale, this._startCenter); }, - _onTouchEnd: function (e) { + _onTouchEnd: function () { if (!this._moved || !this._zooming) { return; } var map = this._map; @@ -6397,7 +6614,8 @@ L.Map.addInitHook('addHandler', 'touchZoom', L.Map.TouchZoom); /* - * L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box). + * L.Handler.ShiftDragZoom is used to add shift-drag zoom interaction to the map + * (zoom to a selected bounding box), enabled by default. */ L.Map.mergeOptions({ @@ -6469,9 +6687,11 @@ L.Map.BoxZoom = L.Handler.extend({ .off(document, 'mouseup', this._onMouseUp); var map = this._map, - layerPoint = map.mouseEventToLayerPoint(e), + layerPoint = map.mouseEventToLayerPoint(e); + + if (this._startLayerPoint.equals(layerPoint)) { return; } - bounds = new L.LatLngBounds( + var bounds = new L.LatLngBounds( map.layerPointToLatLng(this._startLayerPoint), map.layerPointToLatLng(layerPoint)); @@ -6486,6 +6706,10 @@ L.Map.BoxZoom = L.Handler.extend({ L.Map.addInitHook('addHandler', 'boxZoom', L.Map.BoxZoom); +/* + * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default. + */ + L.Map.mergeOptions({ keyboard: true, keyboardPanOffset: 80, @@ -6494,14 +6718,13 @@ L.Map.mergeOptions({ L.Map.Keyboard = L.Handler.extend({ - // list of e.keyCode values for particular actions keyCodes: { left: [37], right: [39], down: [40], up: [38], zoomIn: [187, 107, 61], - zoomOut: [189, 109] + zoomOut: [189, 109, 173] }, initialize: function (map) { @@ -6520,9 +6743,9 @@ L.Map.Keyboard = L.Handler.extend({ } L.DomEvent - .addListener(container, 'focus', this._onFocus, this) - .addListener(container, 'blur', this._onBlur, this) - .addListener(container, 'mousedown', this._onMouseDown, this); + .on(container, 'focus', this._onFocus, this) + .on(container, 'blur', this._onBlur, this) + .on(container, 'mousedown', this._onMouseDown, this); this._map .on('focus', this._addHooks, this) @@ -6535,9 +6758,9 @@ L.Map.Keyboard = L.Handler.extend({ var container = this._map._container; L.DomEvent - .removeListener(container, 'focus', this._onFocus, this) - .removeListener(container, 'blur', this._onBlur, this) - .removeListener(container, 'mousedown', this._onMouseDown, this); + .off(container, 'focus', this._onFocus, this) + .off(container, 'blur', this._onBlur, this) + .off(container, 'mousedown', this._onMouseDown, this); this._map .off('focus', this._addHooks, this) @@ -6593,21 +6816,26 @@ L.Map.Keyboard = L.Handler.extend({ }, _addHooks: function () { - L.DomEvent.addListener(document, 'keydown', this._onKeyDown, this); + L.DomEvent.on(document, 'keydown', this._onKeyDown, this); }, _removeHooks: function () { - L.DomEvent.removeListener(document, 'keydown', this._onKeyDown, this); + L.DomEvent.off(document, 'keydown', this._onKeyDown, this); }, _onKeyDown: function (e) { - var key = e.keyCode; + var key = e.keyCode, + map = this._map; if (this._panKeys.hasOwnProperty(key)) { - this._map.panBy(this._panKeys[key]); + map.panBy(this._panKeys[key]); + + if (map.options.maxBounds) { + map.panInsideBounds(map.options.maxBounds); + } } else if (this._zoomKeys.hasOwnProperty(key)) { - this._map.setZoom(this._map.getZoom() + this._zoomKeys[key]); + map.setZoom(map.getZoom() + this._zoomKeys[key]); } else { return; @@ -6648,14 +6876,14 @@ L.Handler.MarkerDrag = L.Handler.extend({ return this._draggable && this._draggable._moved; }, - _onDragStart: function (e) { + _onDragStart: function () { this._marker .closePopup() .fire('movestart') .fire('dragstart'); }, - _onDrag: function (e) { + _onDrag: function () { var marker = this._marker, shadow = marker._shadow, iconPos = L.DomUtil.getPosition(marker._icon), @@ -6681,6 +6909,10 @@ L.Handler.MarkerDrag = L.Handler.extend({ }); +/* + * L.Handler.PolyEdit is an editing handler for polylines and polygons. + */ + L.Handler.PolyEdit = L.Handler.extend({ options: { icon: new L.DivIcon({ @@ -6904,7 +7136,34 @@ L.Handler.PolyEdit = L.Handler.extend({ } }); +L.Polyline.addInitHook(function () { + + if (L.Handler.PolyEdit) { + this.editing = new L.Handler.PolyEdit(this); + + if (this.options.editable) { + this.editing.enable(); + } + } + this.on('add', function () { + if (this.editing && this.editing.enabled()) { + this.editing.addHooks(); + } + }); + + this.on('remove', function () { + if (this.editing && this.editing.enabled()) { + this.editing.removeHooks(); + } + }); +}); + + +/* + * L.Control is a base class for implementing map controls. Handles positioning. + * All other controls extend from this class. + */ L.Control = L.Class.extend({ options: { @@ -6973,6 +7232,10 @@ L.control = function (options) { }; +/* + * Adds control-related methods to L.Map. + */ + L.Map.include({ addControl: function (control) { control.addTo(this); @@ -7004,23 +7267,44 @@ L.Map.include({ }); +/* + * L.Control.Zoom is used for the default zoom buttons on the map. + */ + L.Control.Zoom = L.Control.extend({ options: { position: 'topleft' }, onAdd: function (map) { - var className = 'leaflet-control-zoom', - container = L.DomUtil.create('div', className); + var zoomName = 'leaflet-control-zoom', + barName = 'leaflet-bar', + partName = barName + '-part', + container = L.DomUtil.create('div', zoomName + ' ' + barName); this._map = map; - this._createButton('Zoom in', className + '-in', container, this._zoomIn, this); - this._createButton('Zoom out', className + '-out', container, this._zoomOut, this); + this._zoomInButton = this._createButton('+', 'Zoom in', + zoomName + '-in ' + + partName + ' ' + + partName + '-top', + container, this._zoomIn, this); + + this._zoomOutButton = this._createButton('-', 'Zoom out', + zoomName + '-out ' + + partName + ' ' + + partName + '-bottom', + container, this._zoomOut, this); + + map.on('zoomend', this._updateDisabled, this); return container; }, + onRemove: function (map) { + map.off('zoomend', this._updateDisabled, this); + }, + _zoomIn: function (e) { this._map.zoomIn(e.shiftKey ? 3 : 1); }, @@ -7029,19 +7313,37 @@ L.Control.Zoom = L.Control.extend({ this._map.zoomOut(e.shiftKey ? 3 : 1); }, - _createButton: function (title, className, container, fn, context) { + _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', L.DomEvent.stopPropagation) - .on(link, 'mousedown', L.DomEvent.stopPropagation) - .on(link, 'dblclick', L.DomEvent.stopPropagation) + .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; + }, + + _updateDisabled: function () { + var map = this._map, + className = 'leaflet-control-zoom-disabled'; + + L.DomUtil.removeClass(this._zoomInButton, className); + L.DomUtil.removeClass(this._zoomOutButton, className); + + if (map._zoom === map.getMinZoom()) { + L.DomUtil.addClass(this._zoomOutButton, className); + } + if (map._zoom === map.getMaxZoom()) { + L.DomUtil.addClass(this._zoomInButton, className); + } } }); @@ -7062,6 +7364,10 @@ L.control.zoom = function (options) { +/* + * L.Control.Attribution is used for displaying attribution on the map (added by default). + */ + L.Control.Attribution = L.Control.extend({ options: { position: 'bottomright', @@ -7173,6 +7479,10 @@ L.control.attribution = function (options) { }; +/* + * L.Control.Scale is used for displaying metric/imperial scale on the map. + */ + L.Control.Scale = L.Control.extend({ options: { position: 'bottomleft', @@ -7283,6 +7593,10 @@ L.control.scale = function (options) { }; +/* + * L.Control.Layers is a control to allow users to switch between different layers on the map. + */ + L.Control.Layers = L.Control.extend({ options: { collapsed: true, @@ -7315,16 +7629,16 @@ L.Control.Layers = L.Control.extend({ this._update(); map - .on('layeradd', this._update, this) - .on('layerremove', this._update, this); + .on('layeradd', this._onLayerChange, this) + .on('layerremove', this._onLayerChange, this); return this._container; }, onRemove: function (map) { map - .off('layeradd', this._update) - .off('layerremove', this._update); + .off('layeradd', this._onLayerChange) + .off('layerremove', this._onLayerChange); }, addBaseLayer: function (layer, name) { @@ -7352,6 +7666,7 @@ L.Control.Layers = L.Control.extend({ if (!L.Browser.touch) { L.DomEvent.disableClickPropagation(container); + L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation); } else { L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); } @@ -7406,7 +7721,7 @@ L.Control.Layers = L.Control.extend({ }, _update: function () { - if (!this._container || this._handlingClick) { + if (!this._container) { return; } @@ -7428,6 +7743,14 @@ L.Control.Layers = L.Control.extend({ this._separator.style.display = (overlaysPresent && baseLayersPresent ? '' : 'none'); }, + _onLayerChange: function (e) { + var id = L.stamp(e.layer); + + if (this._layers[id] && !this._handlingClick) { + this._update(); + } + }, + // IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see http://bit.ly/PqYLBe) _createRadioElement: function (name, checked) { @@ -7469,6 +7792,8 @@ L.Control.Layers = L.Control.extend({ var container = obj.overlay ? this._overlaysList : this._baseLayersList; container.appendChild(label); + + return label; }, _onInputClick: function () { @@ -7494,6 +7819,7 @@ L.Control.Layers = L.Control.extend({ } if (baseLayer) { + this._map.setZoom(this._map.getZoom()); this._map.fire('baselayerchange', {layer: baseLayer}); } @@ -7538,7 +7864,7 @@ L.PosAnimation = L.Class.extend({ // toggle reflow, Chrome flickers for some reason if you don't do this L.Util.falseFn(el.offsetWidth); - // there's no native way to track value updates of tranisitioned properties, so we imitate this + // there's no native way to track value updates of transitioned properties, so we imitate this this._stepTimer = setInterval(L.bind(this.fire, this, 'step'), 50); }, @@ -7591,6 +7917,9 @@ L.PosAnimation = L.Class.extend({ }); +/* + * Extends L.Map to handle panning animations. + */ L.Map.include({ @@ -7642,7 +7971,7 @@ L.Map.include({ L.DomUtil.addClass(this._mapPane, 'leaflet-pan-anim'); - var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset); + var newPos = L.DomUtil.getPosition(this._mapPane).subtract(offset)._round(); this._panAnim.run(this._mapPane, newPos, duration || 0.25, easeLinearity); return this; @@ -7747,6 +8076,10 @@ L.PosAnimation = L.DomUtil.TRANSITION ? L.PosAnimation : L.PosAnimation.extend({ }); +/* + * Extends L.Map to handle zoom animations. + */ + L.Map.mergeOptions({ zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android23 && !L.Browser.mobileOpera }); @@ -7790,7 +8123,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : { return true; }, - _catchTransitionEnd: function (e) { + _catchTransitionEnd: function () { if (this._animatingZoom) { this._onZoomTransitionEnd(); } @@ -7888,11 +8221,11 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : { _onZoomTransitionEnd: function () { this._restoreTileFront(); - L.Util.falseFn(this._tileBg.offsetWidth); // force reflow - this._resetView(this._animateToCenter, this._animateToZoom, true, true); L.DomUtil.removeClass(this._mapPane, 'leaflet-zoom-anim'); + L.Util.falseFn(this._tileBg.offsetWidth); // force reflow this._animatingZoom = false; + this._resetView(this._animateToCenter, this._animateToZoom, true, true); if (L.Draggable) { L.Draggable._disabled = false; @@ -7915,7 +8248,7 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : { /* - * Provides L.Map with convenient shortcuts for W3C geolocation. + * Provides L.Map with convenient shortcuts for using browser geolocation features. */ L.Map.include({ @@ -8003,6 +8336,4 @@ L.Map.include({ }); - - -}(this)); +}(this, document)); \ No newline at end of file diff --git a/vendor/assets/leaflet/leaflet.locationfilter.js b/vendor/assets/leaflet/leaflet.locationfilter.js index 8e5121f62..a17f1a5af 100644 --- a/vendor/assets/leaflet/leaflet.locationfilter.js +++ b/vendor/assets/leaflet/leaflet.locationfilter.js @@ -294,7 +294,7 @@ L.LocationFilter = L.Class.extend({ this._eastRect = this._drawRectangle(this._eastBounds); this._southRect = this._drawRectangle(this._southBounds); this._innerRect = this._drawRectangle(this.getBounds(), { - fillColor: "transparent", + fillOpacity: 0, stroke: true, color: "white", weight: 1, diff --git a/vendor/assets/leaflet/leaflet.zoom.js b/vendor/assets/leaflet/leaflet.zoom.js index 6ce96757c..fe7e2f5d2 100644 --- a/vendor/assets/leaflet/leaflet.zoom.js +++ b/vendor/assets/leaflet/leaflet.zoom.js @@ -7,21 +7,18 @@ L.Control.Zoomslider = L.Control.extend({ onAdd: function (map) { var className = 'leaflet-control-zoomslider', - container = L.DomUtil.create('div', className); + container = L.DomUtil.create('div', className); + + L.DomEvent.disableClickPropagation(container); - L.DomEvent - .on(container, 'click', L.DomEvent.stopPropagation) - .on(container, 'mousedown', L.DomEvent.stopPropagation) - .on(container, 'dblclick', L.DomEvent.stopPropagation); - this._map = map; - this._zoomInButton = this._createButton('+', 'Zoom in', className + '-in' + this._zoomInButton = this._createButton('Zoom in', className + '-in' , container, this._zoomIn , this); this._createSlider(className + '-slider', container, map); - this._zoomOutButton = this._createButton('-', 'Zoom out', className + '-out' + this._zoomOutButton = this._createButton('Zoom out', className + '-out' , container, this._zoomOut, this); - + map.on('layeradd layerremove', this._refresh, this); map.whenReady(function(){ @@ -45,6 +42,11 @@ L.Control.Zoomslider = L.Control.extend({ _createSlider: function (className, container, map) { var zoomLevels = map.getMaxZoom() - map.getMinZoom(); + // This means we have no tilelayers (or that they are setup in a strange way). + // Either way we don't want to add a slider here. + if(zoomLevels == Infinity){ + return undefined; + } this._sliderHeight = this.options.stepHeight * zoomLevels; var wrapper = L.DomUtil.create('div', className + '-wrap', container); @@ -68,9 +70,8 @@ L.Control.Zoomslider = L.Control.extend({ this._map.zoomOut(e.shiftKey ? 3 : 1); }, - _createButton: function (html, title, className, container, fn, context) { + _createButton: function (title, className, container, fn, context) { var link = L.DomUtil.create('a', className, container); - link.innerHTML = html; link.href = '#'; link.title = title; @@ -82,16 +83,12 @@ L.Control.Zoomslider = L.Control.extend({ }, _createDraggable: function() { - L.DomUtil.setPosition(this._knob, new L.Point(0, 0)); - L.DomEvent - .on(this._knob - , L.Draggable.START - , L.DomEvent.stopPropagation) - .on(this._knob, 'click', L.DomEvent.stopPropagation); + L.DomUtil.setPosition(this._knob, L.point(0, 0)); + L.DomEvent.disableClickPropagation(this._knob); var bounds = new L.Bounds( - new L.Point(0, 0), - new L.Point(0, this._sliderHeight) + L.point(0, 0), + L.point(0, this._sliderHeight) ); var draggable = new L.BoundedDraggable(this._knob, this._knob, @@ -135,7 +132,7 @@ L.Control.Zoomslider = L.Control.extend({ : sliderValue; var y = this._sliderHeight - (sliderValue * this.options.stepHeight); - L.DomUtil.setPosition(this._knob, new L.Point(0, y)); + L.DomUtil.setPosition(this._knob, L.point(0, y)); } }, _toZoomLevel: function(sliderValue) { @@ -191,7 +188,7 @@ L.BoundedDraggable = L.Draggable.extend({ }, this); }, _fitPoint: function(point){ - var closest = new L.Point( + var closest = L.point( Math.min(point.x, this._bounds.max.x), Math.min(point.y, this._bounds.max.y) );