From 214ff28cd9df00154441f43ea18d2c043375dd3b Mon Sep 17 00:00:00 2001 From: Tom Hughes Date: Mon, 18 Nov 2013 17:34:22 +0000 Subject: [PATCH] Update to Leaflet 0.7 --- Vendorfile | 7 +- app/views/export/embed.html.erb | 3 - app/views/layouts/_head.html.erb | 1 - vendor/assets/leaflet/leaflet.css | 127 +-- vendor/assets/leaflet/leaflet.ie.css | 51 -- vendor/assets/leaflet/leaflet.js | 1078 ++++++++++++++++---------- 6 files changed, 745 insertions(+), 522 deletions(-) delete mode 100644 vendor/assets/leaflet/leaflet.ie.css diff --git a/Vendorfile b/Vendorfile index c1242beb7..4fc1e5348 100644 --- a/Vendorfile +++ b/Vendorfile @@ -9,14 +9,13 @@ folder 'vendor/assets' do end folder 'leaflet' do - file 'leaflet.js', 'http://cdn.leafletjs.com/leaflet-0.6.3/leaflet-src.js' - file 'leaflet.css', 'http://cdn.leafletjs.com/leaflet-0.6.3/leaflet.css' - file 'leaflet.ie.css', 'http://cdn.leafletjs.com/leaflet-0.6.3/leaflet.ie.css' + file 'leaflet.js', 'http://cdn.leafletjs.com/leaflet-0.7/leaflet-src.js' + file 'leaflet.css', 'http://cdn.leafletjs.com/leaflet-0.7/leaflet.css' [ 'layers.png', 'layers-2x.png', 'marker-icon.png', 'marker-icon-2x.png', 'marker-shadow.png' ].each do |image| - file "images/#{image}", "http://cdn.leafletjs.com/leaflet-0.6.3/images/#{image}" + file "images/#{image}", "http://cdn.leafletjs.com/leaflet-0.7/images/#{image}" end from 'git://github.com/kajic/leaflet-locationfilter.git' do diff --git a/app/views/export/embed.html.erb b/app/views/export/embed.html.erb index b683c2665..6c3ebdd16 100644 --- a/app/views/export/embed.html.erb +++ b/app/views/export/embed.html.erb @@ -4,9 +4,6 @@ OpenStreetMap Embedded <%= stylesheet_link_tag "embed", :media=> "screen" %> - <%= javascript_include_tag "embed" %> diff --git a/app/views/layouts/_head.html.erb b/app/views/layouts/_head.html.erb index 93b48c315..0a4fb143f 100644 --- a/app/views/layouts/_head.html.erb +++ b/app/views/layouts/_head.html.erb @@ -7,7 +7,6 @@ <%= stylesheet_link_tag "print-#{dir}", :media => "print" %> <%= stylesheet_link_tag "leaflet-all", :media => "screen, print" %> <%= favicon_link_tag "favicon.ico" %> diff --git a/vendor/assets/leaflet/leaflet.css b/vendor/assets/leaflet/leaflet.css index e6fb4908e..ac0cd174d 100644 --- a/vendor/assets/leaflet/leaflet.css +++ b/vendor/assets/leaflet/leaflet.css @@ -65,6 +65,16 @@ .leaflet-marker-pane { z-index: 6; } .leaflet-popup-pane { z-index: 7; } +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + /* control positioning */ @@ -160,9 +170,8 @@ .leaflet-control { cursor: auto; } -.leaflet-dragging, -.leaflet-dragging .leaflet-clickable, -.leaflet-dragging .leaflet-container { +.leaflet-dragging .leaflet-container, +.leaflet-dragging .leaflet-clickable { cursor: move; cursor: -webkit-grabbing; cursor: -moz-grabbing; @@ -182,9 +191,8 @@ outline: 2px solid orange; } .leaflet-zoom-box { - border: 2px dotted #05f; - background: white; - opacity: 0.5; + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); } @@ -197,11 +205,11 @@ /* general toolbar styles */ .leaflet-bar { - box-shadow: 0 1px 7px rgba(0,0,0,0.65); - -webkit-border-radius: 4px; - border-radius: 4px; + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; } -.leaflet-bar a, .leaflet-bar a:hover { +.leaflet-bar a, +.leaflet-bar a:hover { background-color: #fff; border-bottom: 1px solid #ccc; width: 26px; @@ -222,16 +230,12 @@ background-color: #f4f4f4; } .leaflet-bar a:first-child { - -webkit-border-top-left-radius: 4px; - border-top-left-radius: 4px; - -webkit-border-top-right-radius: 4px; - border-top-right-radius: 4px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; } .leaflet-bar a:last-child { - -webkit-border-bottom-left-radius: 4px; - border-bottom-left-radius: 4px; - -webkit-border-bottom-right-radius: 4px; - border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; border-bottom: none; } .leaflet-bar a.leaflet-disabled { @@ -240,55 +244,38 @@ color: #bbb; } -.leaflet-touch .leaflet-bar { - -webkit-border-radius: 10px; - border-radius: 10px; - } .leaflet-touch .leaflet-bar a { width: 30px; height: 30px; - } -.leaflet-touch .leaflet-bar a:first-child { - -webkit-border-top-left-radius: 7px; - border-top-left-radius: 7px; - -webkit-border-top-right-radius: 7px; - border-top-right-radius: 7px; - } -.leaflet-touch .leaflet-bar a:last-child { - -webkit-border-bottom-left-radius: 7px; - border-bottom-left-radius: 7px; - -webkit-border-bottom-right-radius: 7px; - border-bottom-right-radius: 7px; - border-bottom: none; + line-height: 30px; } /* zoom control */ -.leaflet-control-zoom-in { +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; } .leaflet-control-zoom-out { - font: bold 22px 'Lucida Console', Monaco, monospace; + font-size: 20px; } .leaflet-touch .leaflet-control-zoom-in { font-size: 22px; - line-height: 30px; } .leaflet-touch .leaflet-control-zoom-out { - font-size: 28px; - line-height: 30px; + font-size: 24px; } /* layers control */ .leaflet-control-layers { - box-shadow: 0 1px 7px rgba(0,0,0,0.4); - background: #f8f8f9; - -webkit-border-radius: 5px; - border-radius: 5px; + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; } .leaflet-control-layers-toggle { background-image: url(images/layers.png); @@ -334,8 +321,8 @@ /* attribution and scale controls */ .leaflet-container .leaflet-control-attribution { - background-color: rgba(255, 255, 255, 0.7); - box-shadow: 0 0 5px #bbb; + background: #fff; + background: rgba(255, 255, 255, 0.7); margin: 0; } .leaflet-control-attribution, @@ -343,6 +330,12 @@ padding: 0 5px; color: #333; } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } .leaflet-container .leaflet-control-attribution, .leaflet-container .leaflet-control-scale { font-size: 11px; @@ -356,23 +349,21 @@ .leaflet-control-scale-line { border: 2px solid #777; border-top: none; - color: black; 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; -moz-box-sizing: content-box; box-sizing: content-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); } .leaflet-control-scale-line:not(:first-child) { border-top: 2px solid #777; 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; @@ -385,7 +376,8 @@ } .leaflet-touch .leaflet-control-layers, .leaflet-touch .leaflet-bar { - border: 4px solid rgba(0,0,0,0.3); + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; } @@ -398,8 +390,7 @@ .leaflet-popup-content-wrapper { padding: 1px; text-align: left; - -webkit-border-radius: 12px; - border-radius: 12px; + border-radius: 12px; } .leaflet-popup-content { margin: 13px 19px; @@ -428,7 +419,8 @@ -o-transform: rotate(45deg); transform: rotate(45deg); } -.leaflet-popup-content-wrapper, .leaflet-popup-tip { +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { background: white; box-shadow: 0 3px 14px rgba(0,0,0,0.4); @@ -456,6 +448,27 @@ border-top: 1px solid #ddd; } +.leaflet-oldie .leaflet-popup-content-wrapper { + zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + /* div icon */ @@ -463,7 +476,3 @@ background: #fff; border: 1px solid #666; } -.leaflet-editing-icon { - -webkit-border-radius: 2px; - border-radius: 2px; - } diff --git a/vendor/assets/leaflet/leaflet.ie.css b/vendor/assets/leaflet/leaflet.ie.css deleted file mode 100644 index 14b84b691..000000000 --- a/vendor/assets/leaflet/leaflet.ie.css +++ /dev/null @@ -1,51 +0,0 @@ -.leaflet-vml-shape { - width: 1px; - height: 1px; - } -.lvml { - behavior: url(#default#VML); - display: inline-block; - position: absolute; - } - -.leaflet-control { - display: inline; - } - -.leaflet-popup-tip { - width: 21px; - _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)"; - } -.leaflet-popup-tip-container { - margin-top: -1px; - } -.leaflet-popup-content-wrapper, .leaflet-popup-tip { - border: 1px solid #999; - } -.leaflet-popup-content-wrapper { - zoom: 1; - } - -.leaflet-control-zoom, -.leaflet-control-layers { - border: 3px solid #999; - } -.leaflet-control-layers-toggle { - } -.leaflet-control-attribution, -.leaflet-control-layers, -.leaflet-control-scale-line { - background: white; - } -.leaflet-zoom-box { - filter: alpha(opacity=50); - } -.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 b972941d9..fa26f6e2c 100644 --- a/vendor/assets/leaflet/leaflet.js +++ b/vendor/assets/leaflet/leaflet.js @@ -7,7 +7,7 @@ var oldL = window.L, L = {}; -L.version = '0.6.3'; +L.version = '0.7'; // define Leaflet for Node module pattern loaders, including Browserify if (typeof module === 'object' && typeof module.exports === 'object') { @@ -135,19 +135,23 @@ L.Util = { return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&'); }, - template: function (str, data) { - return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { - var value = data[key]; - if (value === undefined) { - throw new Error('No value provided for variable ' + str); - } else if (typeof value === 'function') { - value = value(data); - } - return value; + compileTemplate: function (str, data) { + // based on https://gist.github.com/padolsey/6008842 + str = str.replace(/"/g, '\\\"'); + str = str.replace(/\{ *([\w_]+) *\}/g, function (str, key) { + return '" + o["' + key + '"]' + (typeof data[key] === 'function' ? '(o)' : '') + ' + "'; }); + // jshint evil: true + return new Function('o', 'return "' + str + '";'); + }, + + template: function (str, data) { + var cache = L.Util._templateCache = L.Util._templateCache || {}; + cache[str] = cache[str] || L.Util.compileTemplate(str, data); + return cache[str](data); }, - isArray: function (obj) { + isArray: Array.isArray || function (obj) { return (Object.prototype.toString.call(obj) === '[object Array]'); }, @@ -337,7 +341,7 @@ L.Mixin.Events = { if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; } var events = this[eventsKey] = this[eventsKey] || {}, - contextId = context && L.stamp(context), + contextId = context && context !== this && L.stamp(context), i, len, event, type, indexKey, indexLenKey, typeIndex; // types can be a string of space-separated words @@ -350,7 +354,7 @@ L.Mixin.Events = { }; type = types[i]; - if (context) { + if (contextId) { // store listeners of a particular context in a separate hash (if it has an id) // gives a major performance boost when removing thousands of map layers @@ -397,7 +401,7 @@ L.Mixin.Events = { if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; } var events = this[eventsKey], - contextId = context && L.stamp(context), + contextId = context && context !== this && L.stamp(context), i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed; types = L.Util.splitWords(types); @@ -413,9 +417,10 @@ L.Mixin.Events = { // clear all listeners for a type if function isn't specified delete events[type]; delete events[indexKey]; + delete events[indexLenKey]; } else { - listeners = context && typeIndex ? typeIndex[contextId] : events[type]; + listeners = contextId && typeIndex ? typeIndex[contextId] : events[type]; if (listeners) { for (j = listeners.length - 1; j >= 0; j--) { @@ -458,7 +463,7 @@ L.Mixin.Events = { listeners = events[type].slice(); for (i = 0, len = listeners.length; i < len; i++) { - listeners[i].action.call(listeners[i].context || this, event); + listeners[i].action.call(listeners[i].context, event); } } @@ -470,7 +475,7 @@ L.Mixin.Events = { if (listeners) { for (i = 0, len = listeners.length; i < len; i++) { - listeners[i].action.call(listeners[i].context || this, event); + listeners[i].action.call(listeners[i].context, event); } } } @@ -506,9 +511,7 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent; (function () { - var ie = !!window.ActiveXObject, - ie6 = ie && !window.XMLHttpRequest, - ie7 = ie && !document.querySelector, + var ie = 'ActiveXObject' in window, ielt9 = ie && !document.addEventListener, // terrible browser detection to work around Safari / iOS / Android browser bugs @@ -518,10 +521,13 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent; phantomjs = ua.indexOf('phantom') !== -1, android = ua.indexOf('android') !== -1, android23 = ua.search('android [23]') !== -1, + gecko = ua.indexOf('gecko') !== -1, mobile = typeof orientation !== undefined + '', - msTouch = window.navigator && window.navigator.msPointerEnabled && - window.navigator.msMaxTouchPoints, + msPointer = window.navigator && window.navigator.msPointerEnabled && + window.navigator.msMaxTouchPoints && !window.PointerEvent, + pointer = (window.PointerEvent && window.navigator.pointerEnabled && window.navigator.maxTouchPoints) || + msPointer, retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) || ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') && window.matchMedia('(min-resolution:144dpi)').matches), @@ -541,8 +547,8 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent; var startName = 'ontouchstart'; - // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.MsTouch) or WebKit, etc. - if (msTouch || (startName in doc)) { + // IE10+ (We simulate these into touch* events in L.DomEvent and L.DomEvent.Pointer) or WebKit, etc. + if (pointer || (startName in doc)) { return true; } @@ -568,10 +574,9 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent; L.Browser = { ie: ie, - ie6: ie6, - ie7: ie7, ielt9: ielt9, webkit: webkit, + gecko: gecko && !webkit && !window.opera && !ie, android: android, android23: android23, @@ -590,7 +595,8 @@ L.Mixin.Events.fire = L.Mixin.Events.fireEvent; mobileOpera: mobile && window.opera, touch: touch, - msTouch: msTouch, + msPointer: msPointer, + pointer: pointer, retina: retina }; @@ -881,8 +887,7 @@ L.DomUtil = { el = element, docBody = document.body, docEl = document.documentElement, - pos, - ie7 = L.Browser.ie7; + pos; do { top += el.offsetTop || 0; @@ -929,19 +934,6 @@ L.DomUtil = { top -= el.scrollTop || 0; left -= el.scrollLeft || 0; - // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else - // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js - if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) { - left += el.scrollWidth - el.clientWidth; - - // ie7 shows the scrollbar by default and provides clientWidth counting it, so we - // need to add it back in if it is visible; scrollbar is on the left as we are RTL - if (ie7 && L.DomUtil.getStyle(el, 'overflow-y') !== 'hidden' && - L.DomUtil.getStyle(el, 'overflow') !== 'hidden') { - left += 17; - } - } - el = el.parentNode; } while (el); @@ -969,18 +961,44 @@ L.DomUtil = { }, hasClass: function (el, name) { - return (el.className.length > 0) && - new RegExp('(^|\\s)' + name + '(\\s|$)').test(el.className); + if (el.classList !== undefined) { + return el.classList.contains(name); + } + var className = L.DomUtil._getClass(el); + return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className); }, addClass: function (el, name) { - if (!L.DomUtil.hasClass(el, name)) { - el.className += (el.className ? ' ' : '') + name; + if (el.classList !== undefined) { + var classes = L.Util.splitWords(name); + for (var i = 0, len = classes.length; i < len; i++) { + el.classList.add(classes[i]); + } + } else if (!L.DomUtil.hasClass(el, name)) { + var className = L.DomUtil._getClass(el); + L.DomUtil._setClass(el, (className ? className + ' ' : '') + name); } }, removeClass: function (el, name) { - el.className = L.Util.trim((' ' + el.className + ' ').replace(' ' + name + ' ', ' ')); + if (el.classList !== undefined) { + el.classList.remove(name); + } else { + L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' '))); + } + }, + + _setClass: function (el, name) { + if (el.className.baseVal === undefined) { + el.className = name; + } else { + // in case of SVG element + el.className.baseVal = name; + } + }, + + _getClass: function (el) { + return el.className.baseVal === undefined ? el.className : el.className.baseVal; }, setOpacity: function (el, value) { @@ -1089,27 +1107,39 @@ L.DomUtil.TRANSITION_END = L.DomUtil.TRANSITION + 'End' : 'transitionend'; (function () { - var userSelectProperty = L.DomUtil.testProp( - ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + if ('onselectstart' in document) { + L.extend(L.DomUtil, { + disableTextSelection: function () { + L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); + }, + + enableTextSelection: function () { + L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); + } + }); + } else { + var userSelectProperty = L.DomUtil.testProp( + ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']); + + L.extend(L.DomUtil, { + disableTextSelection: function () { + if (userSelectProperty) { + var style = document.documentElement.style; + this._userSelect = style[userSelectProperty]; + style[userSelectProperty] = 'none'; + } + }, + + enableTextSelection: function () { + if (userSelectProperty) { + document.documentElement.style[userSelectProperty] = this._userSelect; + delete this._userSelect; + } + } + }); + } L.extend(L.DomUtil, { - disableTextSelection: function () { - L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault); - if (userSelectProperty) { - var style = document.documentElement.style; - this._userSelect = style[userSelectProperty]; - style[userSelectProperty] = 'none'; - } - }, - - enableTextSelection: function () { - L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault); - if (userSelectProperty) { - document.documentElement.style[userSelectProperty] = this._userSelect; - delete this._userSelect; - } - }, - disableImageDrag: function () { L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault); }, @@ -1125,16 +1155,20 @@ L.DomUtil.TRANSITION_END = * L.LatLng represents a geographical point with latitude and longitude coordinates. */ -L.LatLng = function (rawLat, rawLng) { // (Number, Number) - var lat = parseFloat(rawLat), - lng = parseFloat(rawLng); +L.LatLng = function (lat, lng, alt) { // (Number, Number, Number) + lat = parseFloat(lat); + lng = parseFloat(lng); if (isNaN(lat) || isNaN(lng)) { - throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')'); + throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')'); } this.lat = lat; this.lng = lng; + + if (alt !== undefined) { + this.alt = parseFloat(alt); + } }; L.extend(L.LatLng, { @@ -1198,7 +1232,11 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe return a; } if (L.Util.isArray(a)) { - return new L.LatLng(a[0], a[1]); + if (typeof a[0] === 'number' || typeof a[0] === 'string') { + return new L.LatLng(a[0], a[1], a[2]); + } else { + return null; + } } if (a === undefined || a === null) { return a; @@ -1206,6 +1244,9 @@ L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Numbe if (typeof a === 'object' && 'lat' in a) { return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon); } + if (b === undefined) { + return null; + } return new L.LatLng(a, b); }; @@ -1230,8 +1271,9 @@ L.LatLngBounds.prototype = { extend: function (obj) { // (LatLng) or (LatLngBounds) if (!obj) { return this; } - if (typeof obj[0] === 'number' || typeof obj[0] === 'string' || obj instanceof L.LatLng) { - obj = L.latLng(obj); + var latLng = L.latLng(obj); + if (latLng !== null) { + obj = latLng; } else { obj = L.latLngBounds(obj); } @@ -1444,6 +1486,11 @@ L.CRS = { scale: function (zoom) { return 256 * Math.pow(2, zoom); + }, + + getSize: function (zoom) { + var s = this.scale(zoom); + return L.point(s, s); } }; @@ -1522,8 +1569,13 @@ L.Map = L.Class.extend({ initialize: function (id, options) { // (HTMLElement or String, Object) options = L.setOptions(this, options); + this._initContainer(id); this._initLayout(); + + // hack for https://github.com/Leaflet/Leaflet/issues/1980 + this._onResize = L.bind(this._onResize, this); + this._initEvents(); if (options.maxBounds) { @@ -1550,11 +1602,16 @@ L.Map = L.Class.extend({ // replaced by animation-powered implementation in Map.PanAnimation.js setView: function (center, zoom) { + zoom = zoom === undefined ? this.getZoom() : zoom; this._resetView(L.latLng(center), this._limitZoom(zoom)); return this; }, setZoom: function (zoom, options) { + if (!this._loaded) { + this._zoom = this._limitZoom(zoom); + return this; + } return this.setView(this.getCenter(), zoom, {zoom: options}); }, @@ -1592,6 +1649,8 @@ L.Map = L.Class.extend({ nePoint = this.project(bounds.getNorthEast(), zoom), center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom); + zoom = options && options.maxZoom ? Math.min(options.maxZoom, zoom) : zoom; + return this.setView(center, zoom, options); }, @@ -1604,7 +1663,7 @@ L.Map = L.Class.extend({ }, panBy: function (offset) { // (Point) - // replaced with animated panBy in Map.Animation.js + // replaced with animated panBy in Map.PanAnimation.js this.fire('movestart'); this._rawPanBy(L.point(offset)); @@ -1613,63 +1672,29 @@ L.Map = L.Class.extend({ return this.fire('moveend'); }, - setMaxBounds: function (bounds, options) { + setMaxBounds: function (bounds) { bounds = L.latLngBounds(bounds); this.options.maxBounds = bounds; if (!bounds) { - this._boundsMinZoom = null; - this.off('moveend', this._panInsideMaxBounds, this); - return this; + return this.off('moveend', this._panInsideMaxBounds, this); } - var minZoom = this.getBoundsZoom(bounds, true); - - this._boundsMinZoom = minZoom; - if (this._loaded) { - if (this._zoom < minZoom) { - this.setView(bounds.getCenter(), minZoom, options); - } else { - this.panInsideBounds(bounds); - } + this._panInsideMaxBounds(); } - this.on('moveend', this._panInsideMaxBounds, this); - - return this; + return this.on('moveend', this._panInsideMaxBounds, this); }, - panInsideBounds: function (bounds) { - bounds = L.latLngBounds(bounds); + panInsideBounds: function (bounds, options) { + var center = this.getCenter(), + newCenter = this._limitCenter(center, this._zoom, bounds); - var viewBounds = this.getPixelBounds(), - viewSw = viewBounds.getBottomLeft(), - viewNe = viewBounds.getTopRight(), - sw = this.project(bounds.getSouthWest()), - ne = this.project(bounds.getNorthEast()), - dx = 0, - dy = 0; + if (center.equals(newCenter)) { return this; } - if (viewNe.y < ne.y) { // north - dy = Math.ceil(ne.y - viewNe.y); - } - if (viewNe.x > ne.x) { // east - dx = Math.floor(ne.x - viewNe.x); - } - if (viewSw.y > sw.y) { // south - dy = Math.floor(sw.y - viewSw.y); - } - if (viewSw.x < sw.x) { // west - dx = Math.ceil(sw.x - viewSw.x); - } - - if (dx || dy) { - return this.panBy([dx, dy]); - } - - return this; + return this.panTo(newCenter, options); }, addLayer: function (layer) { @@ -1704,7 +1729,7 @@ L.Map = L.Class.extend({ removeLayer: function (layer) { var id = L.stamp(layer); - if (!this._layers[id]) { return; } + if (!this._layers[id]) { return this; } if (this._loaded) { layer.onRemove(this); @@ -1754,14 +1779,12 @@ L.Map = L.Class.extend({ this._sizeChanged = true; this._initialCenter = null; - if (this.options.maxBounds) { - this.setMaxBounds(this.options.maxBounds); - } - if (!this._loaded) { return this; } var newSize = this.getSize(), - offset = oldSize.subtract(newSize).divideBy(2).round(); + oldCenter = oldSize.divideBy(2).round(), + newCenter = newSize.divideBy(2).round(), + offset = oldCenter.subtract(newCenter); if (!offset.x && !offset.y) { return this; } @@ -1775,9 +1798,12 @@ L.Map = L.Class.extend({ this.fire('move'); - // make sure moveend is not fired too often on resize - clearTimeout(this._sizeTimer); - this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); + if (options.debounceMoveend) { + clearTimeout(this._sizeTimer); + this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200); + } else { + this.fire('moveend'); + } } return this.fire('resize', { @@ -1788,7 +1814,7 @@ L.Map = L.Class.extend({ // TODO handler.addTo addHandler: function (name, HandlerClass) { - if (!HandlerClass) { return; } + if (!HandlerClass) { return this; } var handler = this[name] = new HandlerClass(this); @@ -1808,7 +1834,12 @@ L.Map = L.Class.extend({ this._initEvents('off'); - delete this._container._leaflet; + try { + // throws error in IE6-8 + delete this._container._leaflet; + } catch (e) { + this._container._leaflet = undefined; + } this._clearPanes(); if (this._clearControlPos) { @@ -1845,9 +1876,9 @@ L.Map = L.Class.extend({ }, getMinZoom: function () { - var z1 = this._layersMinZoom === undefined ? -Infinity : this._layersMinZoom, - z2 = this._boundsMinZoom === undefined ? -Infinity : this._boundsMinZoom; - return this.options.minZoom === undefined ? Math.max(z1, z2) : this.options.minZoom; + return this.options.minZoom === undefined ? + (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) : + this.options.minZoom; }, getMaxZoom: function () { @@ -1999,6 +2030,7 @@ L.Map = L.Class.extend({ L.DomUtil.addClass(container, 'leaflet-container' + (L.Browser.touch ? ' leaflet-touch' : '') + (L.Browser.retina ? ' leaflet-retina' : '') + + (L.Browser.ielt9 ? ' leaflet-oldie' : '') + (this.options.fadeAnimation ? ' leaflet-fade-anim' : '')); var position = L.DomUtil.getStyle(container, 'position'); @@ -2169,12 +2201,14 @@ L.Map = L.Class.extend({ _onResize: function () { L.Util.cancelAnimFrame(this._resizeRequest); this._resizeRequest = L.Util.requestAnimFrame( - this.invalidateSize, this, false, this._container); + function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container); }, _onMouseClick: function (e) { - if (!this._loaded || (!e._simulated && this.dragging && this.dragging.moved()) || - L.DomEvent._skipped(e)) { return; } + if (!this._loaded || (!e._simulated && + ((this.dragging && this.dragging.moved()) || + (this.boxZoom && this.boxZoom.moved()))) || + L.DomEvent._skipped(e)) { return; } this.fire('preclick'); this._fireMouseEvent(e); @@ -2269,6 +2303,46 @@ L.Map = L.Class.extend({ return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint()); }, + // adjust center for view to get inside bounds + _limitCenter: function (center, zoom, bounds) { + + if (!bounds) { return center; } + + var centerPoint = this.project(center, zoom), + viewHalf = this.getSize().divideBy(2), + viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)), + offset = this._getBoundsOffset(viewBounds, bounds, zoom); + + return this.unproject(centerPoint.add(offset), zoom); + }, + + // adjust offset for view to get inside bounds + _limitOffset: function (offset, bounds) { + if (!bounds) { return offset; } + + var viewBounds = this.getPixelBounds(), + newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset)); + + return offset.add(this._getBoundsOffset(newBounds, bounds)); + }, + + // returns offset needed for pxBounds to get inside maxBounds at a specified zoom + _getBoundsOffset: function (pxBounds, maxBounds, zoom) { + var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min), + seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max), + + dx = this._rebound(nwOffset.x, -seOffset.x), + dy = this._rebound(nwOffset.y, -seOffset.y); + + return new L.Point(dx, dy); + }, + + _rebound: function (left, right) { + return left + right > 0 ? + Math.round(left - right) / 2 : + Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right)); + }, + _limitZoom: function (zoom) { var min = this.getMinZoom(), max = this.getMaxZoom(); @@ -2349,9 +2423,9 @@ L.CRS.EPSG3395 = L.extend({}, L.CRS, { transformation: (function () { var m = L.Projection.Mercator, r = m.R_MAJOR, - r2 = m.R_MINOR; + scale = 0.5 / (Math.PI * r); - return new L.Transformation(0.5 / (Math.PI * r), 0.5, -0.5 / (Math.PI * r2), 0.5); + return new L.Transformation(scale, 0.5, -scale, 0.5); }()) }); @@ -2372,7 +2446,8 @@ L.TileLayer = L.Class.extend({ attribution: '', zoomOffset: 0, opacity: 1, - /* (undefined works too) + /* + maxNativeZoom: null, zIndex: null, tms: false, continuousWorld: false, @@ -2421,9 +2496,6 @@ L.TileLayer = L.Class.extend({ // create a container div for tiles this._initContainer(); - // create an image to clone for tiles - this._createTileProto(); - // set up events map.on({ 'viewreset': this._reset, @@ -2588,7 +2660,7 @@ L.TileLayer = L.Class.extend({ this._updateZIndex(); if (this._animated) { - var className = 'leaflet-tile-container leaflet-zoom-animated'; + var className = 'leaflet-tile-container'; this._bgBuffer = L.DomUtil.create('div', className, this._container); this._tileContainer = L.DomUtil.create('div', className, this._container); @@ -2626,13 +2698,27 @@ L.TileLayer = L.Class.extend({ this._initContainer(); }, + _getTileSize: function () { + var map = this._map, + zoom = map.getZoom() + this.options.zoomOffset, + zoomN = this.options.maxNativeZoom, + tileSize = this.options.tileSize; + + if (zoomN && zoom > zoomN) { + tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize); + } + + return tileSize; + }, + _update: function () { if (!this._map) { return; } - var bounds = this._map.getPixelBounds(), - zoom = this._map.getZoom(), - tileSize = this.options.tileSize; + var map = this._map, + bounds = map.getPixelBounds(), + zoom = map.getZoom(), + tileSize = this._getTileSize(); if (zoom > this.options.maxZoom || zoom < this.options.minZoom) { return; @@ -2701,8 +2787,8 @@ L.TileLayer = L.Class.extend({ var limit = this._getWrapTileNum(); // don't load if exceeds world bounds - if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit)) || - tilePoint.y < 0 || tilePoint.y >= limit) { return false; } + if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) || + tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; } } if (options.bounds) { @@ -2795,12 +2881,14 @@ L.TileLayer = L.Class.extend({ zoom = options.maxZoom - zoom; } - return zoom + options.zoomOffset; + zoom += options.zoomOffset; + + return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom; }, _getTilePos: function (tilePoint) { var origin = this._map.getPixelOrigin(), - tileSize = this.options.tileSize; + tileSize = this._getTileSize(); return tilePoint.multiplyBy(tileSize).subtract(origin); }, @@ -2817,8 +2905,9 @@ L.TileLayer = L.Class.extend({ }, _getWrapTileNum: function () { - // TODO refactor, limit is not valid for non-standard projections - return Math.pow(2, this._getZoomForUrl()); + var crs = this._map.options.crs, + size = crs.getSize(this._map.getZoom()); + return size.divideBy(this.options.tileSize); }, _adjustTilePoint: function (tilePoint) { @@ -2827,11 +2916,11 @@ L.TileLayer = L.Class.extend({ // wrap tile coordinates if (!this.options.continuousWorld && !this.options.noWrap) { - tilePoint.x = ((tilePoint.x % limit) + limit) % limit; + tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x; } if (this.options.tms) { - tilePoint.y = limit - tilePoint.y - 1; + tilePoint.y = limit.y - tilePoint.y - 1; } tilePoint.z = this._getZoomForUrl(); @@ -2842,12 +2931,6 @@ L.TileLayer = L.Class.extend({ return this.options.subdomains[index]; }, - _createTileProto: function () { - var img = this._tileImg = L.DomUtil.create('img', 'leaflet-tile'); - img.style.width = img.style.height = this.options.tileSize + 'px'; - img.galleryimg = 'no'; - }, - _getTile: function () { if (this.options.reuseTiles && this._unusedTiles.length > 0) { var tile = this._unusedTiles.pop(); @@ -2861,12 +2944,20 @@ L.TileLayer = L.Class.extend({ _resetTile: function (/*tile*/) {}, _createTile: function () { - var tile = this._tileImg.cloneNode(false); + var tile = L.DomUtil.create('img', 'leaflet-tile'); + tile.style.width = tile.style.height = this._getTileSize() + 'px'; + tile.galleryimg = 'no'; + tile.onselectstart = tile.onmousemove = L.Util.falseFn; if (L.Browser.ielt9 && this.options.opacity !== undefined) { L.DomUtil.setOpacity(tile, this.options.opacity); } + // without this hack, tiles disappear after zoom on Chrome for Android + // https://github.com/Leaflet/Leaflet/issues/2078 + if (L.Browser.mobileWebkit3d) { + tile.style.WebkitBackfaceVisibility = 'hidden'; + } return tile; }, @@ -2877,10 +2968,20 @@ L.TileLayer = L.Class.extend({ this._adjustTilePoint(tilePoint); tile.src = this.getTileUrl(tilePoint); + + this.fire('tileloadstart', { + tile: tile, + url: tile.src + }); }, _tileLoaded: function () { this._tilesToLoad--; + + if (this._animated) { + L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated'); + } + if (!this._tilesToLoad) { this.fire('load'); @@ -2975,13 +3076,15 @@ L.TileLayer.WMS = L.TileLayer.extend({ this._crs = this.options.crs || map.options.crs; - var projectionKey = parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs'; + this._wmsVersion = parseFloat(this.wmsParams.version); + + var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; this.wmsParams[projectionKey] = this._crs.code; L.TileLayer.prototype.onAdd.call(this, map); }, - getTileUrl: function (tilePoint, zoom) { // (Point, Number) -> String + getTileUrl: function (tilePoint) { // (Point, Number) -> String var map = this._map, tileSize = this.options.tileSize, @@ -2989,10 +3092,11 @@ L.TileLayer.WMS = L.TileLayer.extend({ nwPoint = tilePoint.multiplyBy(tileSize), sePoint = nwPoint.add([tileSize, tileSize]), - nw = this._crs.project(map.unproject(nwPoint, zoom)), - se = this._crs.project(map.unproject(sePoint, zoom)), - - bbox = [nw.x, se.y, se.x, nw.y].join(','), + nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)), + se = this._crs.project(map.unproject(sePoint, tilePoint.z)), + bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ? + [se.y, nw.x, nw.y, se.x].join(',') : + [nw.x, se.y, se.x, nw.y].join(','), url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)}); @@ -3035,7 +3139,7 @@ L.TileLayer.Canvas = L.TileLayer.extend({ this._reset({hard: true}); this._update(); } - + for (var i in this._tiles) { this._redrawTile(this._tiles[i]); } @@ -3046,13 +3150,9 @@ L.TileLayer.Canvas = L.TileLayer.extend({ this.drawTile(tile, tile._tilePoint, this._map._zoom); }, - _createTileProto: function () { - var proto = this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile'); - proto.width = proto.height = this.options.tileSize; - }, - _createTile: function () { - var tile = this._canvasProto.cloneNode(false); + var tile = L.DomUtil.create('canvas', 'leaflet-tile'); + tile.width = tile.height = this.options.tileSize; tile.onselectstart = tile.onmousemove = L.Util.falseFn; return tile; }, @@ -3156,6 +3256,15 @@ L.ImageOverlay = L.Class.extend({ return this; }, + setUrl: function (url) { + this._url = url; + this._image.src = this._url; + }, + + getAttribution: function () { + return this.options.attribution; + }, + _initImage: function () { this._image = L.DomUtil.create('img', 'leaflet-image-layer'); @@ -3299,19 +3408,8 @@ L.Icon = L.Class.extend({ }, _createImg: function (src, el) { - - if (!L.Browser.ie6) { - if (!el) { - el = document.createElement('img'); - } - el.src = src; - } else { - if (!el) { - el = document.createElement('div'); - } - el.style.filter = - 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")'; - } + el = el || document.createElement('img'); + el.src = src; return el; }, @@ -3392,6 +3490,7 @@ L.Marker = L.Class.extend({ options: { icon: new L.Icon.Default(), title: '', + alt: '', clickable: true, draggable: false, keyboard: true, @@ -3413,6 +3512,7 @@ L.Marker = L.Class.extend({ this._initIcon(); this.update(); + this.fire('add'); if (map.options.zoomAnimation && map.options.markerZoomAnimation) { map.on('zoomanim', this._animateZoom, this); @@ -3470,6 +3570,10 @@ L.Marker = L.Class.extend({ this.update(); } + if (this._popup) { + this.bindPopup(this._popup); + } + return this; }, @@ -3501,6 +3605,10 @@ L.Marker = L.Class.extend({ if (options.title) { icon.title = options.title; } + + if (options.alt) { + icon.alt = options.alt; + } } L.DomUtil.addClass(icon, classToAdd); @@ -3585,7 +3693,7 @@ L.Marker = L.Class.extend({ }, _animateZoom: function (opt) { - var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center); + var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round(); this._setPos(pos); }, @@ -3666,7 +3774,7 @@ L.Marker = L.Class.extend({ if (this._map) { this._updateOpacity(); } - + return this; }, @@ -3752,11 +3860,13 @@ L.Popup = L.Class.extend({ options: { minWidth: 50, maxWidth: 300, - maxHeight: null, + // maxHeight: null, autoPan: true, closeButton: true, offset: [0, 7], autoPanPadding: [5, 5], + // autoPanPaddingTopLeft: null, + // autoPanPaddingBottomRight: null, keepInView: false, className: '', zoomAnimation: true @@ -3776,7 +3886,6 @@ L.Popup = L.Class.extend({ if (!this._container) { this._initLayout(); } - this._updateContent(); var animFade = map.options.fadeAnimation; @@ -3787,7 +3896,7 @@ L.Popup = L.Class.extend({ map.on(this._getEvents(), this); - this._update(); + this.update(); if (animFade) { L.DomUtil.setOpacity(this._container, 1); @@ -3834,18 +3943,43 @@ L.Popup = L.Class.extend({ } }, + getLatLng: function () { + return this._latlng; + }, + setLatLng: function (latlng) { this._latlng = L.latLng(latlng); - this._update(); + if (this._map) { + this._updatePosition(); + this._adjustPan(); + } return this; }, + getContent: function () { + return this._content; + }, + setContent: function (content) { this._content = content; - this._update(); + this.update(); return this; }, + update: function () { + if (!this._map) { return; } + + this._container.style.visibility = 'hidden'; + + this._updateContent(); + this._updateLayout(); + this._updatePosition(); + + this._container.style.visibility = ''; + + this._adjustPan(); + }, + _getEvents: function () { var events = { viewreset: this._updatePosition @@ -3892,27 +4026,14 @@ L.Popup = L.Class.extend({ L.DomEvent.disableClickPropagation(wrapper); this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper); - L.DomEvent.on(this._contentNode, 'mousewheel', L.DomEvent.stopPropagation); - L.DomEvent.on(this._contentNode, 'MozMousePixelScroll', L.DomEvent.stopPropagation); + + L.DomEvent.disableScrollPropagation(this._contentNode); L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation); + this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container); this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer); }, - _update: function () { - if (!this._map) { return; } - - this._container.style.visibility = 'hidden'; - - this._updateContent(); - this._updateLayout(); - this._updatePosition(); - - this._container.style.visibility = ''; - - this._adjustPan(); - }, - _updateContent: function () { if (!this._content) { return; } @@ -3997,21 +4118,23 @@ L.Popup = L.Class.extend({ var containerPos = map.layerPointToContainerPoint(layerPos), padding = L.point(this.options.autoPanPadding), + paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding), + paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding), size = map.getSize(), dx = 0, dy = 0; - if (containerPos.x + containerWidth > size.x) { // right - dx = containerPos.x + containerWidth - size.x + padding.x; + if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right + dx = containerPos.x + containerWidth - size.x + paddingBR.x; } - if (containerPos.x - dx < 0) { // left - dx = containerPos.x - padding.x; + if (containerPos.x - dx - paddingTL.x < 0) { // left + dx = containerPos.x - paddingTL.x; } - if (containerPos.y + containerHeight > size.y) { // bottom - dy = containerPos.y + containerHeight - size.y + padding.y; + if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom + dy = containerPos.y + containerHeight - size.y + paddingBR.y; } - if (containerPos.y - dy < 0) { // top - dy = containerPos.y - padding.y; + if (containerPos.y - dy - paddingTL.y < 0) { // top + dy = containerPos.y - paddingTL.y; } if (dx || dy) { @@ -4106,11 +4229,12 @@ L.Marker.include({ options = L.extend({offset: anchor}, options); - if (!this._popup) { + if (!this._popupHandlersAdded) { this .on('click', this.togglePopup, this) .on('remove', this.closePopup, this) .on('move', this._movePopup, this); + this._popupHandlersAdded = true; } if (content instanceof L.Popup) { @@ -4135,13 +4259,18 @@ L.Marker.include({ if (this._popup) { this._popup = null; this - .off('click', this.togglePopup) - .off('remove', this.closePopup) - .off('move', this._movePopup); + .off('click', this.togglePopup, this) + .off('remove', this.closePopup, this) + .off('move', this._movePopup, this); + this._popupHandlersAdded = false; } return this; }, + getPopup: function () { + return this._popup; + }, + _movePopup: function (e) { this._popup.setLatLng(e.latlng); } @@ -4282,7 +4411,9 @@ L.FeatureGroup = L.LayerGroup.extend({ return this; } - layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); + if ('on' in layer) { + layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this); + } L.LayerGroup.prototype.addLayer.call(this, layer); @@ -4318,6 +4449,15 @@ L.FeatureGroup = L.LayerGroup.extend({ return this.invoke('bindPopup', content, options); }, + openPopup: function (latlng) { + // open popup on the first layer + for (var id in this._layers) { + this._layers[id].openPopup(latlng); + break; + } + return this; + }, + setStyle: function (style) { return this.invoke('setStyle', style); }, @@ -4341,11 +4481,10 @@ L.FeatureGroup = L.LayerGroup.extend({ }, _propagateEvent: function (e) { - if (!e.layer) { - e.layer = e.target; - } - e.target = this; - + e = L.extend({}, e, { + layer: e.target, + target: this + }); this.fire(e.type, e); } }); @@ -4377,6 +4516,8 @@ L.Path = L.Class.extend({ stroke: true, color: '#0033ff', dashArray: null, + lineCap: null, + lineJoin: null, weight: 5, opacity: 0.5, @@ -4526,6 +4667,11 @@ L.Path = L.Path.extend({ this._container = this._createElement('g'); this._path = this._createElement('path'); + + if (this.options.className) { + L.DomUtil.addClass(this._path, this.options.className); + } + this._container.appendChild(this._path); }, @@ -4556,6 +4702,12 @@ L.Path = L.Path.extend({ } else { this._path.removeAttribute('stroke-dasharray'); } + if (this.options.lineCap) { + this._path.setAttribute('stroke-linecap', this.options.lineCap); + } + if (this.options.lineJoin) { + this._path.setAttribute('stroke-linejoin', this.options.lineJoin); + } } else { this._path.setAttribute('stroke', 'none'); } @@ -4580,7 +4732,7 @@ L.Path = L.Path.extend({ _initEvents: function () { if (this.options.clickable) { if (L.Browser.svg || !L.Browser.vml) { - this._path.setAttribute('class', 'leaflet-clickable'); + L.DomUtil.addClass(this._path, 'leaflet-clickable'); } L.DomEvent.on(this._container, 'click', this._onMouseClick, this); @@ -4630,14 +4782,14 @@ L.Map.include({ this._panes.overlayPane.appendChild(this._pathRoot); if (this.options.zoomAnimation && L.Browser.any3d) { - this._pathRoot.setAttribute('class', ' leaflet-zoom-animated'); + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated'); this.on({ 'zoomanim': this._animatePathZoom, 'zoomend': this._endPathZoom }); } else { - this._pathRoot.setAttribute('class', ' leaflet-zoom-hide'); + L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide'); } this.on('moveend', this._updateSvgViewport); @@ -4804,10 +4956,14 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ _initPath: function () { var container = this._container = this._createElement('shape'); - L.DomUtil.addClass(container, 'leaflet-vml-shape'); + + L.DomUtil.addClass(container, 'leaflet-vml-shape' + + (this.options.className ? ' ' + this.options.className : '')); + if (this.options.clickable) { L.DomUtil.addClass(container, 'leaflet-clickable'); } + container.coordsize = '1 1'; this._path = this._createElement('path'); @@ -4840,12 +4996,18 @@ L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({ stroke.opacity = options.opacity; if (options.dashArray) { - stroke.dashStyle = options.dashArray instanceof Array ? + stroke.dashStyle = L.Util.isArray(options.dashArray) ? options.dashArray.join(' ') : options.dashArray.replace(/( *, *)/g, ' '); } else { stroke.dashStyle = ''; } + if (options.lineCap) { + stroke.endcap = options.lineCap.replace('butt', 'flat'); + } + if (options.lineJoin) { + stroke.joinstyle = options.lineJoin; + } } else if (stroke) { container.removeChild(stroke); @@ -5525,10 +5687,12 @@ L.Polygon = L.Polyline.extend({ }, initialize: function (latlngs, options) { - var i, len, hole; - L.Polyline.prototype.initialize.call(this, latlngs, options); + this._initWithHoles(latlngs); + }, + _initWithHoles: function (latlngs) { + var i, len, hole; if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { this._latlngs = this._convertLatLngs(latlngs[0]); this._holes = latlngs.slice(1); @@ -5569,6 +5733,15 @@ L.Polygon = L.Polyline.extend({ } }, + setLatLngs: function (latlngs) { + if (latlngs && L.Util.isArray(latlngs[0]) && (typeof latlngs[0][0] !== 'number')) { + this._initWithHoles(latlngs); + return this.redraw(); + } else { + return L.Polyline.prototype.setLatLngs.call(this, latlngs); + } + }, + _clipPoints: function () { var points = this._originalPoints, newParts = []; @@ -5810,9 +5983,20 @@ L.CircleMarker = L.Circle.extend({ this.setRadius(this.options.radius); }, + setLatLng: function (latlng) { + L.Circle.prototype.setLatLng.call(this, latlng); + if (this._popup && this._popup._isOpen) { + this._popup.setLatLng(latlng); + } + }, + setRadius: function (radius) { this.options.radius = this._radius = radius; return this.redraw(); + }, + + getRadius: function () { + return this._radius; } }); @@ -5958,7 +6142,7 @@ L.GeoJSON = L.FeatureGroup.extend({ if (options.filter && !options.filter(geojson)) { return; } - var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng); + var layer = L.GeoJSON.geometryToLayer(geojson, options.pointToLayer, options.coordsToLatLng, options); layer.feature = L.GeoJSON.asFeature(geojson); layer.defaultOptions = layer.options; @@ -5998,11 +6182,11 @@ L.GeoJSON = L.FeatureGroup.extend({ }); L.extend(L.GeoJSON, { - geometryToLayer: function (geojson, pointToLayer, coordsToLatLng) { + geometryToLayer: function (geojson, pointToLayer, coordsToLatLng, vectorOptions) { var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson, coords = geometry.coordinates, layers = [], - latlng, latlngs, i, len, layer; + latlng, latlngs, i, len; coordsToLatLng = coordsToLatLng || this.coordsToLatLng; @@ -6014,37 +6198,37 @@ L.extend(L.GeoJSON, { case 'MultiPoint': for (i = 0, len = coords.length; i < len; i++) { latlng = coordsToLatLng(coords[i]); - layer = pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng); - layers.push(layer); + layers.push(pointToLayer ? pointToLayer(geojson, latlng) : new L.Marker(latlng)); } return new L.FeatureGroup(layers); case 'LineString': latlngs = this.coordsToLatLngs(coords, 0, coordsToLatLng); - return new L.Polyline(latlngs); + return new L.Polyline(latlngs, vectorOptions); case 'Polygon': + if (coords.length === 2 && !coords[1].length) { + throw new Error('Invalid GeoJSON object.'); + } latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); - return new L.Polygon(latlngs); + return new L.Polygon(latlngs, vectorOptions); case 'MultiLineString': latlngs = this.coordsToLatLngs(coords, 1, coordsToLatLng); - return new L.MultiPolyline(latlngs); + return new L.MultiPolyline(latlngs, vectorOptions); case 'MultiPolygon': latlngs = this.coordsToLatLngs(coords, 2, coordsToLatLng); - return new L.MultiPolygon(latlngs); + return new L.MultiPolygon(latlngs, vectorOptions); case 'GeometryCollection': for (i = 0, len = geometry.geometries.length; i < len; i++) { - layer = this.geometryToLayer({ + layers.push(this.geometryToLayer({ geometry: geometry.geometries[i], type: 'Feature', properties: geojson.properties - }, pointToLayer, coordsToLatLng); - - layers.push(layer); + }, pointToLayer, coordsToLatLng, vectorOptions)); } return new L.FeatureGroup(layers); @@ -6054,7 +6238,7 @@ L.extend(L.GeoJSON, { }, coordsToLatLng: function (coords) { // (Array[, Boolean]) -> LatLng - return new L.LatLng(coords[1], coords[0]); + return new L.LatLng(coords[1], coords[0], coords[2]); }, coordsToLatLngs: function (coords, levelsDeep, coordsToLatLng) { // (Array[, Number, Function]) -> Array @@ -6072,8 +6256,13 @@ L.extend(L.GeoJSON, { return latlngs; }, - latLngToCoords: function (latLng) { - return [latLng.lng, latLng.lat]; + latLngToCoords: function (latlng) { + var coords = [latlng.lng, latlng.lat]; + + if (latlng.alt !== undefined) { + coords.push(latlng.alt); + } + return coords; }, latLngsToCoords: function (latLngs) { @@ -6148,43 +6337,58 @@ L.Polygon.include({ }); (function () { - function includeMulti(Klass, type) { - Klass.include({ - toGeoJSON: function () { - var coords = []; + function multiToGeoJSON(type) { + return function () { + var coords = []; - this.eachLayer(function (layer) { - coords.push(layer.toGeoJSON().geometry.coordinates); - }); + this.eachLayer(function (layer) { + coords.push(layer.toGeoJSON().geometry.coordinates); + }); - return L.GeoJSON.getFeature(this, { - type: type, - coordinates: coords - }); - } - }); + return L.GeoJSON.getFeature(this, { + type: type, + coordinates: coords + }); + }; } - includeMulti(L.MultiPolyline, 'MultiLineString'); - includeMulti(L.MultiPolygon, 'MultiPolygon'); -}()); + L.MultiPolyline.include({toGeoJSON: multiToGeoJSON('MultiLineString')}); + L.MultiPolygon.include({toGeoJSON: multiToGeoJSON('MultiPolygon')}); -L.LayerGroup.include({ - toGeoJSON: function () { - var features = []; + L.LayerGroup.include({ + toGeoJSON: function () { - this.eachLayer(function (layer) { - if (layer.toGeoJSON) { - features.push(L.GeoJSON.asFeature(layer.toGeoJSON())); + var geometry = this.feature && this.feature.geometry, + jsons = [], + json; + + if (geometry && geometry.type === 'MultiPoint') { + return multiToGeoJSON('MultiPoint').call(this); } - }); - return { - type: 'FeatureCollection', - features: features - }; - } -}); + var isGeometryCollection = geometry && geometry.type === 'GeometryCollection'; + + this.eachLayer(function (layer) { + if (layer.toGeoJSON) { + json = layer.toGeoJSON(); + jsons.push(isGeometryCollection ? json.geometry : L.GeoJSON.asFeature(json)); + } + }); + + if (isGeometryCollection) { + return L.GeoJSON.getFeature(this, { + geometries: jsons, + type: 'GeometryCollection' + }); + } + + return { + type: 'FeatureCollection', + features: jsons + }; + } + }); +}()); L.geoJson = function (geojson, options) { return new L.GeoJSON(geojson, options); @@ -6209,8 +6413,8 @@ L.DomEvent = { return fn.call(context || obj, e || L.DomEvent._getEvent()); }; - if (L.Browser.msTouch && type.indexOf('touch') === 0) { - return this.addMsTouchListener(obj, type, handler, id); + if (L.Browser.pointer && type.indexOf('touch') === 0) { + return this.addPointerListener(obj, type, handler, id); } if (L.Browser.touch && (type === 'dblclick') && this.addDoubleTapListener) { this.addDoubleTapListener(obj, handler, id); @@ -6262,8 +6466,8 @@ L.DomEvent = { if (!handler) { return this; } - if (L.Browser.msTouch && type.indexOf('touch') === 0) { - this.removeMsTouchListener(obj, type, id); + if (L.Browser.pointer && type.indexOf('touch') === 0) { + this.removePointerListener(obj, type, id); } else if (L.Browser.touch && (type === 'dblclick') && this.removeDoubleTapListener) { this.removeDoubleTapListener(obj, id); @@ -6294,19 +6498,29 @@ L.DomEvent = { } else { e.cancelBubble = true; } + L.DomEvent._skipped(e); + return this; }, + disableScrollPropagation: function (el) { + var stop = L.DomEvent.stopPropagation; + + return L.DomEvent + .on(el, 'mousewheel', stop) + .on(el, 'MozMousePixelScroll', stop); + }, + disableClickPropagation: function (el) { 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); + L.DomEvent.on(el, L.Draggable.START[i], stop); } return L.DomEvent - .addListener(el, 'click', L.DomEvent._fakeStop) - .addListener(el, 'dblclick', stop); + .on(el, 'click', L.DomEvent._fakeStop) + .on(el, 'dblclick', stop); }, preventDefault: function (e) { @@ -6320,34 +6534,31 @@ L.DomEvent = { }, stop: function (e) { - return L.DomEvent.preventDefault(e).stopPropagation(e); + return L.DomEvent + .preventDefault(e) + .stopPropagation(e); }, getMousePosition: function (e, container) { - - var ie7 = L.Browser.ie7, - body = document.body, + var body = document.body, docEl = document.documentElement, - x = e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft: e.clientX, + //gecko makes scrollLeft more negative as you scroll in rtl, other browsers don't + //ref: https://code.google.com/p/closure-library/source/browse/closure/goog/style/bidi.js + x = L.DomUtil.documentIsLtr() ? + (e.pageX ? e.pageX - body.scrollLeft - docEl.scrollLeft : e.clientX) : + (L.Browser.gecko ? e.pageX - body.scrollLeft - docEl.scrollLeft : + e.pageX ? e.pageX - body.scrollLeft + docEl.scrollLeft : e.clientX), y = e.pageY ? e.pageY - body.scrollTop - docEl.scrollTop: e.clientY, - pos = new L.Point(x, y), - rect = container.getBoundingClientRect(), - left = rect.left - container.clientLeft, - top = rect.top - container.clientTop; + pos = new L.Point(x, y); - // webkit (and ie <= 7) handles RTL scrollLeft different to everyone else - // https://code.google.com/p/closure-library/source/browse/trunk/closure/goog/style/bidi.js - if (!L.DomUtil.documentIsLtr() && (L.Browser.webkit || ie7)) { - left += container.scrollWidth - container.clientWidth; - - // ie7 shows the scrollbar by default and provides clientWidth counting it, so we - // need to add it back in if it is visible; scrollbar is on the left as we are RTL - if (ie7 && L.DomUtil.getStyle(container, 'overflow-y') !== 'hidden' && - L.DomUtil.getStyle(container, 'overflow') !== 'hidden') { - left += 17; - } + if (!container) { + return pos; } + var rect = container.getBoundingClientRect(), + left = rect.left - container.clientLeft, + top = rect.top - container.clientTop; + return pos._subtract(new L.Point(left, top)); }, @@ -6447,11 +6658,13 @@ L.Draggable = L.Class.extend({ END: { mousedown: 'mouseup', touchstart: 'touchend', + pointerdown: 'touchend', MSPointerDown: 'touchend' }, MOVE: { mousedown: 'mousemove', touchstart: 'touchmove', + pointerdown: 'touchmove', MSPointerDown: 'touchmove' } }, @@ -6483,28 +6696,21 @@ L.Draggable = L.Class.extend({ }, _onDown: function (e) { + this._moved = false; + if (e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; } - L.DomEvent - .stopPropagation(e); + L.DomEvent.stopPropagation(e); if (L.Draggable._disabled) { return; } L.DomUtil.disableImageDrag(); L.DomUtil.disableTextSelection(); - var first = e.touches ? e.touches[0] : e, - el = first.target; - - // if touching a link, highlight it - if (L.Browser.touch && el.tagName.toLowerCase() === 'a') { - L.DomUtil.addClass(el, 'leaflet-active'); - } - - this._moved = false; - if (this._moving) { return; } + var first = e.touches ? e.touches[0] : e; + this._startPoint = new L.Point(first.clientX, first.clientY); this._startPos = this._newPos = L.DomUtil.getPosition(this._element); @@ -6514,7 +6720,10 @@ L.Draggable = L.Class.extend({ }, _onMove: function (e) { - if (e.touches && e.touches.length > 1) { return; } + if (e.touches && e.touches.length > 1) { + this._moved = true; + return; + } var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), newPoint = new L.Point(first.clientX, first.clientY), @@ -6530,9 +6739,8 @@ L.Draggable = L.Class.extend({ this._moved = true; this._startPos = L.DomUtil.getPosition(this._element).subtract(offset); - if (!L.Browser.touch) { - L.DomUtil.addClass(document.body, 'leaflet-dragging'); - } + L.DomUtil.addClass(document.body, 'leaflet-dragging'); + L.DomUtil.addClass((e.target || e.srcElement), 'leaflet-drag-target'); } this._newPos = this._startPos.add(offset); @@ -6548,10 +6756,9 @@ L.Draggable = L.Class.extend({ this.fire('drag'); }, - _onUp: function () { - if (!L.Browser.touch) { - L.DomUtil.removeClass(document.body, 'leaflet-dragging'); - } + _onUp: function (e) { + L.DomUtil.removeClass(document.body, 'leaflet-dragging'); + L.DomUtil.removeClass((e.target || e.srcElement), 'leaflet-drag-target'); for (var i in L.Draggable.MOVE) { L.DomEvent @@ -6566,7 +6773,9 @@ L.Draggable = L.Class.extend({ // ensure drag is not fired after dragend L.Util.cancelAnimFrame(this._animRequest); - this.fire('dragend'); + this.fire('dragend', { + distance: this._newPos.distanceTo(this._startPos) + }); } this._moving = false; @@ -6638,7 +6847,7 @@ L.Map.Drag = L.Handler.extend({ this._draggable.on('predrag', this._onPreDrag, this); map.on('viewreset', this._onViewReset, this); - this._onViewReset(); + map.whenReady(this._onViewReset, this); } } this._draggable.enable(); @@ -6710,14 +6919,14 @@ L.Map.Drag = L.Handler.extend({ this._draggable._newPos.x = newX; }, - _onDragEnd: function () { + _onDragEnd: function (e) { var map = this._map, options = map.options, delay = +new Date() - this._lastTime, noInertia = !options.inertia || delay > options.inertiaThreshold || !this._positions[0]; - map.fire('dragend'); + map.fire('dragend', e); if (noInertia) { map.fire('moveend'); @@ -6741,6 +6950,8 @@ L.Map.Drag = L.Handler.extend({ map.fire('moveend'); } else { + offset = map._limitOffset(offset, map.options.maxBounds); + L.Util.requestAnimFrame(function () { map.panBy(offset, { duration: decelerationDuration, @@ -6775,7 +6986,7 @@ L.Map.DoubleClickZoom = L.Handler.extend({ _onDoubleClick: function (e) { var map = this._map, - zoom = map.getZoom() + 1; + zoom = map.getZoom() + (e.originalEvent.shiftKey ? -1 : 1); if (map.options.doubleClickZoom === 'center') { map.setZoom(zoom); @@ -6858,8 +7069,8 @@ L.Map.addInitHook('addHandler', 'scrollWheelZoom', L.Map.ScrollWheelZoom); L.extend(L.DomEvent, { - _touchstart: L.Browser.msTouch ? 'MSPointerDown' : 'touchstart', - _touchend: L.Browser.msTouch ? 'MSPointerUp' : 'touchend', + _touchstart: L.Browser.msPointer ? 'MSPointerDown' : L.Browser.pointer ? 'pointerdown' : 'touchstart', + _touchend: L.Browser.msPointer ? 'MSPointerUp' : L.Browser.pointer ? 'pointerup' : 'touchend', // inspired by Zepto touch code by Thomas Fuchs addDoubleTapListener: function (obj, handler, id) { @@ -6875,7 +7086,7 @@ L.extend(L.DomEvent, { function onTouchStart(e) { var count; - if (L.Browser.msTouch) { + if (L.Browser.pointer) { trackedTouches.push(e.pointerId); count = trackedTouches.length; } else { @@ -6894,7 +7105,7 @@ L.extend(L.DomEvent, { } function onTouchEnd(e) { - if (L.Browser.msTouch) { + if (L.Browser.pointer) { var idx = trackedTouches.indexOf(e.pointerId); if (idx === -1) { return; @@ -6903,7 +7114,7 @@ L.extend(L.DomEvent, { } if (doubleTap) { - if (L.Browser.msTouch) { + if (L.Browser.pointer) { // work around .type being readonly with MSPointer* events var newTouch = { }, prop; @@ -6927,15 +7138,15 @@ L.extend(L.DomEvent, { obj[pre + touchstart + id] = onTouchStart; obj[pre + touchend + id] = onTouchEnd; - // on msTouch we need to listen on the document, otherwise a drag starting on the map and moving off screen + // on pointer we need to listen on the document, otherwise a drag starting on the map and moving off screen // will not come through to us, so we will lose track of how many touches are ongoing - var endElement = L.Browser.msTouch ? document.documentElement : obj; + var endElement = L.Browser.pointer ? document.documentElement : obj; obj.addEventListener(touchstart, onTouchStart, false); endElement.addEventListener(touchend, onTouchEnd, false); - if (L.Browser.msTouch) { - endElement.addEventListener('MSPointerCancel', onTouchEnd, false); + if (L.Browser.pointer) { + endElement.addEventListener(L.DomEvent.POINTER_CANCEL, onTouchEnd, false); } return this; @@ -6945,11 +7156,12 @@ L.extend(L.DomEvent, { var pre = '_leaflet_'; obj.removeEventListener(this._touchstart, obj[pre + this._touchstart + id], false); - (L.Browser.msTouch ? document.documentElement : obj).removeEventListener( + (L.Browser.pointer ? document.documentElement : obj).removeEventListener( this._touchend, obj[pre + this._touchend + id], false); - if (L.Browser.msTouch) { - document.documentElement.removeEventListener('MSPointerCancel', obj[pre + this._touchend + id], false); + if (L.Browser.pointer) { + document.documentElement.removeEventListener(L.DomEvent.POINTER_CANCEL, obj[pre + this._touchend + id], + false); } return this; @@ -6963,81 +7175,90 @@ L.extend(L.DomEvent, { L.extend(L.DomEvent, { - _msTouches: [], - _msDocumentListener: false, + //static + POINTER_DOWN: L.Browser.msPointer ? 'MSPointerDown' : 'pointerdown', + POINTER_MOVE: L.Browser.msPointer ? 'MSPointerMove' : 'pointermove', + POINTER_UP: L.Browser.msPointer ? 'MSPointerUp' : 'pointerup', + POINTER_CANCEL: L.Browser.msPointer ? 'MSPointerCancel' : 'pointercancel', - // Provides a touch events wrapper for msPointer events. + _pointers: [], + _pointerDocumentListener: false, + + // Provides a touch events wrapper for (ms)pointer events. // Based on changes by veproza https://github.com/CloudMade/Leaflet/pull/1019 + //ref http://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890 - addMsTouchListener: function (obj, type, handler, id) { + addPointerListener: function (obj, type, handler, id) { switch (type) { case 'touchstart': - return this.addMsTouchListenerStart(obj, type, handler, id); + return this.addPointerListenerStart(obj, type, handler, id); case 'touchend': - return this.addMsTouchListenerEnd(obj, type, handler, id); + return this.addPointerListenerEnd(obj, type, handler, id); case 'touchmove': - return this.addMsTouchListenerMove(obj, type, handler, id); + return this.addPointerListenerMove(obj, type, handler, id); default: throw 'Unknown touch event type'; } }, - addMsTouchListenerStart: function (obj, type, handler, id) { + addPointerListenerStart: function (obj, type, handler, id) { var pre = '_leaflet_', - touches = this._msTouches; + pointers = this._pointers; var cb = function (e) { + L.DomEvent.preventDefault(e); + var alreadyInArray = false; - for (var i = 0; i < touches.length; i++) { - if (touches[i].pointerId === e.pointerId) { + for (var i = 0; i < pointers.length; i++) { + if (pointers[i].pointerId === e.pointerId) { alreadyInArray = true; break; } } if (!alreadyInArray) { - touches.push(e); + pointers.push(e); } - e.touches = touches.slice(); + e.touches = pointers.slice(); e.changedTouches = [e]; handler(e); }; obj[pre + 'touchstart' + id] = cb; - obj.addEventListener('MSPointerDown', cb, false); + obj.addEventListener(this.POINTER_DOWN, cb, false); - // need to also listen for end events to keep the _msTouches list accurate + // need to also listen for end events to keep the _pointers list accurate // this needs to be on the body and never go away - if (!this._msDocumentListener) { + if (!this._pointerDocumentListener) { var internalCb = function (e) { - for (var i = 0; i < touches.length; i++) { - if (touches[i].pointerId === e.pointerId) { - touches.splice(i, 1); + for (var i = 0; i < pointers.length; i++) { + if (pointers[i].pointerId === e.pointerId) { + pointers.splice(i, 1); break; } } }; //We listen on the documentElement as any drags that end by moving the touch off the screen get fired there - document.documentElement.addEventListener('MSPointerUp', internalCb, false); - document.documentElement.addEventListener('MSPointerCancel', internalCb, false); + document.documentElement.addEventListener(this.POINTER_UP, internalCb, false); + document.documentElement.addEventListener(this.POINTER_CANCEL, internalCb, false); - this._msDocumentListener = true; + this._pointerDocumentListener = true; } return this; }, - addMsTouchListenerMove: function (obj, type, handler, id) { + addPointerListenerMove: function (obj, type, handler, id) { var pre = '_leaflet_', - touches = this._msTouches; + touches = this._pointers; function cb(e) { // don't fire touch moves when mouse isn't down - if (e.pointerType === e.MSPOINTER_TYPE_MOUSE && e.buttons === 0) { return; } + if ((e.pointerType === e.MSPOINTER_TYPE_MOUSE || e.pointerType === 'mouse') && e.buttons === 0) { return; } for (var i = 0; i < touches.length; i++) { if (touches[i].pointerId === e.pointerId) { @@ -7053,14 +7274,14 @@ L.extend(L.DomEvent, { } obj[pre + 'touchmove' + id] = cb; - obj.addEventListener('MSPointerMove', cb, false); + obj.addEventListener(this.POINTER_MOVE, cb, false); return this; }, - addMsTouchListenerEnd: function (obj, type, handler, id) { + addPointerListenerEnd: function (obj, type, handler, id) { var pre = '_leaflet_', - touches = this._msTouches; + touches = this._pointers; var cb = function (e) { for (var i = 0; i < touches.length; i++) { @@ -7077,26 +7298,26 @@ L.extend(L.DomEvent, { }; obj[pre + 'touchend' + id] = cb; - obj.addEventListener('MSPointerUp', cb, false); - obj.addEventListener('MSPointerCancel', cb, false); + obj.addEventListener(this.POINTER_UP, cb, false); + obj.addEventListener(this.POINTER_CANCEL, cb, false); return this; }, - removeMsTouchListener: function (obj, type, id) { + removePointerListener: function (obj, type, id) { var pre = '_leaflet_', cb = obj[pre + type + id]; switch (type) { case 'touchstart': - obj.removeEventListener('MSPointerDown', cb, false); + obj.removeEventListener(this.POINTER_DOWN, cb, false); break; case 'touchmove': - obj.removeEventListener('MSPointerMove', cb, false); + obj.removeEventListener(this.POINTER_MOVE, cb, false); break; case 'touchend': - obj.removeEventListener('MSPointerUp', cb, false); - obj.removeEventListener('MSPointerCancel', cb, false); + obj.removeEventListener(this.POINTER_UP, cb, false); + obj.removeEventListener(this.POINTER_CANCEL, cb, false); break; } @@ -7110,7 +7331,8 @@ L.extend(L.DomEvent, { */ L.Map.mergeOptions({ - touchZoom: L.Browser.touch && !L.Browser.android23 + touchZoom: L.Browser.touch && !L.Browser.android23, + bounceAtZoomLimits: true }); L.Map.TouchZoom = L.Handler.extend({ @@ -7163,6 +7385,11 @@ L.Map.TouchZoom = L.Handler.extend({ if (this._scale === 1) { return; } + if (!map.options.bounceAtZoomLimits) { + if ((map.getZoom() === map.getMinZoom() && this._scale < 1) || + (map.getZoom() === map.getMaxZoom() && this._scale > 1)) { return; } + } + if (!this._moved) { L.DomUtil.addClass(map._mapPane, 'leaflet-touching'); @@ -7266,7 +7493,7 @@ L.Map.Tap = L.Handler.extend({ this._startPos = this._newPos = new L.Point(first.clientX, first.clientY); // if touching a link, highlight it - if (el.tagName.toLowerCase() === 'a') { + if (el.tagName && el.tagName.toLowerCase() === 'a') { L.DomUtil.addClass(el, 'leaflet-active'); } @@ -7296,7 +7523,7 @@ L.Map.Tap = L.Handler.extend({ var first = e.changedTouches[0], el = first.target; - if (el.tagName.toLowerCase() === 'a') { + if (el && el.tagName && el.tagName.toLowerCase() === 'a') { L.DomUtil.removeClass(el, 'leaflet-active'); } @@ -7332,7 +7559,7 @@ L.Map.Tap = L.Handler.extend({ } }); -if (L.Browser.touch && !L.Browser.msTouch) { +if (L.Browser.touch && !L.Browser.pointer) { L.Map.addInitHook('addHandler', 'tap', L.Map.Tap); } @@ -7351,6 +7578,7 @@ L.Map.BoxZoom = L.Handler.extend({ this._map = map; this._container = map._container; this._pane = map._panes.overlayPane; + this._moved = false; }, addHooks: function () { @@ -7359,9 +7587,16 @@ L.Map.BoxZoom = L.Handler.extend({ removeHooks: function () { L.DomEvent.off(this._container, 'mousedown', this._onMouseDown); + this._moved = false; + }, + + moved: function () { + return this._moved; }, _onMouseDown: function (e) { + this._moved = false; + if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; } L.DomUtil.disableTextSelection(); @@ -7369,21 +7604,22 @@ L.Map.BoxZoom = L.Handler.extend({ this._startLayerPoint = this._map.mouseEventToLayerPoint(e); - this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); - L.DomUtil.setPosition(this._box, this._startLayerPoint); - - //TODO refactor: move cursor to styles - this._container.style.cursor = 'crosshair'; - L.DomEvent .on(document, 'mousemove', this._onMouseMove, this) .on(document, 'mouseup', this._onMouseUp, this) .on(document, 'keydown', this._onKeyDown, this); - - this._map.fire('boxzoomstart'); }, _onMouseMove: function (e) { + if (!this._moved) { + this._box = L.DomUtil.create('div', 'leaflet-zoom-box', this._pane); + L.DomUtil.setPosition(this._box, this._startLayerPoint); + + //TODO refactor: move cursor to styles + this._container.style.cursor = 'crosshair'; + this._map.fire('boxzoomstart'); + } + var startPoint = this._startLayerPoint, box = this._box, @@ -7396,14 +7632,18 @@ L.Map.BoxZoom = L.Handler.extend({ L.DomUtil.setPosition(box, newPos); + this._moved = true; + // TODO refactor: remove hardcoded 4 pixels box.style.width = (Math.max(0, Math.abs(offset.x) - 4)) + 'px'; box.style.height = (Math.max(0, Math.abs(offset.y) - 4)) + 'px'; }, _finish: function () { - this._pane.removeChild(this._box); - this._container.style.cursor = ''; + if (this._moved) { + this._pane.removeChild(this._box); + this._container.style.cursor = ''; + } L.DomUtil.enableTextSelection(); L.DomUtil.enableImageDrag(); @@ -7461,7 +7701,7 @@ L.Map.Keyboard = L.Handler.extend({ right: [39], down: [40], up: [38], - zoomIn: [187, 107, 61], + zoomIn: [187, 107, 61, 171], zoomOut: [189, 109, 173] }, @@ -7511,7 +7751,7 @@ L.Map.Keyboard = L.Handler.extend({ var body = document.body, docEl = document.documentElement, top = body.scrollTop || docEl.scrollTop, - left = body.scrollTop || docEl.scrollLeft; + left = body.scrollLeft || docEl.scrollLeft; this._map._container.focus(); @@ -7616,6 +7856,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ .on('drag', this._onDrag, this) .on('dragend', this._onDragEnd, this); this._draggable.enable(); + L.DomUtil.addClass(this._marker._icon, 'leaflet-marker-draggable'); }, removeHooks: function () { @@ -7625,6 +7866,7 @@ L.Handler.MarkerDrag = L.Handler.extend({ .off('dragend', this._onDragEnd, this); this._draggable.disable(); + L.DomUtil.removeClass(this._marker._icon, 'leaflet-marker-draggable'); }, moved: function () { @@ -7656,10 +7898,10 @@ L.Handler.MarkerDrag = L.Handler.extend({ .fire('drag'); }, - _onDragEnd: function () { + _onDragEnd: function (e) { this._marker .fire('moveend') - .fire('dragend'); + .fire('dragend', e); } }); @@ -7732,6 +7974,12 @@ L.Control = L.Class.extend({ } return this; + }, + + _refocusOnMap: function () { + if (this._map) { + this._map.getContainer().focus(); + } } }); @@ -7783,7 +8031,11 @@ L.Map.include({ L.Control.Zoom = L.Control.extend({ options: { - position: 'topleft' + position: 'topleft', + zoomInText: '+', + zoomInTitle: 'Zoom in', + zoomOutText: '-', + zoomOutTitle: 'Zoom out' }, onAdd: function (map) { @@ -7793,10 +8045,13 @@ L.Control.Zoom = L.Control.extend({ this._map = map; this._zoomInButton = this._createButton( - '+', 'Zoom in', zoomName + '-in', container, this._zoomIn, this); + this.options.zoomInText, this.options.zoomInTitle, + zoomName + '-in', container, this._zoomIn, this); this._zoomOutButton = this._createButton( - '-', 'Zoom out', zoomName + '-out', container, this._zoomOut, this); + this.options.zoomOutText, this.options.zoomOutTitle, + zoomName + '-out', container, this._zoomOut, this); + this._updateDisabled(); map.on('zoomend zoomlevelschange', this._updateDisabled, this); return container; @@ -7827,7 +8082,8 @@ L.Control.Zoom = L.Control.extend({ .on(link, 'mousedown', stop) .on(link, 'dblclick', stop) .on(link, 'click', L.DomEvent.preventDefault) - .on(link, 'click', fn, context); + .on(link, 'click', fn, context) + .on(link, 'click', this._refocusOnMap, context); return link; }, @@ -7885,6 +8141,12 @@ L.Control.Attribution = L.Control.extend({ this._container = L.DomUtil.create('div', 'leaflet-control-attribution'); L.DomEvent.disableClickPropagation(this._container); + for (var i in map._layers) { + if (map._layers[i].getAttribution) { + this.addAttribution(map._layers[i].getAttribution()); + } + } + map .on('layeradd', this._onLayerAdd, this) .on('layerremove', this._onLayerRemove, this); @@ -8167,8 +8429,9 @@ L.Control.Layers = L.Control.extend({ container.setAttribute('aria-haspopup', true); if (!L.Browser.touch) { - L.DomEvent.disableClickPropagation(container); - L.DomEvent.on(container, 'mousewheel', L.DomEvent.stopPropagation); + L.DomEvent + .disableClickPropagation(container) + .disableScrollPropagation(container); } else { L.DomEvent.on(container, 'click', L.DomEvent.stopPropagation); } @@ -8193,6 +8456,10 @@ L.Control.Layers = L.Control.extend({ else { L.DomEvent.on(link, 'focus', this._expand, this); } + //Work around for Firefox android issue https://github.com/Leaflet/Leaflet/issues/2033 + L.DomEvent.on(form, 'click', function () { + setTimeout(L.bind(this._onInputClick, this), 0); + }, this); this._map.on('click', this._collapse, this); // TODO keyboard accessibility @@ -8327,6 +8594,8 @@ L.Control.Layers = L.Control.extend({ } this._handlingClick = false; + + this._refocusOnMap(); }, _expand: function () { @@ -8447,8 +8716,8 @@ L.Map.include({ setView: function (center, zoom, options) { - zoom = this._limitZoom(zoom); - center = L.latLng(center); + zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom); + center = this._limitCenter(L.latLng(center), zoom, this.options.maxBounds); options = options || {}; if (this._panAnim) { @@ -8904,7 +9173,8 @@ L.Map.include({ var data = { latlng: latlng, - bounds: bounds + bounds: bounds, + timestamp: pos.timestamp }; for (var i in pos.coords) { -- 2.43.2