merge last changes

This commit is contained in:
mourner 2012-02-12 15:15:03 +02:00
parent 5611745b1e
commit 839396ca78
21 changed files with 404 additions and 173 deletions

View File

@ -18,6 +18,7 @@ Leaflet Changelog
* Map now preserves its center after resize.
* When panning to another copy of the world (that's infinite horizontally), map overlays now jump to corresponding positions. [#273](https://github.com/CloudMade/Leaflet/issues/273)
* Limited maximum zoom change on a single mouse wheel movement (so you won't zoom across the whole zoom range in one scroll). [#149](https://github.com/CloudMade/Leaflet/issues/149)
* Significantly improved line simplification performance (noticeable when rendering polylines/polygons with tens of thousands of points)
* Improved circles performance by not drawing them if they're off the clip region.
#### API improvements
@ -27,10 +28,13 @@ Leaflet Changelog
* Improved `Map` `locate` method, added ability to watch location continuously and more options. [#212](https://github.com/CloudMade/Leaflet/issues/212)
* Added second argument `inside` to `Map` `getBoundsZoom` method that allows you to get appropriate zoom for the view to fit *inside* the given bounds.
* Added `hasLayer` method to `Map`.
* Added `Marker` `zIndexOffset` option to be able to set certain markers below/above others. [#65](https://github.com/CloudMade/Leaflet/issues/65)
* Added `urlParams` third optional argument to `TileLayer` constructor for convenience: an object with properties that will be evaluated in the URL template.
* Added `TileLayer` `continuousWorld` option to disable tile coordinates checking/wrapping.
* Added `TileLayer` `tileunload` event fired when tile gets removed after panning (by [@CodeJosch](https://github.com/CodeJosch)). [#256](https://github.com/CloudMade/Leaflet/pull/256)
* Added `TileLayer` `zoomOffset` option useful for non-256px tiles (by [@msaspence](https://github.com/msaspence)).
* Added `TileLayer` `zoomReverse` option to reverse zoom numbering (by [@Majiir](https://github.com/Majiir)). [#406](https://github.com/CloudMade/Leaflet/pull/406)
* Added `TileLayer.Canvas` `redraw` method (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#459](https://github.com/CloudMade/Leaflet/pull/459)
* Added `Polyline` `closestLayerPoint` method that's can be useful for interaction features (by [@anru](https://github.com/anru)). [#186](https://github.com/CloudMade/Leaflet/pull/186)
* Added `setLatLngs` method to `MultiPolyline` and `MultiPolygon` (by [@anru](https://github.com/anru)). [#194](https://github.com/CloudMade/Leaflet/pull/194)
* Added `getBounds` method to `Polyline` and `Polygon` (by [@JasonSanford](https://github.com/JasonSanford)). [#253](https://github.com/CloudMade/Leaflet/pull/253)
@ -39,18 +43,27 @@ Leaflet Changelog
* Added `ImageOverlay` `load` event. [#213](https://github.com/CloudMade/Leaflet/issues/213)
* Added `minWidth` option to `Popup` (by [@marphi](https://github.com/marphi)). [#214](https://github.com/CloudMade/Leaflet/pull/214)
* Improved `LatLng` constructor to be more tolerant (and throw descriptive error if latitude or longitude can't be interpreted as a number). [#136](https://github.com/CloudMade/Leaflet/issues/136)
* Added `LatLng` `distanceTo` method (great circle distance) (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#462](https://github.com/CloudMade/Leaflet/pull/462)
* Added `LatLngBounds` `toBBoxString` method for convenience (by [@JasonSanford](https://github.com/JasonSanford)). [#263](https://github.com/CloudMade/Leaflet/pull/263)
* Added `LatLngBounds` `intersects(otherBounds)` method (thanks to [@pagameba](https://github.com/pagameba)). [#350](https://github.com/CloudMade/Leaflet/pull/350)
* Added `Bounds` `intersects(otherBounds)` method. [#461](https://github.com/CloudMade/Leaflet/issues/461)
* Added `L.Util.template` method for simple string template evaluation.
* Added `DomUtil.removeClass` method (by [@anru](https://github.com/anru)).
* Added ability to pass empty imageUrl to icons for creating transparent clickable regions (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#460](https://github.com/CloudMade/Leaflet/pull/460)
* Improved browser-specific code to rely more on feature detection rather than user agent string.
* Improved superclass access mechanism to work with inheritance chains of 3 or more classes; now you should use `Klass.superclass` instead of `this.superclass` (by [@anru](https://github.com/anru)). [#179](https://github.com/CloudMade/Leaflet/pull/179)
#### Development workflow improvements
* Build system completely overhauled to be based on Node.js, Jake, JSHint and UglifyJS.
* All code is now linted for errors and conformity with a strict code style (with JSHint), and wont build unless the check passes.
### Bugfixes
#### General bugfixes
* Fixed a bug where `Circle` was rendered with incorrect radius (didn't take projection exagerration into account). [#331](https://github.com/CloudMade/Leaflet/issues/331)
* Fixed a bug where `Map` `getBounds` would work incorrectly on a date line cross. [#295](https://github.com/CloudMade/Leaflet/issues/295)
* Fixed a bug where polygons and polylines sometimes rendered incorrectly on some zoom levels. [#381](https://github.com/CloudMade/Leaflet/issues/381)
* Fixed a bug where fast mouse wheel zoom worked incorrectly when approaching min/max zoom values.
* Fixed a bug where `GeoJSON` `pointToLayer` option wouldn't work in a `GeometryCollection`. [#391](https://github.com/CloudMade/Leaflet/issues/391)
@ -66,6 +79,7 @@ Leaflet Changelog
* Fixed broken popup `closePopup` option (by [@jgerigmeyer](https://github.com/jgerigmeyer)).
* Fixed a bug that caused en error when dragging marker with icon without shadow (by [@anru](https://github.com/anru)). [#178](https://github.com/CloudMade/Leaflet/issues/178)
* Fixed a typo in `Bounds` `contains` method (by [@anru](https://github.com/anru)). [#180](https://github.com/CloudMade/Leaflet/pull/180)
* Fixed a bug where creating an empty `Polygon` with `new L.Polygon()` would raise an error.
* Fixed a bug where drag event fired before the actual movement of layer (by [@anru](https://github.com/anru)). [#197](https://github.com/CloudMade/Leaflet/pull/197)
* Fixed a bug where map click caused an error if dragging is initially disabled. [#196](https://github.com/CloudMade/Leaflet/issues/196)
* Fixed a bug where map `movestart` event would fire after zoom animation.
@ -81,6 +95,7 @@ Leaflet Changelog
#### Browser bugfixes
* Fixed occasional crashes on Mac Safari (thanks to [@lapinos03](https://github.com/lapinos03)). [#191](https://github.com/CloudMade/Leaflet/issues/191)
* Fixed a bug where resizing the map would sometimes make it blurry on WebKit (by [@mortenbekditlevsen](https://github.com/mortenbekditlevsen)). [#453](https://github.com/CloudMade/Leaflet/pull/453)
* Fixed a bug that raised error in IE6-8 when clicking on popup close button. [#235](https://github.com/CloudMade/Leaflet/issues/235)
* Fixed a bug with Safari not redrawing UI immediately after closing a popup. [#296](https://github.com/CloudMade/Leaflet/issues/296)
* Fixed a bug that caused performance drop and high CPU usage when calling `setView` or `panTo` to the current center. [#231](https://github.com/CloudMade/Leaflet/issues/231)
@ -91,6 +106,7 @@ Leaflet Changelog
* Fixed a bug that caused an error when clicking vector layers under iOS. [#204](https://github.com/CloudMade/Leaflet/issues/204)
* Fixed crash on Android 3+ when panning or zooming (by [@florian](https://github.com/florianf)). [#137](https://github.com/CloudMade/Leaflet/issues/137)
* Fixed a bug that caused the map to pan when touch-panning inside a popup. [#452](https://github.com/CloudMade/Leaflet/issues/452)
## 0.2.1 (2011-06-18)

View File

@ -47,7 +47,7 @@ exports.uglify = function (code) {
ast = pro.ast_squeeze(ast, {keep_comps: false});
ast = pro.ast_squeeze_more(ast);
return pro.gen_code(ast);
return pro.gen_code(ast) + ';';
};
exports.combineFiles = function (files) {

277
dist/leaflet-src.js vendored
View File

@ -451,7 +451,20 @@ L.Bounds = L.Class.extend({
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
intersects: function (/*Bounds*/ bounds) {
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max;
var xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
}
});
@ -659,7 +672,7 @@ L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
if (noWrap !== true) {
lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
lng = (lng + 180) % 360 + (lng < -180 ? 180 : -180); // wrap longtitude into -180..180
lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
}
//TODO change to lat() & lng()
@ -687,6 +700,22 @@ L.LatLng.prototype = {
return 'LatLng(' +
L.Util.formatNum(this.lat) + ', ' +
L.Util.formatNum(this.lng) + ')';
},
// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
distanceTo: function (/*LatLng*/ other)/*->Double*/ {
var R = 6378137, // earth radius in meters
d2r = L.LatLng.DEG_TO_RAD,
dLat = (other.lat - this.lat) * d2r,
dLon = (other.lng - this.lng) * d2r,
lat1 = this.lat * d2r,
lat2 = other.lat * d2r,
sin1 = Math.sin(dLat / 2),
sin2 = Math.sin(dLon / 2);
var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
};
@ -709,8 +738,8 @@ L.LatLngBounds = L.Class.extend({
// extend the bounds to contain the given point
extend: function (/*LatLng*/ latlng) {
if (!this._southWest && !this._northEast) {
this._southWest = new L.LatLng(latlng.lat, latlng.lng);
this._northEast = new L.LatLng(latlng.lat, latlng.lng);
this._southWest = new L.LatLng(latlng.lat, latlng.lng, true);
this._northEast = new L.LatLng(latlng.lat, latlng.lng, true);
} else {
this._southWest.lat = Math.min(latlng.lat, this._southWest.lat);
this._southWest.lng = Math.min(latlng.lng, this._southWest.lng);
@ -734,11 +763,11 @@ L.LatLngBounds = L.Class.extend({
},
getNorthWest: function () {
return new L.LatLng(this._northEast.lat, this._southWest.lng);
return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
},
getSouthEast: function () {
return new L.LatLng(this._southWest.lat, this._northEast.lng);
return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
},
contains: function (/*LatLngBounds or LatLng*/ obj) /*-> Boolean*/ {
@ -1125,7 +1154,7 @@ L.Map = L.Class.extend({
return this;
}
this._rawPanBy(oldSize.subtract(this.getSize()).divideBy(2));
this._rawPanBy(oldSize.subtract(this.getSize()).divideBy(2, true));
this.fire('move');
@ -1152,8 +1181,8 @@ L.Map = L.Class.extend({
getBounds: function () {
var bounds = this.getPixelBounds(),
sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y)),
ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y));
sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y), this._zoom, true),
ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y), this._zoom, true);
return new L.LatLngBounds(sw, ne);
},
@ -1200,7 +1229,7 @@ L.Map = L.Class.extend({
}
} while (zoomNotFound && (zoom <= maxZoom));
if (zoomNotFound) {
if (zoomNotFound && inside) {
return null;
}
@ -1382,7 +1411,7 @@ L.Map = L.Class.extend({
_initEvents: function () {
L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove'];
var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'];
var i, len;
@ -1420,6 +1449,10 @@ L.Map = L.Class.extend({
return;
}
if (type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
this.fire(type, {
latlng: this.mouseEventToLatLng(e),
layerPoint: this.mouseEventToLayerPoint(e)
@ -1568,9 +1601,11 @@ L.TileLayer = L.Class.extend({
continuousWorld: false,
noWrap: false,
zoomOffset: 0,
zoomReverse: false,
unloadInvisibleTiles: L.Browser.mobile,
updateWhenIdle: L.Browser.mobile
updateWhenIdle: L.Browser.mobile,
reuseTiles: false
},
initialize: function (url, options, urlParams) {
@ -1676,6 +1711,10 @@ L.TileLayer = L.Class.extend({
}
this._tiles = {};
if (this.options.reuseTiles) {
this._unusedTiles = [];
}
if (clearOldContainer && this._container) {
this._container.innerHTML = "";
}
@ -1696,7 +1735,7 @@ L.TileLayer = L.Class.extend({
this._addTilesFromCenterOut(tileBounds);
if (this.options.unloadInvisibleTiles) {
if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
this._removeOtherTiles(tileBounds);
}
},
@ -1747,9 +1786,14 @@ L.TileLayer = L.Class.extend({
// evil, don't do this! crashes Android 3, produces load errors, doesn't solve memory leaks
// this._tiles[key].src = '';
//tile.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
if (tile.parentNode === this._container) {
this._container.removeChild(tile);
}
if (this.options.reuseTiles) {
this._unusedTiles.push(this._tiles[key]);
}
delete this._tiles[key];
}
}
@ -1760,7 +1804,7 @@ L.TileLayer = L.Class.extend({
var tilePos = this._getTilePos(tilePoint),
zoom = this._map.getZoom(),
key = tilePoint.x + ':' + tilePoint.y,
tileLimit = Math.pow(2, (zoom + this.options.zoomOffset));
tileLimit = Math.pow(2, this._getOffsetZoom(zoom));
// wrap tile coordinates
if (!this.options.continuousWorld) {
@ -1777,8 +1821,8 @@ L.TileLayer = L.Class.extend({
}
}
// create tile
var tile = this._createTile();
// get unused tile - or create a new tile
var tile = this._getTile();
L.DomUtil.setPosition(tile, tilePos);
this._tiles[key] = tile;
@ -1792,6 +1836,11 @@ L.TileLayer = L.Class.extend({
container.appendChild(tile);
},
_getOffsetZoom: function (zoom) {
zoom = this.options.zoomReverse ? this.options.maxZoom - zoom : zoom;
return zoom + this.options.zoomOffset;
},
_getTilePos: function (tilePoint) {
var origin = this._map.getPixelOrigin(),
tileSize = this.options.tileSize;
@ -1807,7 +1856,7 @@ L.TileLayer = L.Class.extend({
return L.Util.template(this._url, L.Util.extend({
s: s,
z: zoom + this.options.zoomOffset,
z: this._getOffsetZoom(zoom),
x: tilePoint.x,
y: tilePoint.y
}, this._urlParams));
@ -1822,6 +1871,19 @@ L.TileLayer = L.Class.extend({
this._tileImg.style.height = tileSize + 'px';
},
_getTile: function () {
if (this.options.reuseTiles && this._unusedTiles.length > 0) {
var tile = this._unusedTiles.pop();
this._resetTile(tile);
return tile;
}
return this._createTile();
},
_resetTile: function (tile) {
// Override if data stored on a tile needs to be cleaned up before reuse
},
_createTile: function () {
var tile = this._tileImg.cloneNode(false);
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
@ -1919,6 +1981,17 @@ L.TileLayer.Canvas = L.TileLayer.extend({
L.Util.setOptions(this, options);
},
redraw: function () {
for (var i in this._tiles) {
var tile = this._tiles[i];
this._redrawTile(tile);
}
},
_redrawTile: function (tile) {
this.drawTile(tile, tile._tilePoint, tile._zoom);
},
_createTileProto: function () {
this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
@ -1935,6 +2008,8 @@ L.TileLayer.Canvas = L.TileLayer.extend({
_loadTile: function (tile, tilePoint, zoom) {
tile._layer = this;
tile._tilePoint = tilePoint;
tile._zoom = zoom;
this.drawTile(tile, tilePoint, zoom);
@ -2039,13 +2114,19 @@ L.Icon = L.Class.extend({
_createIcon: function (name) {
var size = this[name + 'Size'],
src = this[name + 'Url'],
img = this._createImg(src);
if (!src) {
src = this[name + 'Url'];
if (!src && name === 'shadow') {
return null;
}
var img;
if (!src) {
img = this._createDiv();
}
else {
img = this._createImg(src);
}
img.className = 'leaflet-marker-' + name;
img.style.marginLeft = (-this.iconAnchor.x) + 'px';
@ -2069,6 +2150,10 @@ L.Icon = L.Class.extend({
el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
}
return el;
},
_createDiv: function () {
return document.createElement('div');
}
});
@ -2085,7 +2170,8 @@ L.Marker = L.Class.extend({
icon: new L.Icon(),
title: '',
clickable: true,
draggable: false
draggable: false,
zIndexOffset: 0
},
initialize: function (latlng, options) {
@ -2179,8 +2265,7 @@ L.Marker = L.Class.extend({
L.DomUtil.setPosition(this._shadow, pos);
}
this._icon.style.zIndex = pos.y;
// TODO zIndex offset
this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
},
_initInteraction: function () {
@ -2227,7 +2312,8 @@ L.Popup = L.Class.extend({
autoPan: true,
closeButton: true,
offset: new L.Point(0, 2),
autoPanPadding: new L.Point(5, 5)
autoPanPadding: new L.Point(5, 5),
className: ''
},
initialize: function (options, source) {
@ -2294,7 +2380,7 @@ L.Popup = L.Class.extend({
},
_initLayout: function () {
this._container = L.DomUtil.create('div', 'leaflet-popup');
this._container = L.DomUtil.create('div', 'leaflet-popup ' + this.options.className);
if (this.options.closeButton) {
this._closeButton = L.DomUtil.create('a', 'leaflet-popup-close-button', this._container);
@ -2404,7 +2490,9 @@ L.Popup = L.Class.extend({
L.Marker.include({
openPopup: function () {
this._popup.setLatLng(this._latlng);
this._map.openPopup(this._popup);
if (this._map) {
this._map.openPopup(this._popup);
}
return this;
},
@ -2816,7 +2904,7 @@ L.Map.include({
L.Path.include({
bindPopup: function (content, options) {
if (!this._popup || this._popup.options !== options) {
this._popup = new L.Popup(options);
this._popup = new L.Popup(options, this);
}
this._popup.setContent(content);
@ -2945,72 +3033,80 @@ L.LineUtil = {
return points.slice();
}
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = this.reducePoints(points, tolerance);
points = this._reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = this.simplifyDP(points, tolerance);
points = this._simplifyDP(points, sqTolerance);
return points;
},
// distance from a point to a segment between two points
pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
return Math.sqrt(this._sqPointToSegmentDist(p, p1, p2));
return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
},
closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
var point = this._sqClosestPointOnSegment(p, p1, p2);
point.distance = Math.sqrt(point._sqDist);
return point;
return this._sqClosestPointOnSegment(p, p1, p2);
},
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
simplifyDP: function (points, tol) {
var maxDist2 = 0,
index = 0,
t2 = tol * tol,
len = points.length,
i, dist2;
_simplifyDP: function (points, sqTolerance) {
if (len < 3) {
return points;
}
var len = points.length,
ArrayConstructor = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
for (i = 0; i < len - 1; i++) {
dist2 = this._sqPointToSegmentDist(points[i], points[0], points[len - 1]);
if (dist2 > maxDist2) {
index = i;
maxDist2 = dist2;
markers[0] = markers[len - 1] = 1;
this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
var part1, part2;
return newPoints;
},
if (maxDist2 >= t2) {
part1 = points.slice(0, index);
part2 = points.slice(index);
_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
part1 = this.simplifyDP(part1, tol);
part2 = this.simplifyDP(part2, tol);
var maxSqDist = 0,
index, i, sqDist;
return part1.concat(part2);
} else {
return [points[0], points[len - 1]];
for (i = first + 1; i <= last - 1; i++) {
sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
this._simplifyDPStep(points, markers, sqTolerance, first, index);
this._simplifyDPStep(points, markers, sqTolerance, index, last);
}
},
// reduce points that are too close to each other to a single point
reducePoints: function (points, tol) {
var reducedPoints = [points[0]],
t2 = tol * tol;
_reducePoints: function (points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (this._sqDist(points[i], points[prev]) < t2) {
continue;
if (this._sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
reducedPoints.push(points[i]);
prev = i;
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
@ -3100,28 +3196,31 @@ L.LineUtil = {
return dx * dx + dy * dy;
},
// return closest point on segment with attribute _sqDist - square distance to segment
_sqClosestPointOnSegment: function (p, p1, p2) {
var x2 = p2.x - p1.x,
y2 = p2.y - p1.y,
apoint = p1;
if (x2 || y2) {
var dot = (p.x - p1.x) * x2 + (p.y - p1.y) * y2,
t = dot / this._sqDist(p1, p2);
// return closest point on segment or distance to that point
_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
apoint = p2;
x = p2.x;
y = p2.y;
} else if (t > 0) {
apoint = new L.Point(p1.x + x2 * t, p1.y + y2 * t);
x += dx * t;
y += dy * t;
}
}
apoint._sqDist = this._sqDist(p, apoint);
return apoint;
},
// distance from a point to a segment between two points
_sqPointToSegmentDist: function (p, p1, p2) {
return this._sqClosestPointOnSegment(p, p1, p2)._sqDist;
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
}
};
@ -3347,7 +3446,7 @@ L.Polygon = L.Polyline.extend({
initialize: function (latlngs, options) {
L.Polyline.prototype.initialize.call(this, latlngs, options);
if (latlngs[0] instanceof Array) {
if (latlngs && (latlngs[0] instanceof Array)) {
this._latlngs = latlngs[0];
this._holes = latlngs.slice(1);
}
@ -3987,7 +4086,7 @@ L.DomEvent = {
},
disableClickPropagation: function (/*HTMLElement*/ el) {
L.DomEvent.addListener(el, 'mousedown', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, L.Draggable.START, L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'click', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'dblclick', L.DomEvent.stopPropagation);
},
@ -4745,12 +4844,15 @@ L.Control.Zoom = L.Class.extend({
L.Control.Attribution = L.Class.extend({
initialize: function (prefix) {
this._prefix = prefix || 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>';
this._attributions = {};
},
onAdd: function (map) {
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
L.DomEvent.disableClickPropagation(this._container);
this._map = map;
this._prefix = 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>';
this._attributions = {};
this._update();
},
@ -5356,6 +5458,8 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
var transform = L.DomUtil.TRANSFORM;
clearTimeout(this._clearTileBgTimer);
//dumb FireFox hack, I have no idea why this magic zero translate fixes the scale transition problem
if (L.Browser.gecko || window.opera) {
this._tileBg.style[transform] += ' translate(0,0)';
@ -5415,7 +5519,10 @@ L.Map.include(!L.DomUtil.TRANSITION ? {} : {
for (var i = 0, len = tiles.length; i < len; i++) {
if (!tiles[i].complete) {
// tiles[i].src = '' - evil, doesn't cancel the request!
tiles[i].onload = L.Util.falseFn;
tiles[i].onerror = L.Util.falseFn;
tiles[i].src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
tiles[i].parentNode.removeChild(tiles[i]);
tiles[i] = null;
}
@ -5491,7 +5598,7 @@ L.Map.include({
options = L.Util.extend({
maxZoom: maxZoom || Infinity,
setView: true
});
}, options);
return this.locate(options);
},

2
dist/leaflet.js vendored

File diff suppressed because one or more lines are too long

View File

@ -21,7 +21,7 @@ describe('LatLng', function() {
it("should clamp longtitude to lie between -180 and 180", function() {
var a = new L.LatLng(0, 190).lng;
expect(a).toEqual(-170);
var b = new L.LatLng(0, 360).lng;
expect(b).toEqual(0);
@ -36,7 +36,13 @@ describe('LatLng', function() {
var f = new L.LatLng(0, -380).lng;
expect(f).toEqual(-20);
});
var g = new L.LatLng(0, 90).lng;
expect(g).toEqual(90);
var h = new L.LatLng(0, 180).lng;
expect(h).toEqual(180);
});
it("should not clamp latitude and longtitude if unbounded flag set to true", function() {
var a = new L.LatLng(150, 0, true).lat;

View File

@ -1,10 +1,13 @@
L.Control.Attribution = L.Class.extend({
initialize: function (prefix) {
this._prefix = prefix || 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>';
this._attributions = {};
},
onAdd: function (map) {
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
L.DomEvent.disableClickPropagation(this._container);
this._map = map;
this._prefix = 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>';
this._attributions = {};
this._update();
},

View File

@ -112,7 +112,7 @@ L.DomEvent = {
},
disableClickPropagation: function (/*HTMLElement*/ el) {
L.DomEvent.addListener(el, 'mousedown', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, L.Draggable.START, L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'click', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'dblclick', L.DomEvent.stopPropagation);
},

View File

@ -12,7 +12,7 @@ L.LatLng = function (/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
if (noWrap !== true) {
lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
lng = (lng + 180) % 360 + (lng < -180 ? 180 : -180); // wrap longtitude into -180..180
lng = (lng + 180) % 360 + ((lng < -180 || lng === 180) ? 180 : -180); // wrap longtitude into -180..180
}
//TODO change to lat() & lng()
@ -40,5 +40,21 @@ L.LatLng.prototype = {
return 'LatLng(' +
L.Util.formatNum(this.lat) + ', ' +
L.Util.formatNum(this.lng) + ')';
},
// Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
distanceTo: function (/*LatLng*/ other)/*->Double*/ {
var R = 6378137, // earth radius in meters
d2r = L.LatLng.DEG_TO_RAD,
dLat = (other.lat - this.lat) * d2r,
dLon = (other.lng - this.lng) * d2r,
lat1 = this.lat * d2r,
lat2 = other.lat * d2r,
sin1 = Math.sin(dLat / 2),
sin2 = Math.sin(dLon / 2);
var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
};

View File

@ -16,8 +16,8 @@ L.LatLngBounds = L.Class.extend({
// extend the bounds to contain the given point
extend: function (/*LatLng*/ latlng) {
if (!this._southWest && !this._northEast) {
this._southWest = new L.LatLng(latlng.lat, latlng.lng);
this._northEast = new L.LatLng(latlng.lat, latlng.lng);
this._southWest = new L.LatLng(latlng.lat, latlng.lng, true);
this._northEast = new L.LatLng(latlng.lat, latlng.lng, true);
} else {
this._southWest.lat = Math.min(latlng.lat, this._southWest.lat);
this._southWest.lng = Math.min(latlng.lng, this._southWest.lng);
@ -41,11 +41,11 @@ L.LatLngBounds = L.Class.extend({
},
getNorthWest: function () {
return new L.LatLng(this._northEast.lat, this._southWest.lng);
return new L.LatLng(this._northEast.lat, this._southWest.lng, true);
},
getSouthEast: function () {
return new L.LatLng(this._southWest.lat, this._northEast.lng);
return new L.LatLng(this._southWest.lat, this._northEast.lng, true);
},
contains: function (/*LatLngBounds or LatLng*/ obj) /*-> Boolean*/ {

View File

@ -46,5 +46,18 @@ L.Bounds = L.Class.extend({
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
intersects: function (/*Bounds*/ bounds) {
var min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max;
var xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
}
});

View File

@ -13,72 +13,80 @@ L.LineUtil = {
return points.slice();
}
var sqTolerance = tolerance * tolerance;
// stage 1: vertex reduction
points = this.reducePoints(points, tolerance);
points = this._reducePoints(points, sqTolerance);
// stage 2: Douglas-Peucker simplification
points = this.simplifyDP(points, tolerance);
points = this._simplifyDP(points, sqTolerance);
return points;
},
// distance from a point to a segment between two points
pointToSegmentDistance: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
return Math.sqrt(this._sqPointToSegmentDist(p, p1, p2));
return Math.sqrt(this._sqClosestPointOnSegment(p, p1, p2, true));
},
closestPointOnSegment: function (/*Point*/ p, /*Point*/ p1, /*Point*/ p2) {
var point = this._sqClosestPointOnSegment(p, p1, p2);
point.distance = Math.sqrt(point._sqDist);
return point;
return this._sqClosestPointOnSegment(p, p1, p2);
},
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
simplifyDP: function (points, tol) {
var maxDist2 = 0,
index = 0,
t2 = tol * tol,
len = points.length,
i, dist2;
_simplifyDP: function (points, sqTolerance) {
if (len < 3) {
return points;
}
var len = points.length,
ArrayConstructor = typeof Uint8Array !== 'undefined' ? Uint8Array : Array,
markers = new ArrayConstructor(len);
for (i = 0; i < len - 1; i++) {
dist2 = this._sqPointToSegmentDist(points[i], points[0], points[len - 1]);
if (dist2 > maxDist2) {
index = i;
maxDist2 = dist2;
markers[0] = markers[len - 1] = 1;
this._simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
var i,
newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
var part1, part2;
return newPoints;
},
if (maxDist2 >= t2) {
part1 = points.slice(0, index);
part2 = points.slice(index);
_simplifyDPStep: function (points, markers, sqTolerance, first, last) {
part1 = this.simplifyDP(part1, tol);
part2 = this.simplifyDP(part2, tol);
var maxSqDist = 0,
index, i, sqDist;
return part1.concat(part2);
} else {
return [points[0], points[len - 1]];
for (i = first + 1; i <= last - 1; i++) {
sqDist = this._sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
this._simplifyDPStep(points, markers, sqTolerance, first, index);
this._simplifyDPStep(points, markers, sqTolerance, index, last);
}
},
// reduce points that are too close to each other to a single point
reducePoints: function (points, tol) {
var reducedPoints = [points[0]],
t2 = tol * tol;
_reducePoints: function (points, sqTolerance) {
var reducedPoints = [points[0]];
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (this._sqDist(points[i], points[prev]) < t2) {
continue;
if (this._sqDist(points[i], points[prev]) > sqTolerance) {
reducedPoints.push(points[i]);
prev = i;
}
reducedPoints.push(points[i]);
prev = i;
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
@ -168,27 +176,30 @@ L.LineUtil = {
return dx * dx + dy * dy;
},
// return closest point on segment with attribute _sqDist - square distance to segment
_sqClosestPointOnSegment: function (p, p1, p2) {
var x2 = p2.x - p1.x,
y2 = p2.y - p1.y,
apoint = p1;
if (x2 || y2) {
var dot = (p.x - p1.x) * x2 + (p.y - p1.y) * y2,
t = dot / this._sqDist(p1, p2);
// return closest point on segment or distance to that point
_sqClosestPointOnSegment: function (p, p1, p2, sqDist) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
dot = dx * dx + dy * dy,
t;
if (dot > 0) {
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
apoint = p2;
x = p2.x;
y = p2.y;
} else if (t > 0) {
apoint = new L.Point(p1.x + x2 * t, p1.y + y2 * t);
x += dx * t;
y += dy * t;
}
}
apoint._sqDist = this._sqDist(p, apoint);
return apoint;
},
// distance from a point to a segment between two points
_sqPointToSegmentDist: function (p, p1, p2) {
return this._sqClosestPointOnSegment(p, p1, p2)._sqDist;
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new L.Point(x, y);
}
};

View File

@ -8,7 +8,8 @@ L.Popup = L.Class.extend({
autoPan: true,
closeButton: true,
offset: new L.Point(0, 2),
autoPanPadding: new L.Point(5, 5)
autoPanPadding: new L.Point(5, 5),
className: ''
},
initialize: function (options, source) {
@ -75,7 +76,7 @@ L.Popup = L.Class.extend({
},
_initLayout: function () {
this._container = L.DomUtil.create('div', 'leaflet-popup');
this._container = L.DomUtil.create('div', 'leaflet-popup ' + this.options.className);
if (this.options.closeButton) {
this._closeButton = L.DomUtil.create('a', 'leaflet-popup-close-button', this._container);

View File

@ -24,13 +24,19 @@ L.Icon = L.Class.extend({
_createIcon: function (name) {
var size = this[name + 'Size'],
src = this[name + 'Url'],
img = this._createImg(src);
if (!src) {
src = this[name + 'Url'];
if (!src && name === 'shadow') {
return null;
}
var img;
if (!src) {
img = this._createDiv();
}
else {
img = this._createImg(src);
}
img.className = 'leaflet-marker-' + name;
img.style.marginLeft = (-this.iconAnchor.x) + 'px';
@ -54,5 +60,9 @@ L.Icon = L.Class.extend({
el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
}
return el;
},
_createDiv: function () {
return document.createElement('div');
}
});

View File

@ -5,7 +5,9 @@
L.Marker.include({
openPopup: function () {
this._popup.setLatLng(this._latlng);
this._map.openPopup(this._popup);
if (this._map) {
this._map.openPopup(this._popup);
}
return this;
},

View File

@ -10,7 +10,8 @@ L.Marker = L.Class.extend({
icon: new L.Icon(),
title: '',
clickable: true,
draggable: false
draggable: false,
zIndexOffset: 0
},
initialize: function (latlng, options) {
@ -104,8 +105,7 @@ L.Marker = L.Class.extend({
L.DomUtil.setPosition(this._shadow, pos);
}
this._icon.style.zIndex = pos.y;
// TODO zIndex offset
this._icon.style.zIndex = pos.y + this.options.zIndexOffset;
},
_initInteraction: function () {

View File

@ -7,6 +7,17 @@ L.TileLayer.Canvas = L.TileLayer.extend({
L.Util.setOptions(this, options);
},
redraw: function () {
for (var i in this._tiles) {
var tile = this._tiles[i];
this._redrawTile(tile);
}
},
_redrawTile: function (tile) {
this.drawTile(tile, tile._tilePoint, tile._zoom);
},
_createTileProto: function () {
this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
@ -23,6 +34,8 @@ L.TileLayer.Canvas = L.TileLayer.extend({
_loadTile: function (tile, tilePoint, zoom) {
tile._layer = this;
tile._tilePoint = tilePoint;
tile._zoom = zoom;
this.drawTile(tile, tilePoint, zoom);

View File

@ -17,9 +17,11 @@ L.TileLayer = L.Class.extend({
continuousWorld: false,
noWrap: false,
zoomOffset: 0,
zoomReverse: false,
unloadInvisibleTiles: L.Browser.mobile,
updateWhenIdle: L.Browser.mobile
updateWhenIdle: L.Browser.mobile,
reuseTiles: false
},
initialize: function (url, options, urlParams) {
@ -125,6 +127,10 @@ L.TileLayer = L.Class.extend({
}
this._tiles = {};
if (this.options.reuseTiles) {
this._unusedTiles = [];
}
if (clearOldContainer && this._container) {
this._container.innerHTML = "";
}
@ -145,7 +151,7 @@ L.TileLayer = L.Class.extend({
this._addTilesFromCenterOut(tileBounds);
if (this.options.unloadInvisibleTiles) {
if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
this._removeOtherTiles(tileBounds);
}
},
@ -196,9 +202,14 @@ L.TileLayer = L.Class.extend({
// evil, don't do this! crashes Android 3, produces load errors, doesn't solve memory leaks
// this._tiles[key].src = '';
//tile.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
if (tile.parentNode === this._container) {
this._container.removeChild(tile);
}
if (this.options.reuseTiles) {
this._unusedTiles.push(this._tiles[key]);
}
delete this._tiles[key];
}
}
@ -209,7 +220,7 @@ L.TileLayer = L.Class.extend({
var tilePos = this._getTilePos(tilePoint),
zoom = this._map.getZoom(),
key = tilePoint.x + ':' + tilePoint.y,
tileLimit = Math.pow(2, (zoom + this.options.zoomOffset));
tileLimit = Math.pow(2, this._getOffsetZoom(zoom));
// wrap tile coordinates
if (!this.options.continuousWorld) {
@ -226,8 +237,8 @@ L.TileLayer = L.Class.extend({
}
}
// create tile
var tile = this._createTile();
// get unused tile - or create a new tile
var tile = this._getTile();
L.DomUtil.setPosition(tile, tilePos);
this._tiles[key] = tile;
@ -241,6 +252,11 @@ L.TileLayer = L.Class.extend({
container.appendChild(tile);
},
_getOffsetZoom: function (zoom) {
zoom = this.options.zoomReverse ? this.options.maxZoom - zoom : zoom;
return zoom + this.options.zoomOffset;
},
_getTilePos: function (tilePoint) {
var origin = this._map.getPixelOrigin(),
tileSize = this.options.tileSize;
@ -256,7 +272,7 @@ L.TileLayer = L.Class.extend({
return L.Util.template(this._url, L.Util.extend({
s: s,
z: zoom + this.options.zoomOffset,
z: this._getOffsetZoom(zoom),
x: tilePoint.x,
y: tilePoint.y
}, this._urlParams));
@ -271,6 +287,19 @@ L.TileLayer = L.Class.extend({
this._tileImg.style.height = tileSize + 'px';
},
_getTile: function () {
if (this.options.reuseTiles && this._unusedTiles.length > 0) {
var tile = this._unusedTiles.pop();
this._resetTile(tile);
return tile;
}
return this._createTile();
},
_resetTile: function (tile) {
// Override if data stored on a tile needs to be cleaned up before reuse
},
_createTile: function () {
var tile = this._tileImg.cloneNode(false);
tile.onselectstart = tile.onmousemove = L.Util.falseFn;

View File

@ -5,7 +5,7 @@
L.Path.include({
bindPopup: function (content, options) {
if (!this._popup || this._popup.options !== options) {
this._popup = new L.Popup(options);
this._popup = new L.Popup(options, this);
}
this._popup.setContent(content);

View File

@ -10,7 +10,7 @@ L.Polygon = L.Polyline.extend({
initialize: function (latlngs, options) {
L.Polyline.prototype.initialize.call(this, latlngs, options);
if (latlngs[0] instanceof Array) {
if (latlngs && (latlngs[0] instanceof Array)) {
this._latlngs = latlngs[0];
this._holes = latlngs.slice(1);
}

View File

@ -253,7 +253,7 @@ L.Map = L.Class.extend({
return this;
}
this._rawPanBy(oldSize.subtract(this.getSize()).divideBy(2));
this._rawPanBy(oldSize.subtract(this.getSize()).divideBy(2, true));
this.fire('move');
@ -280,8 +280,8 @@ L.Map = L.Class.extend({
getBounds: function () {
var bounds = this.getPixelBounds(),
sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y)),
ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y));
sw = this.unproject(new L.Point(bounds.min.x, bounds.max.y), this._zoom, true),
ne = this.unproject(new L.Point(bounds.max.x, bounds.min.y), this._zoom, true);
return new L.LatLngBounds(sw, ne);
},
@ -328,7 +328,7 @@ L.Map = L.Class.extend({
}
} while (zoomNotFound && (zoom <= maxZoom));
if (zoomNotFound) {
if (zoomNotFound && inside) {
return null;
}
@ -510,7 +510,7 @@ L.Map = L.Class.extend({
_initEvents: function () {
L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove'];
var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove', 'contextmenu'];
var i, len;
@ -548,6 +548,10 @@ L.Map = L.Class.extend({
return;
}
if (type === 'contextmenu') {
L.DomEvent.preventDefault(e);
}
this.fire(type, {
latlng: this.mouseEventToLatLng(e),
layerPoint: this.mouseEventToLayerPoint(e)

View File

@ -42,7 +42,7 @@ L.Map.include({
options = L.Util.extend({
maxZoom: maxZoom || Infinity,
setView: true
});
}, options);
return this.locate(options);
},