Installed leaflet in extlib

This commit is contained in:
Joar Wandborg 2012-01-10 01:54:37 +01:00
parent c47a03b909
commit c5ba5b0456
96 changed files with 5756 additions and 0 deletions

View File

@ -0,0 +1,88 @@
Leaflet Changelog
=================
## 0.3 (master)
## 0.2.1 (2011-06-18)
* Fixed regression that caused error in `TileLayer.Canvas`
## 0.2 (2011-06-17)
### Major features
* Added **WMS** support (`TileLayer.WMS` layer).
* Added different **projections** support, having `EPSG:3857`, `EPSG:4326` and `EPSG:3395` out of the box (through `crs` option in `Map`). Thanks to [@Miroff](https://github.com/Miroff) & [@Komzpa](https://github.com/Komzpa) for great advice and explanation regarding this.
* Added **GeoJSON** layer support.
### Improvements
#### Usability improvements
* Improved panning performance in Chrome and FF considerably with the help of `requestAnimationFrame`. [#130](https://github.com/CloudMade/Leaflet/issues/130)
* Improved click responsiveness in mobile WebKit (now it happens without delay). [#26](https://github.com/CloudMade/Leaflet/issues/26)
* Added tap tolerance (so click happens even if you moved your finger slighly when tapping).
* Improved geolocation error handling: better error messages, explicit timeout, set world view on locateAndSetView failure. [#61](https://github.com/CloudMade/Leaflet/issues/61)
#### API improvements
* Added **MultiPolyline** and **MultiPolygon** layers. [#77](https://github.com/CloudMade/Leaflet/issues/77)
* Added **LayerGroup** and **FeatureGroup** layers for grouping other layers.
* Added **TileLayer.Canvas** for easy creation of canvas-based tile layers.
* Changed `Circle` to be zoom-dependent (with radius in meters); circle of a permanent size is now called `CircleMarker`.
* Added `mouseover` and `mouseout` events to map, markers and paths; added map `mousemove` event.
* Added `setLatLngs`, `spliceLatLngs`, `addLatLng`, `getLatLngs` methods to polylines and polygons.
* Added `setLatLng` and `setRadius` methods to `Circle` and `CircleMarker`.
* Improved `LatLngBounds contains` method to accept `LatLng` in addition to `LatLngBounds`, the same for `Bounds contains` and `Point`
* Improved `LatLngBounds` & `Bounds` to allow their instantiation without arguments (by [@snc](https://github.com/snc)).
* Added TMS tile numbering support through `TileLayer` `scheme: 'tms'` option (by [@tmcw](https://github.com/tmcw)).
* Added `TileLayer` `noWrap` option to disable wrapping `x` tile coordinate (by [@jasondavies](https://github.com/jasondavies)).
* Added `opacity` option and `setOpacity` method to `TileLayer`.
* Added `setLatLng` and `setIcon` methods to `Marker`.
* Added `title` option to `Marker`.
* Added `maxZoom` argument to `map.locateAndSetView` method.
* Added ability to pass Geolocation options to map `locate` and `locateAndSetView` methods (by [@JasonSanford](https://github.com/JasonSanford)).
* Improved `Popup` to accept HTML elements in addition to strings as its content.
#### Development workflow improvements
* Added `Makefile` for building `leaflet.js` on non-Windows machines (by [@tmcw](https://github.com/tmcw)).
* Improved `debug/leaflet-include.js` script to allow using it outside of `debug` folder (by [@antonj](https://github.com/antonj)).
* Improved `L` definition to be compatible with CommonJS. [#122](https://github.com/CloudMade/Leaflet/issues/122)
### Bug fixes
#### General bugfixes
* Fixed a bug where zooming is broken if the map contains a polygon and you zoom to an area where it's not visible. [#47](https://github.com/CloudMade/Leaflet/issues/47)
* Fixed a bug where closed polylines would not appear on the map.
* Fixed a bug where marker that was added, removed and then added again would not appear on the map. [#66](https://github.com/CloudMade/Leaflet/issues/66)
* Fixed a bug where tile layer that was added, removed and then added again would not appear on the map.
* Fixed a bug where some tiles would not load when panning across the date line. [#97](https://github.com/CloudMade/Leaflet/issues/97)
* Fixed a bug where map div with `position: absolute` is reset to `relative`. [#100](https://github.com/CloudMade/Leaflet/issues/100)
* Fixed a bug that caused an error when trying to add a marker without shadow in its icon.
* Fixed a bug where popup content would not update on `setContent` call. [#94](https://github.com/CloudMade/Leaflet/issues/94)
* Fixed a bug where double click zoom wouldn't work if popup is opened on map click
* Fixed a bug with click propagation on popup close button. [#99](https://github.com/CloudMade/Leaflet/issues/99)
* Fixed inability to remove ImageOverlay layer.
#### Browser bugfixes
* Fixed a bug where paths would not appear in IE8.
* Fixed a bug where there were occasional slowdowns before zoom animation in WebKit. [#123](https://github.com/CloudMade/Leaflet/issues/123)
* Fixed incorrect zoom animation & popup styling in Opera 11.11.
* Fixed popup fade animation in Firefox and Opera.
* Fixed a bug where map isn't displayed in Firefox when there's an `img { max-width: 100% }` rule.
#### Mobile browsers bugfixes
* Fixed a bug that prevented panning on some Android 2.1 (and possibly older) devices. [#84](https://github.com/CloudMade/Leaflet/issues/84)
* Disabled zoom animation on Android by default because it's buggy on some devices (will be enabled back when it's stable enough). [#32](https://github.com/CloudMade/Leaflet/issues/32)
* Fixed a bug where map would occasionally break while multi-touch-zooming on iOS. [#32](https://github.com/CloudMade/Leaflet/issues/32)
* Fixed a bug that prevented panning/clicking on Android 3 tablets. [#121](https://github.com/CloudMade/Leaflet/issues/121)
* Fixed a bug that prevented panning/clicking on Opera Mobile. [#138](https://github.com/CloudMade/Leaflet/issues/138)
* Fixed potentional memory leak on WebKit when removing tiles, thanks to [@Scalar4eg](https://github.com/Scalar4eg). [#107](https://github.com/CloudMade/Leaflet/issues/107)
## 0.1 (2011-05-13)
* Initial Leaflet release.

22
extlib/leaflet/LICENSE Normal file
View File

@ -0,0 +1,22 @@
Copyright (c) 2010-2011, CloudMade, Vladimir Agafonkin
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright notice, this list of
conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list
of conditions and the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

10
extlib/leaflet/README.md Normal file
View File

@ -0,0 +1,10 @@
<img src="http://leaflet.cloudmade.com/docs/images/logo.png" alt="Leaflet" />
Leaflet is a modern, lightweight BSD-licensed JavaScript library for making tile-based interactive maps for both desktop and mobile web browsers, developed by [CloudMade](http://cloudmade.com) to form the core of its next generation JavaScript API.
It is built from the ground up to work efficiently and smoothly on both platforms, utilizing cutting-edge technologies included in HTML5. Its top priorities are usability, performance and small size, [A-grade](http://developer.yahoo.com/yui/articles/gbs/) browser support, flexibility and easy to use API. The OOP-based code of the library is designed to be modular, extensible and very easy to understand.
Check out the website for more information: [leaflet.cloudmade.com](http://leaflet.cloudmade.com)
## Contributing to Leaflet
Let's make the best open-source library for maps that can possibly exist! Please send your pull requests to [Vladimir Agafonkin](http://github.com/mourner) (Leaflet maintainer) - we'll be happy to accept your contributions! [List of Leaflet contributors](http://github.com/CloudMade/Leaflet/contributors)

View File

@ -0,0 +1,29 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../include.js"></script>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18}),
latlng = new L.LatLng(50.5, 30.51);
var map = new L.Map('map').addLayer(cloudmade).setView(latlng, 15);
var zoomControl = new L.Control.Zoom();
map.addControl(zoomControl);
</script>
</body>
</html>

View File

@ -0,0 +1,6 @@
html, body, #map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}

View File

@ -0,0 +1,5 @@
#map {
width: 800px;
height: 600px;
border: 1px solid #ccc;
}

View File

@ -0,0 +1,50 @@
var geojsonSample = {
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [102.0, 0.5]
},
"properties": {
"prop0": "value0",
"color": "blue"
}
},
{
"type": "Feature",
"geometry": {
"type": "LineString",
"coordinates": [[102.0, 0.0], [103.0, 1.0], [104.0, 0.0], [105.0, 1.0]]
},
"properties": {
"color": "red",
"prop1": 0.0
}
},
{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [[[100.0, 0.0], [101.0, 0.0], [101.0, 1.0], [100.0, 1.0], [100.0, 0.0]]]
},
"properties": {
"color": "green",
"prop1": {
"this": "that"
}
}
},
{
"type": "Feature",
"geometry": {
"type": "MultiPolygon",
"coordinates": [[[[100.0, 1.5], [100.5, 1.5], [100.5, 2.0], [100.0, 2.0], [100.0, 1.5]]], [[[100.5, 2.0], [100.5, 2.5], [101.0, 2.5], [101.0, 2.0], [100.5, 2.0]]]]
}
}
]
};

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
<button id="populate">Populate with 10 markers</button>
<script type="text/javascript" src="geojson-sample.js"></script>
<script type="text/javascript">
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
var map = new L.Map('map', {
center: new L.LatLng(0.78, 102.37),
zoom: 7,
layers: [cloudmade]
});
var geojson = new L.GeoJSON();
/* points are rendered as markers by default, but you can change this:
var geojson = new L.GeoJSON(null, {
pointToLayer: function(latlng) { return new L.CircleMarker(latlng); }
});
*/
geojson.on('featureparse', function(e) {
// you can style features depending on their properties, etc.
var popupText = 'geometry type: ' + e.geometryType + '<br/>';
if (e.layer instanceof L.Path) {
e.layer.setStyle({color: e.properties.color});
popupText += 'color: ' + e.properties.color;
}
e.layer.bindPopup(popupText);
});
geojson.addGeoJSON(geojsonSample);
map.addLayer(geojson);
</script>
</body>
</html>

View File

@ -0,0 +1,100 @@
(function() {
//TODO replace script list with the one from ../buid/deps.js
var scripts = [
'Leaflet.js',
'core/Util.js',
'core/Class.js',
'core/Events.js',
'core/Browser.js',
'geometry/Point.js',
'geometry/Bounds.js',
'geometry/Transformation.js',
'geometry/LineUtil.js',
'geometry/PolyUtil.js',
'dom/DomEvent.js',
'dom/DomEvent.DoubleTap.js',
'dom/DomUtil.js',
'dom/Draggable.js',
'dom/transition/Transition.js',
'dom/transition/Transition.Native.js',
'dom/transition/Transition.Timer.js',
'geo/LatLng.js',
'geo/LatLngBounds.js',
'geo/projection/Projection.js',
'geo/projection/Projection.SphericalMercator.js',
'geo/projection/Projection.LonLat.js',
'geo/projection/Projection.Mercator.js',
'geo/crs/CRS.js',
'geo/crs/CRS.EPSG3857.js',
'geo/crs/CRS.EPSG4326.js',
'geo/crs/CRS.EPSG3395.js',
'layer/LayerGroup.js',
'layer/FeatureGroup.js',
'layer/tile/TileLayer.js',
'layer/tile/TileLayer.WMS.js',
'layer/tile/TileLayer.Canvas.js',
'layer/ImageOverlay.js',
'layer/Popup.js',
'layer/marker/Icon.js',
'layer/marker/Marker.js',
'layer/marker/Marker.Popup.js',
'layer/vector/Path.js',
'layer/vector/Path.VML.js',
'layer/vector/Path.Popup.js',
'layer/vector/Polyline.js',
'layer/vector/Polygon.js',
'layer/vector/MultiPoly.js',
'layer/vector/Circle.js',
'layer/vector/CircleMarker.js',
'layer/GeoJSON.js',
'handler/Handler.js',
'handler/MapDrag.js',
'handler/TouchZoom.js',
'handler/DoubleClickZoom.js',
'handler/ScrollWheelZoom.js',
'handler/ShiftDragZoom.js',
'handler/MarkerDrag.js',
'control/Control.js',
'control/Control.Zoom.js',
'control/Control.Attribution.js',
'map/Map.js',
'map/ext/Map.Geolocation.js',
'map/ext/Map.Popup.js',
'map/ext/Map.PanAnimation.js',
'map/ext/Map.ZoomAnimation.js',
'map/ext/Map.Control.js'
];
function getSrcUrl() {
var scripts = document.getElementsByTagName('script');
for (var i = 0; i < scripts.length; i++) {
var src = scripts[i].src;
if (src) {
var res = src.match(/^(.*)leaflet-include\.js$/);
if (res) {
return res[1] + '../src/';
}
}
}
}
var path = getSrcUrl();
for (var i = 0; i < scripts.length; i++) {
document.writeln("<script type='text/javascript' src='" + path + "../src/" + scripts[i] + "'></script>");
}
})();

View File

@ -0,0 +1,46 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
<script type="text/javascript">
var tiles = new L.TileLayer.Canvas();
tiles.drawTile = function(canvas, tile, zoom) {
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, 255, 255);
ctx.fillStyle = 'black';
ctx.fillText('x: ' + tile.x + ', y: ' + tile.y + ', zoom:' + zoom, 20, 20);
ctx.strokeStyle = 'red';
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(255, 0);
ctx.lineTo(255, 255);
ctx.lineTo(0, 255);
ctx.closePath();
ctx.stroke();
}
var map = new L.Map('map', {center: new L.LatLng(50.5, 30.51), zoom: 15, layers: [tiles]});
</script>
</body>
</html>

View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map"></div>
<script type="text/javascript">
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
var map = new L.Map('map').addLayer(cloudmade);
map.on('locationfound', function(e) {
var marker = new L.Marker(e.latlng);
map.addLayer(marker);
marker.bindPopup("<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede.</p><p>Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque felis.</p>");
});
map.on('locationerror', function(e) {
alert(e.message);
map.fitWorld();
});
map.locateAndSetView();
</script>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map" style="width: 600px; height: 600px; border: 1px solid #ccc"></div>
<button id="populate">Populate with 10 markers</button>
<script type="text/javascript">
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution}),
latlng = new L.LatLng(50.5, 30.51);
var map = new L.Map('map', {center: latlng, zoom: 15, layers: [cloudmade]});
var markers = new L.FeatureGroup();
function populate() {
var bounds = map.getBounds(),
southWest = bounds.getSouthWest(),
northEast = bounds.getNorthEast(),
lngSpan = northEast.lng - southWest.lng,
latSpan = northEast.lat - southWest.lat;
for (var i = 0; i < 10; i++) {
var latlng = new L.LatLng(
southWest.lat + latSpan * Math.random(),
southWest.lng + lngSpan * Math.random());
markers.addLayer(new L.Marker(latlng));
}
return false;
};
markers.bindPopup("<p>Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec odio. Quisque volutpat mattis eros. Nullam malesuada erat ut turpis. Suspendisse urna nibh, viverra non, semper suscipit, posuere a, pede.</p><p>Donec nec justo eget felis facilisis fermentum. Aliquam porttitor mauris sit amet orci. Aenean dignissim pellentesque.</p>");
map.addLayer(markers);
populate();
L.DomUtil.get('populate').onclick = populate;
</script>
</body>
</html>

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map" style="width: 1024px; height: 440px; border: 1px solid #ccc"></div>
<script type="text/javascript">
var map = new L.Map('map', {crs: L.CRS.EPSG4326});
var bluemarble = new L.TileLayer.WMS("http://maps.opengeo.org/geowebcache/service/wms", {
layers: 'bluemarble',
attribution: "Data &copy; NASA Blue Marble, image service by OpenGeo",
minZoom: 2,
maxZoom: 5,
});
map.addLayer(bluemarble).fitWorld();
</script>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
<script type="text/javascript">
var map = new L.Map('map');
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmadeAttribution = 'Map data &copy; 2011 OpenStreetMap contributors, Imagery &copy; 2011 CloudMade',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18, attribution: cloudmadeAttribution});
var nexrad = new L.TileLayer.WMS("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
layers: 'nexrad-n0r-900913',
format: 'image/png',
transparent: true,
attribution: "Weather data &copy; 2011 IEM Nexrad",
opacity: 0.4
});
var bounds = new L.LatLngBounds(new L.LatLng(32, -126), new L.LatLng(50, -64));
map.addLayer(cloudmade).addLayer(nexrad).fitBounds(bounds);
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/mobile.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map"></div>
<script src="route.js"></script>
<script>
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
latlngs.push(new L.LatLng(route[i][0], route[i][1]));
}
var path = new L.Polyline(latlngs, {smoothFactor: 1});
var map = new L.Map('map', {layers: [cloudmade]});
map.fitBounds(new L.LatLngBounds(latlngs));
map.addLayer(new L.Marker(latlngs[0]));
map.addLayer(new L.Marker(latlngs[latlngs.length - 1]));
map.addLayer(path);
</script>
</body>
</html>

View File

@ -0,0 +1,38 @@
<!DOCTYPE html>
<html>
<head>
<title>Leaflet debug page</title>
<link rel="stylesheet" href="../../dist/leaflet.css" />
<!--[if lte IE 8]><link rel="stylesheet" href="../../dist/leaflet.ie.css" /><![endif]-->
<link rel="stylesheet" href="../css/screen.css" />
<script src="../leaflet-include.js"></script>
</head>
<body>
<div id="map" style="width: 800px; height: 600px; border: 1px solid #ccc"></div>
<script src="route.js"></script>
<script>
var cloudmadeUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/997/256/{z}/{x}/{y}.png',
cloudmade = new L.TileLayer(cloudmadeUrl, {maxZoom: 18});
for (var i = 0, latlngs = [], len = route.length; i < len; i++) {
latlngs.push(new L.LatLng(route[i][0], route[i][1]));
}
var path = new L.Polyline(latlngs);
var map = new L.Map('map', {layers: [cloudmade]});
map.fitBounds(new L.LatLngBounds(latlngs));
map.addLayer(new L.Marker(latlngs[0]));
map.addLayer(new L.Marker(latlngs[len - 1]));
map.addLayer(path);
path.bindPopup("Hello world");
</script>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html>
<head>
<title>Jasmine Test Runner</title>
<link rel="stylesheet" type="text/css" href="../lib/jasmine/jasmine.css">
<script type="text/javascript" src="../lib/jasmine/jasmine.js"></script>
<script type="text/javascript" src="../lib/jasmine/jasmine-html.js"></script>
<!-- source files -->
<script type="text/javascript">
L = 'test'; //to test L#noConflict later
</script>
<script type="text/javascript" src="../src/Leaflet.js"></script>
<!-- /core -->
<script type="text/javascript" src="../src/core/Util.js"></script>
<script type="text/javascript" src="../src/core/Class.js"></script>
<script type="text/javascript" src="../src/core/Events.js"></script>
<script type="text/javascript" src="../src/core/Browser.js"></script>
<!-- /dom -->
<script type="text/javascript" src="../src/dom/DomEvent.js"></script>
<script type="text/javascript" src="../src/dom/DomUtil.js"></script>
<!-- /geo -->
<script type="text/javascript" src="../src/geo/LatLng.js"></script>
<script type="text/javascript" src="../src/geo/LatLngBounds.js"></script>
<script type="text/javascript" src="../src/geo/Projection.js"></script>
<!-- /geometry -->
<script type="text/javascript" src="../src/geometry/Point.js"></script>
<script type="text/javascript" src="../src/geometry/Bounds.js"></script>
<script type="text/javascript" src="../src/geometry/Transformation.js"></script>
<!-- /layer -->
<script type="text/javascript" src="../src/layer/TileLayer.js"></script>
<!-- /map -->
<script type="text/javascript" src="../src/map/Map.js"></script>
<!-- spec files -->
<script type="text/javascript" src="suites/SpecHelper.js"></script>
<script type="text/javascript" src="suites/LeafletSpec.js"></script>
<!-- /core -->
<script type="text/javascript" src="suites/core/UtilSpec.js"></script>
<script type="text/javascript" src="suites/core/ClassSpec.js"></script>
<script type="text/javascript" src="suites/core/EventsSpec.js"></script>
<!-- /geometry -->
<script type="text/javascript" src="suites/geometry/PointSpec.js"></script>
<script type="text/javascript" src="suites/geometry/BoundsSpec.js"></script>
<script type="text/javascript" src="suites/geometry/TransformationSpec.js"></script>
<!-- /geo -->
<script type="text/javascript" src="suites/geo/LatLngSpec.js"></script>
<script type="text/javascript" src="suites/geo/LatLngBoundsSpec.js"></script>
<script type="text/javascript" src="suites/geo/ProjectionSpec.js"></script>
<!-- /dom -->
<script type="text/javascript" src="suites/dom/DomEventSpec.js"></script>
<script type="text/javascript" src="suites/dom/DomUtilSpec.js"></script>
<!-- /layer -->
<script type="text/javascript" src="suites/layer/TileLayerSpec.js"></script>
<!-- /map -->
<script type="text/javascript" src="suites/map/MapSpec.js"></script>
</head>
<body>
<script type="text/javascript">
jasmine.getEnv().addReporter(new jasmine.TrivialReporter());
jasmine.getEnv().execute();
</script>
</body>
</html>

View File

@ -0,0 +1,15 @@
describe('L#noConflict', function() {
it('should restore the previous L value and return Leaflet namespace', function(){
expect(L.VERSION).toBeDefined();
var L2 = L.noConflict();
expect(L).toEqual('test');
expect(L2.VERSION).toBeDefined();
this.after(function() {
window.L = L2;
});
});
});

View File

@ -0,0 +1,5 @@
function noSpecs() {
it('should have specs', function() {
expect('specs').toBe();
});
}

View File

@ -0,0 +1,120 @@
describe("Class", function() {
describe("#extend", function() {
var Klass,
constructor,
method;
beforeEach(function() {
constructor = jasmine.createSpy(),
method = jasmine.createSpy();
Klass = L.Class.extend({
statics: {bla: 1},
includes: {mixin: true},
initialize: constructor,
foo: 5,
bar: method
});
});
it("should create a class with the given constructor & properties", function() {
var a = new Klass();
expect(constructor).toHaveBeenCalled();
expect(a.foo).toEqual(5);
a.bar();
expect(method).toHaveBeenCalled();
});
it("should inherit parent classes' constructor & properties", function() {
var Klass2 = Klass.extend({baz: 2});
var b = new Klass2();
expect(b instanceof Klass).toBeTruthy();
expect(b instanceof Klass2).toBeTruthy();
expect(constructor).toHaveBeenCalled();
expect(b.baz).toEqual(2);
b.bar();
expect(method).toHaveBeenCalled();
});
it("should grant the ability to call parent methods, including constructor", function() {
var Klass2 = Klass.extend({
initialize: function() {},
bar: function() {}
});
var b = new Klass2();
expect(constructor).not.toHaveBeenCalled();
b.superclass.initialize.call(this);
expect(constructor).toHaveBeenCalled();
b.superclass.bar.call(this);
expect(method).toHaveBeenCalled();
});
it("should support static properties", function() {
expect(Klass.bla).toEqual(1);
});
it("should inherit parent static properties", function() {
var Klass2 = Klass.extend({});
expect(Klass2.bla).toEqual(1);
});
it("should include the given mixin", function() {
var a = new Klass();
expect(a.mixin).toBeTruthy();
});
it("should be able to include multiple mixins", function() {
var Klass2 = L.Class.extend({
includes: [{mixin: true}, {mixin2: true}]
});
var a = new Klass2();
expect(a.mixin).toBeTruthy();
expect(a.mixin2).toBeTruthy();
});
it("should grant the ability to include the given mixin", function() {
Klass.include({mixin2: true});
var a = new Klass();
expect(a.mixin2).toBeTruthy();
});
it("should merge options instead of replacing them", function() {
var KlassWithOptions1 = L.Class.extend({
options: {
foo1: 1,
foo2: 2
}
});
var KlassWithOptions2 = KlassWithOptions1.extend({
options: {
foo2: 3,
foo3: 4
}
});
var a = new KlassWithOptions2();
expect(a.options).toEqual({
foo1: 1,
foo2: 3,
foo3: 4
});
});
});
});

View File

@ -0,0 +1,110 @@
describe('Events', function() {
var Klass;
beforeEach(function() {
Klass = L.Class.extend({
includes: L.Mixin.Events
});
});
describe('#fireEvent', function() {
it('should fire all listeners added through #addEventListener', function() {
var obj = new Klass(),
spy = jasmine.createSpy(),
spy2 = jasmine.createSpy(),
spy3 = jasmine.createSpy();
obj.addEventListener('test', spy);
obj.addEventListener('test', spy2);
obj.addEventListener('other', spy3);
expect(spy).not.toHaveBeenCalled();
expect(spy2).not.toHaveBeenCalled();
expect(spy3).not.toHaveBeenCalled();
obj.fireEvent('test');
expect(spy).toHaveBeenCalled();
expect(spy2).toHaveBeenCalled();
expect(spy3).not.toHaveBeenCalled();
});
it('should provide event object to listeners and execute them in the right context', function() {
var obj = new Klass(),
obj2 = new Klass(),
foo = {};
function listener1(e) {
expect(e.type).toEqual('test');
expect(e.target).toEqual(obj);
expect(this).toEqual(obj);
expect(e.bar).toEqual(3);
};
function listener2(e) {
expect(e.target).toEqual(obj2);
expect(this).toEqual(foo);
};
obj.addEventListener('test', listener1);
obj2.addEventListener('test', listener2, foo);
obj.fireEvent('test', {bar: 3});
});
it('should not call listeners removed through #removeEventListener', function() {
var obj = new Klass(),
spy = jasmine.createSpy();
obj.addEventListener('test', spy);
obj.removeEventListener('test', spy);
obj.fireEvent('test');
expect(spy).not.toHaveBeenCalled();
});
});
describe('#on, #off & #fire', function() {
it('should work like #addEventListener && #removeEventListener', function() {
var obj = new Klass(),
spy = jasmine.createSpy();
obj.on('test', spy);
obj.fire('test');
expect(spy).toHaveBeenCalled();
obj.off('test', spy);
obj.fireEvent('test');
expect(spy.callCount).toBeLessThan(2);
});
it('should not override existing methods with the same name', function() {
var spy1 = jasmine.createSpy(),
spy2 = jasmine.createSpy(),
spy3 = jasmine.createSpy();
var Klass2 = L.Class.extend({
includes: L.Mixin.Events,
on: spy1,
off: spy2,
fire: spy3
});
var obj = new Klass2();
obj.on();
expect(spy1).toHaveBeenCalled();
obj.off();
expect(spy2).toHaveBeenCalled();
obj.fire();
expect(spy3).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,63 @@
describe('Util', function() {
describe('#extend', function() {
var a;
beforeEach(function() {
a = {
foo: 5,
bar: 'asd'
};
});
it('should extend the first argument with the properties of the second', function() {
L.Util.extend(a, {
bar: 7,
baz: 3
});
expect(a).toEqual({
foo: 5,
bar: 7,
baz: 3
});
});
it('should work with more than 2 arguments', function() {
L.Util.extend(a, {bar: 7}, {baz: 3});
expect(a).toEqual({
foo: 5,
bar: 7,
baz: 3
});
});
});
describe('#bind', function() {
it('should return the given function with the given context', function() {
var fn = function() {
return this;
};
var fn2 = L.Util.bind(fn, 5);
expect(fn2()).toEqual(5);
});
});
describe('#stamp', function() {
it('should set a unique id on the given object and return it', function() {
var a = {},
id = L.Util.stamp(a);
expect(typeof id).toEqual('number');
expect(L.Util.stamp(a)).toEqual(id);
var b = {},
id2 = L.Util.stamp(b);
expect(id2).not.toEqual(id);
});
});
});

View File

@ -0,0 +1,102 @@
describe('DomEvent', function() {
var el;
function simulateClick(el) {
if (document.createEvent) {
var e = document.createEvent('MouseEvents');
e.initMouseEvent('click', true, true, window,
0, 0, 0, 0, 0, false, false, false, false, 0, null);
return el.dispatchEvent(e);
} else if (el.fireEvent) {
return el.fireEvent('onclick');
}
}
beforeEach(function() {
el = document.createElement('div');
el.style.position = 'absolute';
el.style.top = el.style.left = '-10000px';
document.body.appendChild(el);
});
afterEach(function() {
document.body.removeChild(el);
});
describe('#addListener', function() {
it('should add a listener and call it on event', function() {
var listener1 = jasmine.createSpy('listener1'),
listener2 = jasmine.createSpy('listener2');
L.DomEvent.addListener(el, 'click', listener1);
L.DomEvent.addListener(el, 'click', listener2);
simulateClick(el);
expect(listener1).toHaveBeenCalled();
expect(listener2).toHaveBeenCalled();
});
it('should have "this" keyword point to the given context', function() {
var obj = {foo: 'bar'},
result;
L.DomEvent.addListener(el, 'click', function() {
result = this;
}, obj);
simulateClick(el);
expect(result).toEqual(obj);
});
it('should pass an event object to the listener', function() {
var type;
L.DomEvent.addListener(el, 'click', function(e) {
type = e && e.type;
});
simulateClick(el);
expect(type).toEqual('click');
});
});
describe('#removeListener', function() {
it('should remove prevously added listener', function() {
var listener = jasmine.createSpy('listener');
L.DomEvent.addListener(el, 'click', listener);
L.DomEvent.removeListener(el, 'click', listener);
simulateClick(el);
expect(listener).not.toHaveBeenCalled();
});
});
describe('#stopPropagation', function() {
it('should stop propagation of the given event', function() {
var child = document.createElement('div'),
listener = jasmine.createSpy('listener');
el.appendChild(child);
L.DomEvent.addListener(child, 'click', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'click', listener);
simulateClick(child);
expect(listener).not.toHaveBeenCalled();
el.removeChild(child);
});
});
describe('#preventDefault', function() {
it('should prevent the default action of event', function() {
L.DomEvent.addListener(el, 'click', L.DomEvent.preventDefault);
expect(simulateClick(el)).toBe(false);
});
});
});

View File

@ -0,0 +1,29 @@
describe('DomUtil', function() {
var el;
beforeEach(function() {
el = document.createElement('div');
el.style.position = 'absolute';
el.style.top = el.style.left = '-10000px';
document.body.appendChild(el);
});
afterEach(function() {
document.body.removeChild(el);
});
describe('#get', function() {
it('should get element by id if the given argument is string', function() {
el.id = 'testId';
expect(L.DomUtil.get(el.id)).toBe(el);
});
it('should return the element if it is given as an argument', function() {
expect(L.DomUtil.get(el)).toBe(el);
});
});
describe('#setPosition', noSpecs);
describe('#getStyle', noSpecs);
});

View File

@ -0,0 +1 @@
describe('LatLngBounds', noSpecs);

View File

@ -0,0 +1,70 @@
describe('LatLng', function() {
describe('constructor', function() {
it("should set lat and lng", function() {
var a = new L.LatLng(25, 74);
expect(a.lat).toEqual(25);
expect(a.lng).toEqual(74);
var a = new L.LatLng(-25, -74);
expect(a.lat).toEqual(-25);
expect(a.lng).toEqual(-74);
});
it("should clamp latitude to lie between -90 and 90", function() {
var a = new L.LatLng(150, 0).lat;
expect(a).toEqual(90);
var b = new L.LatLng(-230, 0).lat;
expect(b).toEqual(-90);
});
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);
var c = new L.LatLng(0, 380).lng;
expect(c).toEqual(20);
var d = new L.LatLng(0, -190).lng;
expect(d).toEqual(170);
var e = new L.LatLng(0, -360).lng;
expect(e).toEqual(0);
var f = new L.LatLng(0, -380).lng;
expect(f).toEqual(-20);
});
it("should not clamp latitude and longtitude if unbounded flag set to true", function() {
var a = new L.LatLng(150, 0, true).lat;
expect(a).toEqual(150);
var b = new L.LatLng(-230, 0, true).lat;
expect(b).toEqual(-230);
var c = new L.LatLng(0, 250, true).lng;
expect(c).toEqual(250);
var d = new L.LatLng(0, -190, true).lng;
expect(d).toEqual(-190);
});
});
describe('#equals', function() {
it("should return true if compared objects are equal within a certain margin", function() {
var a = new L.LatLng(10, 20);
var b = new L.LatLng(10 + 1.0E-10, 20 - 1.0E-10);
expect(a.equals(b)).toBe(true);
});
it("should return false if compared objects are not equal within a certain margin", function() {
var a = new L.LatLng(10, 20);
var b = new L.LatLng(10, 23.3);
expect(a.equals(b)).toBe(false);
});
});
});

View File

@ -0,0 +1,42 @@
describe("Projection.Mercator", function() {
var p = L.Projection.Mercator;
beforeEach(function() {
function almostEqual(a, b, p) {
return Math.abs(a - b) <= (p || 1.0E-12);
};
this.addMatchers({
toAlmostEqual: function(expected, margin) {
var p1 = this.actual,
p2 = expected;
return almostEqual(p1.x, p2.x, margin) && almostEqual(p1.y, p2.y, margin);
}
});
});
describe("#project", function() {
it("should do projection properly", function() {
//edge cases
expect(p.project(new L.LatLng(0, 0))).toAlmostEqual(new L.Point(0, 0));
expect(p.project(new L.LatLng(90, 180))).toAlmostEqual(new L.Point(-Math.PI, Math.PI));
expect(p.project(new L.LatLng(-90, -180))).toAlmostEqual(new L.Point(-Math.PI, -Math.PI));
expect(p.project(new L.LatLng(50, 30))).toAlmostEqual(new L.Point(0.523598775598, 1.010683188683));
});
});
describe("#unproject", function() {
it("should do unprojection properly", function() {
function pr(point) {
return p.project(p.unproject(point));
}
expect(pr(new L.Point(0, 0))).toAlmostEqual(new L.Point(0, 0));
expect(pr(new L.Point(-Math.PI, Math.PI))).toAlmostEqual(new L.Point(-Math.PI, Math.PI));
expect(pr(new L.Point(-Math.PI, -Math.PI))).toAlmostEqual(new L.Point(-Math.PI, -Math.PI));
expect(pr(new L.Point(0.523598775598, 1.010683188683))).toAlmostEqual(new L.Point(0.523598775598, 1.010683188683));
});
});
});

View File

@ -0,0 +1,43 @@
describe('Bounds', function() {
var a, b;
beforeEach(function() {
a = new L.Bounds(
new L.Point(14, 12),
new L.Point(30, 40));
b = new L.Bounds([
new L.Point(20, 12),
new L.Point(14, 20),
new L.Point(30, 40)
]);
});
describe('constructor', function() {
it('should create bounds with proper min & max on (Point, Point)', function() {
expect(a.min).toEqual(new L.Point(14, 12));
expect(a.max).toEqual(new L.Point(30, 40));
});
it('should create bounds with proper min & max on (Point[])', function() {
expect(b.min).toEqual(new L.Point(14, 12));
expect(b.max).toEqual(new L.Point(30, 40));
});
});
describe('#extend', function() {
it('should extend the bounds to contain the given point', function() {
a.extend(new L.Point(50, 20));
expect(a.min).toEqual(new L.Point(14, 12));
expect(a.max).toEqual(new L.Point(50, 40));
b.extend(new L.Point(25, 50));
expect(b.min).toEqual(new L.Point(14, 12));
expect(b.max).toEqual(new L.Point(30, 50));
});
});
describe('#getCenter', function() {
it('should return the center point', function() {
expect(a.getCenter()).toEqual(new L.Point(22, 26));
});
});
});

View File

@ -0,0 +1,45 @@
describe("Point", function() {
describe('constructor', function() {
it("should create a point with the given x and y", function() {
var p = new L.Point(1.5, 2.5);
expect(p.x).toEqual(1.5);
expect(p.y).toEqual(2.5);
});
it("should round the given x and y if the third argument is true", function() {
var p = new L.Point(1.3, 2.7, true);
expect(p.x).toEqual(1);
expect(p.y).toEqual(3);
});
});
describe('#subtract', function() {
it('should subtract the given point from this one', function() {
var a = new L.Point(50, 30),
b = new L.Point(20, 10);
expect(a.subtract(b)).toEqual(new L.Point(30, 20));
});
});
describe('#add', function() {
it('should add the given point to this one', function() {
expect(new L.Point(50, 30).add(new L.Point(20, 10))).toEqual(new L.Point(70, 40));
});
});
describe('#divideBy', function() {
it('should divide this point by the given amount', function() {
expect(new L.Point(50, 30).divideBy(5)).toEqual(new L.Point(10, 6));
});
});
describe('#multiplyBy', function() {
it('should multiply this point by the given amount', function() {
expect(new L.Point(50, 30).multiplyBy(2)).toEqual(new L.Point(100, 60));
});
});
describe('#distanceTo', noSpecs);
});

View File

@ -0,0 +1,19 @@
describe("Transformation", function() {
var t, p;
beforeEach(function() {
t = new L.Transformation(1, 2, 3, 4);
p = new L.Point(10, 20);
});
it("#transform should perform a transformation", function() {
var p2 = t.transform(p, 2);
expect(p2).toEqual(new L.Point(24, 128));
});
it("#untransform should perform a reverse transformation", function() {
var p2 = t.transform(p, 2);
var p3 = t.untransform(p2, 2);
expect(p3).toEqual(p);
});
});

View File

@ -0,0 +1 @@
describe('TileLayer', noSpecs);

View File

@ -0,0 +1 @@
describe("Map", noSpecs);

View File

@ -0,0 +1,35 @@
/**
* @preserve Copyright (c) 2010-2011, CloudMade, Vladimir Agafonkin
* Leaflet is a BSD-licensed JavaScript library for map display and interaction.
* See http://cloudmade.github.com/Leaflet/ for more information.
*/
(function(root) {
var L = {
VERSION: '0.2',
ROOT_URL: (function() {
var scripts = document.getElementsByTagName('script'),
leafletRe = /^(.*\/)leaflet-?([\w-]*)\.js.*$/;
for (var i = 0, len = scripts.length; i < len; i++) {
var src = scripts[i].src,
res = src && src.match(leafletRe);
if (res) {
if (res[2] == 'include') break;
return res[1];
}
}
return '../../dist/';
})(),
noConflict: function() {
root.L = this._originalL;
return this;
},
_originalL: root.L
};
window.L = L;
}(this));

View File

@ -0,0 +1,55 @@
L.Control.Attribution = L.Class.extend({
onAdd: function(map) {
this._container = L.DomUtil.create('div', 'leaflet-control-attribution');
this._map = map;
this._prefix = 'Powered by <a href="http://leaflet.cloudmade.com">Leaflet</a>';
this._attributions = {};
this._update();
},
getPosition: function() {
return L.Control.Position.BOTTOM_RIGHT;
},
getContainer: function() {
return this._container;
},
setPrefix: function(prefix) {
this._prefix = prefix;
},
addAttribution: function(text) {
if (!text) return;
this._attributions[text] = true;
this._update();
},
removeAttribution: function(text) {
if (!text) return;
delete this._attributions[text];
this._update();
},
_update: function() {
if (!this._map) return;
var attribs = [];
for (var i in this._attributions) {
if (this._attributions.hasOwnProperty(i)) {
attribs.push(i);
}
}
var prefixAndAttribs = [];
if (this._prefix) {
prefixAndAttribs.push(this._prefix);
}
if (attribs.length) {
prefixAndAttribs.push(attribs.join(', '));
}
this._container.innerHTML = prefixAndAttribs.join(' &mdash; ');
}
});

View File

@ -0,0 +1,36 @@
L.Control.Zoom = L.Class.extend({
onAdd: function(map) {
this._map = map;
this._container = L.DomUtil.create('div', 'leaflet-control-zoom');
this._zoomInButton = this._createButton(
'Zoom in', 'leaflet-control-zoom-in', this._map.zoomIn, this._map);
this._zoomOutButton = this._createButton(
'Zoom out', 'leaflet-control-zoom-out', this._map.zoomOut, this._map);
this._container.appendChild(this._zoomInButton);
this._container.appendChild(this._zoomOutButton);
},
getContainer: function() {
return this._container;
},
getPosition: function() {
return L.Control.Position.TOP_LEFT;
},
_createButton: function(title, className, fn, context) {
var link = document.createElement('a');
link.href = '#';
link.title = title;
link.className = className;
L.DomEvent.disableClickPropagation(link);
L.DomEvent.addListener(link, 'click', L.DomEvent.preventDefault);
L.DomEvent.addListener(link, 'click', fn, context);
return link;
}
});

View File

@ -0,0 +1,9 @@
L.Control = {};
L.Control.Position = {
TOP_LEFT: 'topLeft',
TOP_RIGHT: 'topRight',
BOTTOM_LEFT: 'bottomLeft',
BOTTOM_RIGHT: 'bottomRight'
};

View File

@ -0,0 +1,23 @@
(function() {
var ua = navigator.userAgent.toLowerCase(),
ie = !!window.ActiveXObject,
webkit = ua.indexOf("webkit") != -1,
mobile = ua.indexOf("mobi") != -1,
android = ua.indexOf("android") != -1,
opera = window.opera;
L.Browser = {
ie: ie,
ie6: ie && !window.XMLHttpRequest,
webkit: webkit,
webkit3d: webkit && ('WebKitCSSMatrix' in window) && ('m11' in new WebKitCSSMatrix()),
mobileWebkit: webkit && (mobile || android),
mobileOpera: mobile && opera,
gecko: ua.indexOf("gecko") != -1,
android: android
};
//TODO replace ugly ua sniffing with feature detection
L.Browser.touch = L.Browser.mobileWebkit || L.Browser.mobileOpera;
})();

View File

@ -0,0 +1,66 @@
/*
* Class powers the OOP facilities of the library. Thanks to John Resig and Dean Edwards for inspiration!
*/
L.Class = function() {};
L.Class.extend = function(/*Object*/ props) /*-> Class*/ {
// extended class with the new prototype
var NewClass = function() {
if (!L.Class._prototyping && this.initialize) {
this.initialize.apply(this, arguments);
}
};
// instantiate class without calling constructor
L.Class._prototyping = true;
var proto = new this();
L.Class._prototyping = false;
proto.constructor = NewClass;
NewClass.prototype = proto;
// add superclass access
proto.superclass = this.prototype;
// add class name
//proto.className = props;
// mix static properties into the class
if (props.statics) {
L.Util.extend(NewClass, props.statics);
delete props.statics;
}
// mix includes into the prototype
if (props.includes) {
L.Util.extend.apply(null, [proto].concat(props.includes));
delete props.includes;
}
// merge options
if (props.options && proto.options) {
props.options = L.Util.extend({}, proto.options, props.options);
}
// mix given properties into the prototype
L.Util.extend(proto, props);
// allow inheriting further
NewClass.extend = arguments.callee;
// method for adding properties to prototype
NewClass.include = function(props) {
L.Util.extend(this.prototype, props);
};
//inherit parent's statics
for (var i in this) {
if (this.hasOwnProperty(i) && i != 'prototype') {
NewClass[i] = this[i];
}
}
return NewClass;
};

View File

@ -0,0 +1,58 @@
/*
* L.Mixin.Events adds custom events functionality to Leaflet classes
*/
L.Mixin = {};
L.Mixin.Events = {
addEventListener: function(/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
var events = this._leaflet_events = this._leaflet_events || {};
events[type] = events[type] || [];
events[type].push({
action: fn,
context: context
});
return this;
},
hasEventListeners: function(/*String*/ type) /*-> Boolean*/ {
var k = '_leaflet_events';
return (k in this) && (type in this[k]) && (this[k][type].length > 0);
},
removeEventListener: function(/*String*/ type, /*Function*/ fn, /*(optional) Object*/ context) {
if (!this.hasEventListeners(type)) { return this; }
for (var i = 0, events = this._leaflet_events, len = events[type].length; i < len; i++) {
if (
(events[type][i].action === fn) &&
(!context || (events[type][i].context === context))
) {
events[type].splice(i, 1);
return this;
}
}
return this;
},
fireEvent: function(/*String*/ type, /*(optional) Object*/ data) {
if (!this.hasEventListeners(type)) { return; }
var event = L.Util.extend({
type: type,
target: this
}, data);
var listeners = this._leaflet_events[type].slice();
for (var i = 0, len = listeners.length; i < len; i++) {
listeners[i].action.call(listeners[i].context || this, event);
}
return this;
}
};
L.Mixin.Events.on = L.Mixin.Events.addEventListener;
L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
L.Mixin.Events.fire = L.Mixin.Events.fireEvent;

View File

@ -0,0 +1,96 @@
/*
* L.Util is a namespace for various utility functions.
*/
L.Util = {
extend: function(/*Object*/ dest) /*-> Object*/ { // merge src properties into dest
var sources = Array.prototype.slice.call(arguments, 1);
for (var j = 0, len = sources.length, src; j < len; j++) {
src = sources[j] || {};
for (var i in src) {
if (src.hasOwnProperty(i)) {
dest[i] = src[i];
}
}
}
return dest;
},
bind: function(/*Function*/ fn, /*Object*/ obj) /*-> Object*/ {
return function() {
return fn.apply(obj, arguments);
};
},
stamp: (function() {
var lastId = 0, key = '_leaflet_id';
return function(/*Object*/ obj) {
obj[key] = obj[key] || ++lastId;
return obj[key];
};
})(),
requestAnimFrame: (function() {
function timeoutDefer(callback) {
window.setTimeout(callback, 1000 / 60);
}
var requestFn = window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
timeoutDefer;
return function(callback, context, immediate) {
callback = context ? L.Util.bind(callback, context) : context;
if (immediate && requestFn === timeoutDefer) {
callback();
} else {
requestFn(callback);
}
};
})(),
limitExecByInterval: function(fn, time, context) {
var lock, execOnUnlock, args;
function exec(){
lock = false;
if (execOnUnlock) {
args.callee.apply(context, args);
execOnUnlock = false;
}
}
return function() {
args = arguments;
if (!lock) {
lock = true;
setTimeout(exec, time);
fn.apply(context, args);
} else {
execOnUnlock = true;
}
};
},
falseFn: function() { return false; },
formatNum: function(num, digits) {
var pow = Math.pow(10, digits || 5);
return Math.round(num * pow) / pow;
},
setOptions: function(obj, options) {
obj.options = L.Util.extend({}, obj.options, options);
},
getParamString: function(obj) {
var params = [];
for (var i in obj) {
if (obj.hasOwnProperty(i)) {
params.push(i + '=' + obj[i]);
}
}
return '?' + params.join('&');
}
};

View File

@ -0,0 +1,41 @@
L.Util.extend(L.DomEvent, {
// inspired by Zepto touch code by Thomas Fuchs
addDoubleTapListener: function(obj, handler, id) {
var last,
doubleTap = false,
delay = 250,
touch,
pre = '_leaflet_',
touchstart = 'touchstart',
touchend = 'touchend';
function onTouchStart(e) {
if (e.touches.length != 1) return;
var now = Date.now(),
delta = now - (last || now);
touch = e.touches[0];
doubleTap = (delta > 0 && delta <= delay);
last = now;
}
function onTouchEnd(e) {
if (doubleTap) {
touch.type = 'dblclick';
handler(touch);
last = null;
}
}
obj[pre + touchstart + id] = onTouchStart;
obj[pre + touchend + id] = onTouchEnd;
obj.addEventListener(touchstart, onTouchStart, false);
obj.addEventListener(touchend, onTouchEnd, false);
},
removeDoubleTapListener: function(obj, id) {
var pre = '_leaflet_';
obj.removeEventListener(obj, obj[pre + 'touchstart' + id], false);
obj.removeEventListener(obj, obj[pre + 'touchend' + id], false);
}
});

View File

@ -0,0 +1,132 @@
/*
* L.DomEvent contains functions for working with DOM events.
*/
L.DomEvent = {
/* inpired by John Resig, Dean Edwards and YUI addEvent implementations */
addListener: function(/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn, /*Object*/ context) {
var id = L.Util.stamp(fn);
function handler(e) {
return fn.call(context || obj, e || L.DomEvent._getEvent());
}
if (L.Browser.touch && (type == 'dblclick') && this.addDoubleTapListener) {
this.addDoubleTapListener(obj, handler, id);
} else if ('addEventListener' in obj) {
if (type == 'mousewheel') {
obj.addEventListener('DOMMouseScroll', handler, false);
obj.addEventListener(type, handler, false);
} else if ((type == 'mouseenter') || (type == 'mouseleave')) {
var originalHandler = handler,
newType = (type == 'mouseenter' ? 'mouseover' : 'mouseout');
handler = function(e) {
if (!L.DomEvent._checkMouse(obj, e)) return;
return originalHandler(e);
};
obj.addEventListener(newType, handler, false);
} else {
obj.addEventListener(type, handler, false);
}
} else if ('attachEvent' in obj) {
obj.attachEvent("on" + type, handler);
}
obj['_leaflet_' + type + id] = handler;
},
removeListener: function(/*HTMLElement*/ obj, /*String*/ type, /*Function*/ fn) {
var id = L.Util.stamp(fn),
key = '_leaflet_' + type + id;
handler = obj[key];
if (L.Browser.mobileWebkit && (type == 'dblclick') && this.removeDoubleTapListener) {
this.removeDoubleTapListener(obj, id);
} else if ('removeEventListener' in obj) {
if (type == 'mousewheel') {
obj.removeEventListener('DOMMouseScroll', handler, false);
obj.removeEventListener(type, handler, false);
} else if ((type == 'mouseenter') || (type == 'mouseleave')) {
obj.removeEventListener((type == 'mouseenter' ? 'mouseover' : 'mouseout'), handler, false);
} else {
obj.removeEventListener(type, handler, false);
}
} else if ('detachEvent' in obj) {
obj.detachEvent("on" + type, handler);
}
obj[key] = null;
},
_checkMouse: function(el, e) {
var related = e.relatedTarget;
if (!related) return true;
try {
while (related && (related != el)) {
related = related.parentNode;
}
} catch(err) { return false; }
return (related != el);
},
_getEvent: function()/*->Event*/ {
var e = window.event;
if (!e) {
var caller = arguments.callee.caller;
while (caller) {
e = caller['arguments'][0];
if (e && Event == e.constructor) { break; }
caller = caller.caller;
}
}
return e;
},
stopPropagation: function(/*Event*/ e) {
if (e.stopPropagation) {
e.stopPropagation();
} else {
e.cancelBubble = true;
}
},
disableClickPropagation: function(/*HTMLElement*/ el) {
L.DomEvent.addListener(el, 'mousedown', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'click', L.DomEvent.stopPropagation);
L.DomEvent.addListener(el, 'dblclick', L.DomEvent.stopPropagation);
},
preventDefault: function(/*Event*/ e) {
if (e.preventDefault) {
e.preventDefault();
} else {
e.returnValue = false;
}
},
stop: function(e) {
L.DomEvent.preventDefault(e);
L.DomEvent.stopPropagation(e);
},
getMousePosition: function(e, container) {
var x = e.pageX ? e.pageX : e.clientX +
document.body.scrollLeft + document.documentElement.scrollLeft,
y = e.pageY ? e.pageY : e.clientY +
document.body.scrollTop + document.documentElement.scrollTop,
pos = new L.Point(x, y);
return (container ?
pos.subtract(L.DomUtil.getCumulativeOffset(container)) : pos);
},
getWheelDelta: function(e) {
var delta = 0;
if (e.wheelDelta) { delta = e.wheelDelta/120; }
if (e.detail) { delta = -e.detail/3; }
return delta;
}
};

View File

@ -0,0 +1,124 @@
/*
* L.DomUtil contains various utility functions for working with DOM
*/
L.DomUtil = {
get: function(id) {
return (typeof id == 'string' ? document.getElementById(id) : id);
},
getStyle: function(el, style) {
var value = el.style[style];
if (!value && el.currentStyle) {
value = el.currentStyle[style];
}
if (!value || value == 'auto') {
var css = document.defaultView.getComputedStyle(el, null);
value = css ? css[style] : null;
}
return (value == 'auto' ? null : value);
},
getCumulativeOffset: function(el) {
var top = 0,
left = 0;
do {
top += el.offsetTop || 0;
left += el.offsetLeft || 0;
el = el.offsetParent;
} while (el);
return new L.Point(left, top);
},
create: function(tagName, className, container) {
var el = document.createElement(tagName);
el.className = className;
if (container) {
container.appendChild(el);
}
return el;
},
disableTextSelection: function() {
if (document.selection && document.selection.empty) {
document.selection.empty();
}
if (!this._onselectstart) {
this._onselectstart = document.onselectstart;
document.onselectstart = L.Util.falseFn;
}
},
enableTextSelection: function() {
document.onselectstart = this._onselectstart;
this._onselectstart = null;
},
CLASS_RE: /(\\s|^)'+cls+'(\\s|$)/,
hasClass: function(el, name) {
return (el.className.length > 0) &&
new RegExp("(^|\\s)" + name + "(\\s|$)").test(el.className);
},
addClass: function(el, name) {
if (!L.DomUtil.hasClass(el, name)) {
el.className += (el.className ? ' ' : '') + name;
}
},
setOpacity: function(el, value) {
if (L.Browser.ie) {
el.style.filter = 'alpha(opacity=' + Math.round(value * 100) + ')';
} else {
el.style.opacity = value;
}
},
//TODO refactor away this ugly translate/position mess
testProp: function(props) {
var style = document.documentElement.style;
for (var i = 0; i < props.length; i++) {
if (props[i] in style) {
return props[i];
}
}
return false;
},
getTranslateString: function(point) {
return L.DomUtil.TRANSLATE_OPEN +
point.x + 'px,' + point.y + 'px' +
L.DomUtil.TRANSLATE_CLOSE;
},
getScaleString: function(scale, origin) {
return L.DomUtil.getTranslateString(origin) +
' scale(' + scale + ') ' +
L.DomUtil.getTranslateString(origin.multiplyBy(-1));
},
setPosition: function(el, point) {
el._leaflet_pos = point;
if (L.Browser.webkit) {
el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
} else {
el.style.left = point.x + 'px';
el.style.top = point.y + 'px';
}
},
getPosition: function(el) {
return el._leaflet_pos;
}
};
L.Util.extend(L.DomUtil, {
TRANSITION: L.DomUtil.testProp(['transition', 'webkitTransition', 'OTransition', 'MozTransition', 'msTransition']),
TRANSFORM: L.DomUtil.testProp(['transformProperty', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']),
TRANSLATE_OPEN: 'translate' + (L.Browser.webkit3d ? '3d(' : '('),
TRANSLATE_CLOSE: L.Browser.webkit3d ? ',0)' : ')'
});

View File

@ -0,0 +1,129 @@
/*
* L.Draggable allows you to add dragging capabilities to any element. Supports mobile devices too.
*/
L.Draggable = L.Class.extend({
includes: L.Mixin.Events,
statics: {
START: L.Browser.touch ? 'touchstart' : 'mousedown',
END: L.Browser.touch ? 'touchend' : 'mouseup',
MOVE: L.Browser.touch ? 'touchmove' : 'mousemove',
TAP_TOLERANCE: 15
},
initialize: function(element, dragStartTarget) {
this._element = element;
this._dragStartTarget = dragStartTarget || element;
},
enable: function() {
if (this._enabled) { return; }
L.DomEvent.addListener(this._dragStartTarget, L.Draggable.START, this._onDown, this);
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
L.DomEvent.removeListener(this._dragStartTarget, L.Draggable.START, this._onDown);
this._enabled = false;
},
_onDown: function(e) {
if (e.shiftKey || ((e.which != 1) && (e.button != 1) && !e.touches)) { return; }
if (e.touches && e.touches.length > 1) { return; }
var first = (e.touches && e.touches.length == 1 ? e.touches[0] : e);
L.DomEvent.preventDefault(e);
if (L.Browser.mobileWebkit) {
first.target.className += ' leaflet-active';
}
this._moved = false;
L.DomUtil.disableTextSelection();
this._setMovingCursor();
this._startPos = this._newPos = L.DomUtil.getPosition(this._element);
this._startPoint = new L.Point(first.clientX, first.clientY);
L.DomEvent.addListener(document, L.Draggable.MOVE, this._onMove, this);
L.DomEvent.addListener(document, L.Draggable.END, this._onUp, this);
},
_onMove: function(e) {
if (e.touches && e.touches.length > 1) { return; }
L.DomEvent.preventDefault(e);
var first = (e.touches && e.touches.length == 1 ? e.touches[0] : e);
if (!this._moved) {
this.fire('dragstart');
this._moved = true;
}
var newPoint = new L.Point(first.clientX, first.clientY);
this._newPos = this._startPos.add(newPoint).subtract(this._startPoint);
L.Util.requestAnimFrame(this._updatePosition, this, true);
this.fire('drag');
},
_updatePosition: function() {
L.DomUtil.setPosition(this._element, this._newPos);
},
_onUp: function(e) {
if (e.changedTouches) {
var first = e.changedTouches[0],
el = first.target,
dist = (this._newPos && this._newPos.distanceTo(this._startPos)) || 0;
el.className = el.className.replace(' leaflet-active', '');
if (dist < L.Draggable.TAP_TOLERANCE) {
this._simulateEvent('click', first);
}
}
L.DomUtil.enableTextSelection();
this._restoreCursor();
L.DomEvent.removeListener(document, L.Draggable.MOVE, this._onMove);
L.DomEvent.removeListener(document, L.Draggable.END, this._onUp);
if (this._moved) {
this.fire('dragend');
}
},
_removeActiveClass: function(el) {
},
_setMovingCursor: function() {
this._bodyCursor = document.body.style.cursor;
document.body.style.cursor = 'move';
},
_restoreCursor: function() {
document.body.style.cursor = this._bodyCursor;
},
_simulateEvent: function(type, e) {
var simulatedEvent = document.createEvent('MouseEvent');
simulatedEvent.initMouseEvent(
type, true, true, window, 1,
e.screenX, e.screenY,
e.clientX, e.clientY,
false, false, false, false, 0, null);
e.target.dispatchEvent(simulatedEvent);
}
});

View File

@ -0,0 +1,89 @@
/*
* L.Transition native implementation that powers Leaflet animation
* in browsers that support CSS3 Transitions
*/
L.Transition = L.Transition.extend({
statics: (function() {
var transition = L.DomUtil.TRANSITION,
transitionEnd = (transition == 'webkitTransition' || transition == 'OTransition' ?
transition + 'End' : 'transitionend');
return {
NATIVE: !!transition,
TRANSITION: transition,
PROPERTY: transition + 'Property',
DURATION: transition + 'Duration',
EASING: transition + 'TimingFunction',
END: transitionEnd,
// transition-property value to use with each particular custom property
CUSTOM_PROPS_PROPERTIES: {
position: L.Browser.webkit ? L.DomUtil.TRANSFORM : 'top, left'
}
};
})(),
options: {
fakeStepInterval: 100
},
initialize: function(/*HTMLElement*/ el, /*Object*/ options) {
this._el = el;
L.Util.setOptions(this, options);
L.DomEvent.addListener(el, L.Transition.END, this._onTransitionEnd, this);
this._onFakeStep = L.Util.bind(this._onFakeStep, this);
},
run: function(/*Object*/ props) {
var prop,
propsList = [],
customProp = L.Transition.CUSTOM_PROPS_PROPERTIES;
for (prop in props) {
if (props.hasOwnProperty(prop)) {
prop = customProp[prop] ? customProp[prop] : prop;
prop = prop.replace(/([A-Z])/g, function(w) { return '-' + w.toLowerCase(); });
propsList.push(prop);
}
}
this._el.style[L.Transition.DURATION] = this.options.duration + 's';
this._el.style[L.Transition.EASING] = this.options.easing;
this._el.style[L.Transition.PROPERTY] = propsList.join(', ');
for (prop in props) {
if (props.hasOwnProperty(prop)) {
this._setProperty(prop, props[prop]);
}
}
this._inProgress = true;
this.fire('start');
if (L.Transition.NATIVE) {
this._timer = setInterval(this._onFakeStep, this.options.fakeStepInterval);
} else {
this._onTransitionEnd();
}
},
_onFakeStep: function() {
this.fire('step');
},
_onTransitionEnd: function() {
if (this._inProgress) {
this._inProgress = false;
clearInterval(this._timer);
this._el.style[L.Transition.PROPERTY] = 'none';
this.fire('step');
this.fire('end');
}
}
});

View File

@ -0,0 +1,124 @@
/*
* L.Transition fallback implementation that powers Leaflet animation
* in browsers that don't support CSS3 Transitions
*/
L.Transition = L.Transition.NATIVE ? L.Transition : L.Transition.extend({
statics: {
getTime: Date.now || function() { return +new Date(); },
TIMER: true,
EASINGS: {
'ease': [0.25, 0.1, 0.25, 1.0],
'linear': [0.0, 0.0, 1.0, 1.0],
'ease-in': [0.42, 0, 1.0, 1.0],
'ease-out': [0, 0, 0.58, 1.0],
'ease-in-out': [0.42, 0, 0.58, 1.0]
},
CUSTOM_PROPS_GETTERS: {
position: L.DomUtil.getPosition
},
//used to get units from strings like "10.5px" (->px)
UNIT_RE: /^[\d\.]+(\D*)$/
},
options: {
fps: 50
},
initialize: function(el, options) {
this._el = el;
L.Util.extend(this.options, options);
var easings = L.Transition.EASINGS[this.options.easing] || L.Transition.EASINGS['ease'];
this._p1 = new L.Point(0, 0);
this._p2 = new L.Point(easings[0], easings[1]);
this._p3 = new L.Point(easings[2], easings[3]);
this._p4 = new L.Point(1, 1);
this._step = L.Util.bind(this._step, this);
this._interval = Math.round(1000 / this.options.fps);
},
run: function(props) {
this._props = {};
var getters = L.Transition.CUSTOM_PROPS_GETTERS,
re = L.Transition.UNIT_RE;
this.fire('start');
for (var prop in props) {
if (props.hasOwnProperty(prop)) {
var p = {};
if (prop in getters) {
p.from = getters[prop](this._el);
} else {
var matches = this._el.style[prop].match(re);
p.from = parseFloat(matches[0]);
p.unit = matches[1];
}
p.to = props[prop];
this._props[prop] = p;
}
}
clearInterval(this._timer);
this._timer = setInterval(this._step, this._interval);
this._startTime = L.Transition.getTime();
},
_step: function() {
var time = L.Transition.getTime(),
elapsed = time - this._startTime,
duration = this.options.duration * 1000;
if (elapsed < duration) {
this._runFrame(this._cubicBezier(elapsed / duration));
} else {
this._runFrame(1);
this._complete();
}
},
_runFrame: function(percentComplete) {
var setters = L.Transition.CUSTOM_PROPS_SETTERS,
prop, p, value;
for (prop in this._props) {
if (this._props.hasOwnProperty(prop)) {
p = this._props[prop];
if (prop in setters) {
value = p.to.subtract(p.from).multiplyBy(percentComplete).add(p.from);
setters[prop](this._el, value);
} else {
this._el.style[prop] =
((p.to - p.from) * percentComplete + p.from) + p.unit;
}
}
}
this.fire('step');
},
_complete: function() {
clearInterval(this._timer);
this.fire('end');
},
_cubicBezier: function(t) {
var a = Math.pow(1 - t, 3),
b = 3 * Math.pow(1 - t, 2) * t,
c = 3 * (1 - t) * Math.pow(t, 2),
d = Math.pow(t, 3),
p1 = this._p1.multiplyBy(a),
p2 = this._p2.multiplyBy(b),
p3 = this._p3.multiplyBy(c),
p4 = this._p4.multiplyBy(d);
return p1.add(p2).add(p3).add(p4).y;
}
});

View File

@ -0,0 +1,28 @@
L.Transition = L.Class.extend({
includes: L.Mixin.Events,
statics: {
CUSTOM_PROPS_SETTERS: {
position: L.DomUtil.setPosition
//TODO transform custom attr
},
implemented: function() {
return L.Transition.NATIVE || L.Transition.TIMER;
}
},
options: {
easing: 'ease',
duration: 0.5
},
_setProperty: function(prop, value) {
var setters = L.Transition.CUSTOM_PROPS_SETTERS;
if (prop in setters) {
setters[prop](this._el, value);
} else {
this._el.style[prop] = value;
}
}
});

View File

@ -0,0 +1,35 @@
/*
CM.LatLng represents a geographical point with latitude and longtitude coordinates.
*/
L.LatLng = function(/*Number*/ lat, /*Number*/ lng, /*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
}
//TODO change to lat() & lng()
this.lat = lat;
this.lng = lng;
};
L.Util.extend(L.LatLng, {
DEG_TO_RAD: Math.PI / 180,
RAD_TO_DEG: 180 / Math.PI,
MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
});
L.LatLng.prototype = {
equals: function(/*LatLng*/ obj) {
if (!(obj instanceof L.LatLng)) { return false; }
var margin = Math.max(Math.abs(this.lat - obj.lat), Math.abs(this.lng - obj.lng));
return margin <= L.LatLng.MAX_MARGIN;
},
toString: function() {
return 'LatLng(' +
L.Util.formatNum(this.lat) + ', ' +
L.Util.formatNum(this.lng) + ')';
}
};

View File

@ -0,0 +1,62 @@
/*
* L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
*/
L.LatLngBounds = L.Class.extend({
initialize: function(southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
if (!southWest) return;
var latlngs = (southWest instanceof Array ? southWest : [southWest, northEast]);
for (var i = 0, len = latlngs.length; i < len; i++) {
this.extend(latlngs[i]);
}
},
// 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);
} else {
this._southWest.lat = Math.min(latlng.lat, this._southWest.lat);
this._southWest.lng = Math.min(latlng.lng, this._southWest.lng);
this._northEast.lat = Math.max(latlng.lat, this._northEast.lat);
this._northEast.lng = Math.max(latlng.lng, this._northEast.lng);
}
},
getCenter: function() /*-> LatLng*/ {
return new L.LatLng(
(this._southWest.lat + this._northEast.lat) / 2,
(this._southWest.lng + this._northEast.lng) / 2);
},
getSouthWest: function() { return this._southWest; },
getNorthEast: function() { return this._northEast; },
getNorthWest: function() {
return new L.LatLng(this._northEast.lat, this._southWest.lng);
},
getSouthEast: function() {
return new L.LatLng(this._southWest.lat, this._northEast.lng);
},
contains: function(/*LatLngBounds or LatLng*/ obj) /*-> Boolean*/ {
var sw = this._southWest,
ne = this._northEast,
sw2, ne2;
if (obj instanceof L.LatLngBounds) {
sw2 = obj.getSouthWest();
ne2 = obj.getNorthEast();
} else {
sw2 = ne2 = obj;
}
return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
(sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
}
});
//TODO International date line?

View File

@ -0,0 +1,13 @@
L.CRS.EPSG3395 = L.Util.extend({}, L.CRS, {
code: 'EPSG:3395',
projection: L.Projection.Mercator,
transformation: (function() {
var m = L.Projection.Mercator,
r = m.R_MAJOR,
r2 = m.R_MINOR;
return new L.Transformation(0.5/(Math.PI * r), 0.5, -0.5/(Math.PI * r2), 0.5);
})()
});

View File

@ -0,0 +1,17 @@
L.CRS.EPSG3857 = L.Util.extend({}, L.CRS, {
code: 'EPSG:3857',
projection: L.Projection.SphericalMercator,
transformation: new L.Transformation(0.5/Math.PI, 0.5, -0.5/Math.PI, 0.5),
project: function(/*LatLng*/ latlng)/*-> Point*/ {
var projectedPoint = this.projection.project(latlng),
earthRadius = 6378137;
return projectedPoint.multiplyBy(earthRadius);
}
});
L.CRS.EPSG900913 = L.Util.extend({}, L.CRS.EPSG3857, {
code: 'EPSG:900913'
});

View File

@ -0,0 +1,7 @@
L.CRS.EPSG4326 = L.Util.extend({}, L.CRS, {
code: 'EPSG:4326',
projection: L.Projection.LonLat,
transformation: new L.Transformation(1/360, 0.5, -1/360, 0.5)
});

View File

@ -0,0 +1,17 @@
L.CRS = {
latLngToPoint: function(/*LatLng*/ latlng, /*Number*/ scale)/*-> Point*/ {
var projectedPoint = this.projection.project(latlng);
return this.transformation._transform(projectedPoint, scale);
},
pointToLatLng: function(/*Point*/ point, /*Number*/ scale, /*(optional) Boolean*/ unbounded)/*-> LatLng*/ {
var untransformedPoint = this.transformation.untransform(point, scale);
return this.projection.unproject(untransformedPoint, unbounded);
//TODO get rid of 'unbounded' everywhere
},
project: function(latlng) {
return this.projection.project(latlng);
}
};

View File

@ -0,0 +1,10 @@
L.Projection.LonLat = {
project: function(latlng) {
return new L.Point(latlng.lng, latlng.lat);
},
unproject: function(point, unbounded) {
return new L.LatLng(point.y, point.x, unbounded);
}
};

View File

@ -0,0 +1,49 @@
L.Projection.Mercator = {
MAX_LATITUDE: 85.0840591556,
R_MINOR: 6356752.3142,
R_MAJOR: 6378137,
project: function(/*LatLng*/ latlng) /*-> Point*/ {
var d = L.LatLng.DEG_TO_RAD,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
r = this.R_MAJOR,
x = latlng.lng * d * r,
y = lat * d,
tmp = this.R_MINOR / r,
eccent = Math.sqrt(1.0 - tmp * tmp),
con = eccent * Math.sin(y);
con = Math.pow((1 - con)/(1 + con), eccent * 0.5);
var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
y = -r * Math.log(ts);
return new L.Point(x, y);
},
unproject: function(/*Point*/ point, /*Boolean*/ unbounded) /*-> LatLng*/ {
var d = L.LatLng.RAD_TO_DEG,
r = this.R_MAJOR,
lng = point.x * d / r,
tmp = this.R_MINOR / r,
eccent = Math.sqrt(1 - (tmp * tmp)),
ts = Math.exp(- point.y / r),
phi = Math.PI/2 - 2 * Math.atan(ts),
numIter = 15,
tol = 1e-7,
i = numIter,
dphi = 0.1,
con;
while ((Math.abs(dphi) > tol) && (--i > 0)) {
con = eccent * Math.sin(phi);
dphi = Math.PI/2 - 2 * Math.atan(ts * Math.pow((1.0 - con)/(1.0 + con), 0.5 * eccent)) - phi;
phi += dphi;
}
return new L.LatLng(phi * d, lng, unbounded);
}
};

View File

@ -0,0 +1,23 @@
L.Projection.SphericalMercator = {
MAX_LATITUDE: 85.0511287798,
project: function(/*LatLng*/ latlng) /*-> Point*/ {
var d = L.LatLng.DEG_TO_RAD,
max = this.MAX_LATITUDE,
lat = Math.max(Math.min(max, latlng.lat), -max),
x = latlng.lng * d,
y = lat * d;
y = Math.log(Math.tan(Math.PI/4 + y/2));
return new L.Point(x, y);
},
unproject: function(/*Point*/ point, /*Boolean*/ unbounded) /*-> LatLng*/ {
var d = L.LatLng.RAD_TO_DEG,
lng = point.x * d,
lat = (2 * Math.atan(Math.exp(point.y)) - Math.PI/2) * d;
return new L.LatLng(lat, lng, unbounded);
}
};

View File

@ -0,0 +1,5 @@
/*
* L.Projection contains various geographical projections used by CRS classes.
*/
L.Projection = {};

View File

@ -0,0 +1,48 @@
/*
* L.Bounds represents a rectangular area on the screen in pixel coordinates.
*/
L.Bounds = L.Class.extend({
initialize: function(min, max) { //(Point, Point) or Point[]
if (!min) return;
var points = (min instanceof Array ? min : [min, max]);
for (var i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
},
// extend the bounds to contain the given point
extend: function(/*Point*/ point) {
if (!this.min && !this.max) {
this.min = new L.Point(point.x, point.y);
this.max = new L.Point(point.x, point.y);
} else {
this.min.x = Math.min(point.x, this.min.x);
this.max.x = Math.max(point.x, this.max.x);
this.min.y = Math.min(point.y, this.min.y);
this.max.y = Math.max(point.y, this.max.y);
}
},
getCenter: function(round)/*->Point*/ {
return new L.Point(
(this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round);
},
contains: function(/*Bounds or Point*/ obj)/*->Boolean*/ {
var min, max;
if (obj instanceof L.Bounds) {
min = obj.min;
max = obj.max;
} else {
max = max = obj;
}
return (min.x >= this.min.x) &&
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
}
});

View File

@ -0,0 +1,159 @@
/*
* L.LineUtil contains different utility functions for line segments
* and polylines (clipping, simplification, distances, etc.)
*/
L.LineUtil = {
/*
* Simplify polyline with vertex reduction and Douglas-Peucker simplification.
* Improves rendering performance dramatically by lessening the number of points to draw.
*/
simplify: function(/*Point[]*/ points, /*Number*/ tolerance) {
if (!tolerance) return points.slice();
// stage 1: vertex reduction
points = this.reducePoints(points, tolerance);
// stage 2: Douglas-Peucker simplification
points = this.simplifyDP(points, tolerance);
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));
},
// Douglas-Peucker simplification, see http://en.wikipedia.org/wiki/Douglas-Peucker_algorithm
simplifyDP: function(points, tol) {
var maxDist2 = 0,
index = 0,
t2 = tol * tol;
for (var i = 1, len = points.length, dist2; i < len - 1; i++) {
dist2 = this._sqPointToSegmentDist(points[i], points[0], points[len - 1]);
if (dist2 > maxDist2) {
index = i;
maxDist2 = dist2;
}
}
if (maxDist2 >= t2) {
var part1 = points.slice(0, index),
part2 = points.slice(index),
simplifiedPart1 = this.simplifyDP(part1, tol).slice(0, len - 2),
simplifiedPart2 = this.simplifyDP(part2, tol);
return simplifiedPart1.concat(simplifiedPart2);
} else {
return [points[0], points[len - 1]];
}
},
// reduce points that are too close to each other to a single point
reducePoints: function(points, tol) {
var reducedPoints = [points[0]],
t2 = tol * tol;
for (var i = 1, prev = 0, len = points.length; i < len; i++) {
if (this._sqDist(points[i], points[prev]) < t2) continue;
reducedPoints.push(points[i]);
prev = i;
}
if (prev < len - 1) {
reducedPoints.push(points[len - 1]);
}
return reducedPoints;
},
/*
* Cohen-Sutherland line clipping algorithm.
* Used to avoid rendering parts of a polyline that are not currently visible.
*/
clipSegment: function(a, b, bounds, useLastCode) {
var min = bounds.min,
max = bounds.max;
var codeA = useLastCode ? this._lastCode : this._getBitCode(a, bounds),
codeB = this._getBitCode(b, bounds);
// save 2nd code to avoid calculating it on the next segment
this._lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
// if a,b is outside the clip window (trivial reject)
} else if (codeA & codeB) {
return false;
// other cases
} else {
var codeOut = codeA || codeB,
p = this._getEdgeIntersection(a, b, codeOut, bounds),
newCode = this._getBitCode(p, bounds);
if (codeOut == codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
},
_getEdgeIntersection: function(a, b, code, bounds) {
var dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max;
if (code & 8) { // top
return new L.Point(a.x + dx * (max.y - a.y) / dy, max.y);
} else if (code & 4) { // bottom
return new L.Point(a.x + dx * (min.y - a.y) / dy, min.y);
} else if (code & 2){ // right
return new L.Point(max.x, a.y + dy * (max.x - a.x) / dx);
} else if (code & 1) { // left
return new L.Point(min.x, a.y + dy * (min.x - a.x) / dx);
}
},
_getBitCode: function(/*Point*/ p, bounds) {
var code = 0;
if (p.x < bounds.min.x) code |= 1; // left
else if (p.x > bounds.max.x) code |= 2; // right
if (p.y < bounds.min.y) code |= 4; // bottom
else if (p.y > bounds.max.y) code |= 8; // top
return code;
},
// square distance (to avoid unnecessary Math.sqrt calls)
_sqDist: function(p1, p2) {
var dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
},
// square distance from point to a segment
_sqPointToSegmentDist: function(p, p1, p2) {
var x2 = p2.x - p1.x,
y2 = p2.y - p1.y;
if (!x2 && !y2) return this._sqDist(p, p1);
var dot = (p.x - p1.x) * x2 + (p.y - p1.y) * y2,
t = dot / this._sqDist(p1, p2);
if (t < 0) return this._sqDist(p, p1);
if (t > 1) return this._sqDist(p, p2);
var proj = new L.Point(p1.x + x2 * t, p1.y + y2 * t);
return this._sqDist(p, proj);
}
};

View File

@ -0,0 +1,66 @@
/*
* L.Point represents a point with x and y coordinates.
*/
L.Point = function(/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
this.x = (round ? Math.round(x) : x);
this.y = (round ? Math.round(y) : y);
};
L.Point.prototype = {
add: function(point) {
return this.clone()._add(point);
},
_add: function(point) {
this.x += point.x;
this.y += point.y;
return this;
},
subtract: function(point) {
return this.clone()._subtract(point);
},
// destructive subtract (faster)
_subtract: function(point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
divideBy: function(num, round) {
return new L.Point(this.x/num, this.y/num, round);
},
multiplyBy: function(num) {
return new L.Point(this.x * num, this.y * num);
},
distanceTo: function(point) {
var x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x*x + y*y);
},
round: function() {
return this.clone()._round();
},
// destructive round
_round: function() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
clone: function() {
return new L.Point(this.x, this.y);
},
toString: function() {
return 'Point(' +
L.Util.formatNum(this.x) + ', ' +
L.Util.formatNum(this.y) + ')';
}
};

View File

@ -0,0 +1,55 @@
/*
* L.PolyUtil contains utilify functions for polygons (clipping, etc.).
*/
L.PolyUtil = {};
/*
* Sutherland-Hodgeman polygon clipping algorithm.
* Used to avoid rendering parts of a polygon that are not currently visible.
*/
L.PolyUtil.clipPolygon = function(points, bounds) {
var min = bounds.min,
max = bounds.max,
clippedPoints,
edges = [1, 4, 2, 8],
i, j, k,
a, b,
len, edge, p,
lu = L.LineUtil;
for (i = 0, len = points.length; i < len; i++) {
points[i]._code = lu._getBitCode(points[i], bounds);
}
// for each edge (left, bottom, right, top)
for (k = 0; k < 4; k++) {
edge = edges[k];
clippedPoints = [];
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
a = points[i];
b = points[j];
// if a is inside the clip window
if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) {
p = lu._getEdgeIntersection(b, a, edge, bounds);
p._code = lu._getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) {
p = lu._getEdgeIntersection(b, a, edge, bounds);
p._code = lu._getBitCode(p, bounds);
clippedPoints.push(p);
}
}
points = clippedPoints;
}
return points;
};

View File

@ -0,0 +1,31 @@
/*
* L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
*/
L.Transformation = L.Class.extend({
initialize: function(/*Number*/ a, /*Number*/ b, /*Number*/ c, /*Number*/ d) {
this._a = a;
this._b = b;
this._c = c;
this._d = d;
},
transform: function(point, scale) {
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
_transform: function(/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
untransform: function(/*Point*/ point, /*Number*/ scale) /*-> Point*/ {
scale = scale || 1;
return new L.Point(
(point.x/scale - this._b) / this._a,
(point.y/scale - this._d) / this._c);
}
});

View File

@ -0,0 +1,21 @@
/*
* L.Handler.DoubleClickZoom is used internally by L.Map to add double-click zooming.
*/
L.Handler.DoubleClickZoom = L.Handler.extend({
enable: function() {
if (this._enabled) { return; }
this._map.on('dblclick', this._onDoubleClick, this._map);
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
this._map.off('dblclick', this._onDoubleClick, this._map);
this._enabled = false;
},
_onDoubleClick: function(e) {
this.setView(e.latlng, this._zoom + 1);
}
});

View File

@ -0,0 +1,13 @@
/*
* L.Handler classes are used internally to inject interaction features to classes like Map and Marker.
*/
L.Handler = L.Class.extend({
initialize: function(map) {
this._map = map;
},
enabled: function() {
return !!this._enabled;
}
});

View File

@ -0,0 +1,44 @@
/*
* L.Handler.MapDrag is used internally by L.Map to make the map draggable.
*/
L.Handler.MapDrag = L.Handler.extend({
enable: function() {
if (this._enabled) { return; }
if (!this._draggable) {
this._draggable = new L.Draggable(this._map._mapPane, this._map._container);
this._draggable.on('dragstart', this._onDragStart, this);
this._draggable.on('drag', this._onDrag, this);
this._draggable.on('dragend', this._onDragEnd, this);
}
this._draggable.enable();
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
this._draggable.disable();
this._enabled = false;
},
moved: function() {
return this._draggable._moved;
},
_onDragStart: function() {
this._map.fire('movestart');
this._map.fire('dragstart');
},
_onDrag: function() {
this._map.fire('move');
this._map.fire('drag');
},
_onDragEnd: function() {
this._map.fire('moveend');
this._map.fire('dragend');
}
});

View File

@ -0,0 +1,54 @@
/*
* L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
*/
L.Handler.MarkerDrag = L.Handler.extend({
initialize: function(marker) {
this._marker = marker;
},
enable: function() {
if (this._enabled) { return; }
if (!this._draggable) {
this._draggable = new L.Draggable(this._marker._icon, this._marker._icon);
this._draggable.on('dragstart', this._onDragStart, this);
this._draggable.on('drag', this._onDrag, this);
this._draggable.on('dragend', this._onDragEnd, this);
}
this._draggable.enable();
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
this._draggable.disable();
this._enabled = false;
},
moved: function() {
return this._draggable && this._draggable._moved;
},
_onDragStart: function(e) {
this._marker.closePopup();
this._marker.fire('movestart');
this._marker.fire('dragstart');
},
_onDrag: function(e) {
// update shadow position
var iconPos = L.DomUtil.getPosition(this._marker._icon);
L.DomUtil.setPosition(this._marker._shadow, iconPos);
this._marker._latlng = this._marker._map.layerPointToLatLng(iconPos);
this._marker.fire('move');
this._marker.fire('drag');
},
_onDragEnd: function() {
this._marker.fire('moveend');
this._marker.fire('dragend');
}
});

View File

@ -0,0 +1,50 @@
/*
* L.Handler.ScrollWheelZoom is used internally by L.Map to enable mouse scroll wheel zooming on the map.
*/
L.Handler.ScrollWheelZoom = L.Handler.extend({
enable: function() {
if (this._enabled) { return; }
L.DomEvent.addListener(this._map._container, 'mousewheel', this._onWheelScroll, this);
this._delta = 0;
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
L.DomEvent.removeListener(this._map._container, 'mousewheel', this._onWheelScroll);
this._enabled = false;
},
_onWheelScroll: function(e) {
this._delta += L.DomEvent.getWheelDelta(e);
this._lastMousePos = this._map.mouseEventToContainerPoint(e);
clearTimeout(this._timer);
this._timer = setTimeout(L.Util.bind(this._performZoom, this), 50);
L.DomEvent.preventDefault(e);
},
_performZoom: function() {
var delta = Math.round(this._delta);
this._delta = 0;
if (!delta) { return; }
var center = this._getCenterForScrollWheelZoom(this._lastMousePos, delta),
zoom = this._map.getZoom() + delta;
if (this._map._limitZoom(zoom) == this._map._zoom) { return; }
this._map.setView(center, zoom);
},
_getCenterForScrollWheelZoom: function(mousePos, delta) {
var centerPoint = this._map.getPixelBounds().getCenter(),
viewHalf = this._map.getSize().divideBy(2),
centerOffset = mousePos.subtract(viewHalf).multiplyBy(1 - Math.pow(2, -delta)),
newCenterPoint = centerPoint.add(centerOffset);
return this._map.unproject(newCenterPoint, this._map._zoom, true);
}
});

View File

@ -0,0 +1,79 @@
/*
* L.Handler.ShiftDragZoom is used internally by L.Map to add shift-drag zoom (zoom to a selected bounding box).
*/
L.Handler.ShiftDragZoom = L.Handler.extend({
initialize: function(map) {
this._map = map;
this._container = map._container;
this._pane = map._panes.overlayPane;
},
enable: function() {
if (this._enabled) { return; }
L.DomEvent.addListener(this._container, 'mousedown', this._onMouseDown, this);
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
L.DomEvent.removeListener(this._container, 'mousedown', this._onMouseDown);
this._enabled = false;
},
_onMouseDown: function(e) {
if (!e.shiftKey || ((e.which != 1) && (e.button != 1))) { return false; }
L.DomUtil.disableTextSelection();
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 move cursor to styles
this._container.style.cursor = 'crosshair';
L.DomEvent.addListener(document, 'mousemove', this._onMouseMove, this);
L.DomEvent.addListener(document, 'mouseup', this._onMouseUp, this);
L.DomEvent.preventDefault(e);
},
_onMouseMove: function(e) {
var layerPoint = this._map.mouseEventToLayerPoint(e),
dx = layerPoint.x - this._startLayerPoint.x,
dy = layerPoint.y - this._startLayerPoint.y;
var newX = Math.min(layerPoint.x, this._startLayerPoint.x),
newY = Math.min(layerPoint.y, this._startLayerPoint.y),
newPos = new L.Point(newX, newY);
L.DomUtil.setPosition(this._box, newPos);
this._box.style.width = (Math.abs(dx) - 4) + 'px';
this._box.style.height = (Math.abs(dy) - 4) + 'px';
},
_onMouseUp: function(e) {
this._pane.removeChild(this._box);
this._container.style.cursor = '';
L.DomUtil.enableTextSelection();
L.DomEvent.removeListener(document, 'mousemove', this._onMouseMove);
L.DomEvent.removeListener(document, 'mouseup', this._onMouseUp);
var layerPoint = this._map.mouseEventToLayerPoint(e);
var bounds = new L.LatLngBounds(
this._map.layerPointToLatLng(this._startLayerPoint),
this._map.layerPointToLatLng(layerPoint));
this._map.fitBounds(bounds);
}
});

View File

@ -0,0 +1,87 @@
/*
* L.Handler.TouchZoom is used internally by L.Map to add touch-zooming on Webkit-powered mobile browsers.
*/
L.Handler.TouchZoom = L.Handler.extend({
enable: function() {
if (!L.Browser.mobileWebkit || this._enabled) { return; }
L.DomEvent.addListener(this._map._container, 'touchstart', this._onTouchStart, this);
this._enabled = true;
},
disable: function() {
if (!this._enabled) { return; }
L.DomEvent.removeListener(this._map._container, 'touchstart', this._onTouchStart, this);
this._enabled = false;
},
_onTouchStart: function(e) {
if (!e.touches || e.touches.length != 2 || this._map._animatingZoom) { return; }
var p1 = this._map.mouseEventToLayerPoint(e.touches[0]),
p2 = this._map.mouseEventToLayerPoint(e.touches[1]),
viewCenter = this._map.containerPointToLayerPoint(this._map.getSize().divideBy(2));
this._startCenter = p1.add(p2).divideBy(2, true);
this._startDist = p1.distanceTo(p2);
//this._startTransform = this._map._mapPane.style.webkitTransform;
this._moved = false;
this._zooming = true;
this._centerOffset = viewCenter.subtract(this._startCenter);
L.DomEvent.addListener(document, 'touchmove', this._onTouchMove, this);
L.DomEvent.addListener(document, 'touchend', this._onTouchEnd, this);
L.DomEvent.preventDefault(e);
},
_onTouchMove: function(e) {
if (!e.touches || e.touches.length != 2) { return; }
if (!this._moved) {
this._map._mapPane.className += ' leaflet-zoom-anim';
this._map._prepareTileBg();
this._moved = true;
}
var p1 = this._map.mouseEventToLayerPoint(e.touches[0]),
p2 = this._map.mouseEventToLayerPoint(e.touches[1]);
this._scale = p1.distanceTo(p2) / this._startDist;
this._delta = p1.add(p2).divideBy(2, true).subtract(this._startCenter);
/*
* Used 2 translates instead of transform-origin because of a very strange bug -
* it didn't count the origin on the first touch-zoom but worked correctly afterwards
*/
this._map._tileBg.style.webkitTransform = [
L.DomUtil.getTranslateString(this._delta),
L.DomUtil.getScaleString(this._scale, this._startCenter)
].join(" ");
L.DomEvent.preventDefault(e);
},
_onTouchEnd: function(e) {
if (!this._moved || !this._zooming) { return; }
this._zooming = false;
var oldZoom = this._map.getZoom(),
floatZoomDelta = Math.log(this._scale)/Math.LN2,
roundZoomDelta = (floatZoomDelta > 0 ? Math.ceil(floatZoomDelta) : Math.floor(floatZoomDelta)),
zoom = this._map._limitZoom(oldZoom + roundZoomDelta),
zoomDelta = zoom - oldZoom,
centerOffset = this._centerOffset.subtract(this._delta).divideBy(this._scale),
centerPoint = this._map.getPixelOrigin().add(this._startCenter).add(centerOffset),
center = this._map.unproject(centerPoint);
L.DomEvent.removeListener(document, 'touchmove', this._onTouchMove);
L.DomEvent.removeListener(document, 'touchend', this._onTouchEnd);
var finalScale = Math.pow(2, zoomDelta);
this._map._runAnimation(center, zoom, finalScale / this._scale, this._startCenter.add(centerOffset));
}
});

View File

@ -0,0 +1,40 @@
/*
* L.FeatureGroup extends L.LayerGroup by introducing mouse events and bindPopup method shared between a group of layers.
*/
L.FeatureGroup = L.LayerGroup.extend({
includes: L.Mixin.Events,
addLayer: function(layer) {
this._initEvents(layer);
L.LayerGroup.prototype.addLayer.call(this, layer);
if (this._popupContent && layer.bindPopup) {
layer.bindPopup(this._popupContent);
}
},
bindPopup: function(content) {
this._popupContent = content;
for (var i in this._layers) {
if (this._layers.hasOwnProperty(i) && this._layers[i].bindPopup) {
this._layers[i].bindPopup(content);
}
}
},
_events: ['click', 'dblclick', 'mouseover', 'mouseout'],
_initEvents: function(layer) {
for (var i = 0, len = this._events.length; i < len; i++) {
layer.on(this._events[i], this._propagateEvent, this);
}
},
_propagateEvent: function(e) {
e.layer = e.target;
e.target = this;
this.fire(e.type, e);
}
});

View File

@ -0,0 +1,106 @@
L.GeoJSON = L.LayerGroup.extend({
includes: L.Mixin.Events,
initialize: function(geojson, options) {
L.Util.setOptions(this, options);
this._geojson = geojson;
this._layers = {};
if (geojson) {
this.addGeoJSON(geojson);
}
},
addGeoJSON: function(geojson) {
if (geojson.features) {
for (var i = 0, len = geojson.features.length; i < len; i++) {
this.addGeoJSON(geojson.features[i]);
}
return;
}
var isFeature = (geojson.type == 'Feature'),
geometry = (isFeature ? geojson.geometry : geojson),
layer = L.GeoJSON.geometryToLayer(geometry, this.options.pointToLayer);
this.fire('featureparse', {
layer: layer,
properties: geojson.properties,
geometryType: geometry.type,
bbox: geojson.bbox,
id: geojson.id
});
this.addLayer(layer);
}
});
L.Util.extend(L.GeoJSON, {
geometryToLayer: function(geometry, pointToLayer) {
var coords = geometry.coordinates,
latlng, latlngs,
i, len,
layer,
layers = [];
switch (geometry.type) {
case 'Point':
latlng = this.coordsToLatLng(coords);
return pointToLayer ? pointToLayer(latlng) : new L.Marker(latlng);
case 'MultiPoint':
for (i = 0, len = coords.length; i < len; i++) {
latlng = this.coordsToLatLng(coords[i]);
layer = pointToLayer ? pointToLayer(latlng) : new L.Marker(latlng);
layers.push(layer);
}
return new L.FeatureGroup(layers);
case 'LineString':
latlngs = this.coordsToLatLngs(coords);
return new L.Polyline(latlngs);
case 'Polygon':
latlngs = this.coordsToLatLngs(coords, 1);
return new L.Polygon(latlngs);
case 'MultiLineString':
latlngs = this.coordsToLatLngs(coords, 1);
return new L.MultiPolyline(latlngs);
case "MultiPolygon":
latlngs = this.coordsToLatLngs(coords, 2);
return new L.MultiPolygon(latlngs);
case "GeometryCollection":
for (i = 0, len = geometry.geometries.length; i < len; i++) {
layer = this.geometryToLayer(geometry.geometries[i]);
layers.push(layer);
}
return new L.FeatureGroup(layers);
default:
throw new Error('Invalid GeoJSON object.');
}
},
coordsToLatLng: function(/*Array*/ coords, /*Boolean*/ reverse)/*: LatLng*/ {
var lat = parseFloat(coords[reverse ? 0 : 1]),
lng = parseFloat(coords[reverse ? 1 : 0]);
return new L.LatLng(lat, lng);
},
coordsToLatLngs: function(/*Array*/ coords, /*Number*/ levelsDeep, /*Boolean*/ reverse)/*: Array*/ {
var latlng, latlngs = [],
i, len = coords.length;
for (i = 0; i < len; i++) {
latlng = levelsDeep ?
this.coordsToLatLngs(coords[i], levelsDeep - 1, reverse) :
this.coordsToLatLng(coords[i], reverse);
latlngs.push(latlng);
}
return latlngs;
}
});

View File

@ -0,0 +1,58 @@
L.ImageOverlay = L.Class.extend({
includes: L.Mixin.Events,
initialize: function(/*String*/ url, /*LatLngBounds*/ bounds) {
this._url = url;
this._bounds = bounds;
},
onAdd: function(map) {
this._map = map;
if (!this._image) {
this._initImage();
}
map.getPanes().overlayPane.appendChild(this._image);
map.on('viewreset', this._reset, this);
this._reset();
},
onRemove: function(map) {
map.getPanes().overlayPane.removeChild(this._image);
map.off('viewreset', this._reset, this);
},
_initImage: function() {
this._image = L.DomUtil.create('img', 'leaflet-image-layer');
this._image.style.visibility = 'hidden';
//TODO opacity option
//TODO createImage util method to remove duplication
L.Util.extend(this._image, {
galleryimg: 'no',
onselectstart: L.Util.falseFn,
onmousemove: L.Util.falseFn,
onload: this._onImageLoad,
src: this._url
});
},
_reset: function() {
var topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
bottomRight = this._map.latLngToLayerPoint(this._bounds.getSouthEast()),
size = bottomRight.subtract(topLeft);
L.DomUtil.setPosition(this._image, topLeft);
this._image.style.width = size.x + 'px';
this._image.style.height = size.y + 'px';
},
_onImageLoad: function() {
this.style.visibility = '';
//TODO fire layerload
}
});

View File

@ -0,0 +1,58 @@
/*
* L.LayerGroup is a class to combine several layers so you can manipulate the group (e.g. add/remove it) as one layer.
*/
L.LayerGroup = L.Class.extend({
initialize: function(layers) {
this._layers = {};
if (layers) {
for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
}
},
addLayer: function(layer) {
var id = L.Util.stamp(layer);
this._layers[id] = layer;
if (this._map) {
this._map.addLayer(layer);
}
return this;
},
removeLayer: function(layer) {
var id = L.Util.stamp(layer);
delete this._layers[id];
if (this._map) {
this._map.removeLayer(layer);
}
return this;
},
clearLayers: function() {
this._iterateLayers(this.removeLayer, this);
return this;
},
onAdd: function(map) {
this._map = map;
this._iterateLayers(map.addLayer, map);
},
onRemove: function(map) {
this._iterateLayers(map.removeLayer, map);
delete this._map;
},
_iterateLayers: function(method, context) {
for (var i in this._layers) {
if (this._layers.hasOwnProperty(i)) {
method.call(context, this._layers[i]);
}
}
}
});

View File

@ -0,0 +1,165 @@
L.Popup = L.Class.extend({
includes: L.Mixin.Events,
options: {
maxWidth: 300,
autoPan: true,
closeButton: true,
offset: new L.Point(0, 2),
autoPanPadding: new L.Point(5, 5)
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
onAdd: function(map) {
this._map = map;
if (!this._container) {
this._initLayout();
}
this._updateContent();
this._container.style.opacity = '0';
this._map._panes.popupPane.appendChild(this._container);
this._map.on('viewreset', this._updatePosition, this);
if (this._map.options.closePopupOnClick) {
this._map.on('preclick', this._close, this);
}
this._update();
this._container.style.opacity = '1'; //TODO fix ugly opacity hack
this._opened = true;
},
onRemove: function(map) {
map._panes.popupPane.removeChild(this._container);
map.off('viewreset', this._updatePosition, this);
map.off('click', this._close, this);
this._container.style.opacity = '0';
this._opened = false;
},
setLatLng: function(latlng) {
this._latlng = latlng;
if (this._opened) {
this._update();
}
return this;
},
setContent: function(content) {
this._content = content;
if (this._opened) {
this._update();
}
return this;
},
_close: function() {
if (this._opened) {
this._map.removeLayer(this);
}
},
_initLayout: function() {
this._container = L.DomUtil.create('div', 'leaflet-popup');
this._closeButton = L.DomUtil.create('a', 'leaflet-popup-close-button', this._container);
this._closeButton.href = '#close';
this._closeButton.onclick = L.Util.bind(this._onCloseButtonClick, this);
this._wrapper = L.DomUtil.create('div', 'leaflet-popup-content-wrapper', this._container);
L.DomEvent.disableClickPropagation(this._wrapper);
this._contentNode = L.DomUtil.create('div', 'leaflet-popup-content', this._wrapper);
this._tipContainer = L.DomUtil.create('div', 'leaflet-popup-tip-container', this._container);
this._tip = L.DomUtil.create('div', 'leaflet-popup-tip', this._tipContainer);
},
_update: function() {
this._container.style.visibility = 'hidden';
this._updateContent();
this._updateLayout();
this._updatePosition();
this._container.style.visibility = '';
this._adjustPan();
},
_updateContent: function() {
if (!this._content) return;
if (typeof this._content == 'string') {
this._contentNode.innerHTML = this._content;
} else {
this._contentNode.innerHTML = '';
this._contentNode.appendChild(this._content);
}
},
_updateLayout: function() {
this._container.style.width = '';
this._container.style.whiteSpace = 'nowrap';
var width = this._container.offsetWidth;
this._container.style.width = (width > this.options.maxWidth ? this.options.maxWidth : width) + 'px';
this._container.style.whiteSpace = '';
this._containerWidth = this._container.offsetWidth;
},
_updatePosition: function() {
var pos = this._map.latLngToLayerPoint(this._latlng);
this._containerBottom = -pos.y - this.options.offset.y;
this._containerLeft = pos.x - Math.round(this._containerWidth/2) + this.options.offset.x;
this._container.style.bottom = this._containerBottom + 'px';
this._container.style.left = this._containerLeft + 'px';
},
_adjustPan: function() {
if (!this.options.autoPan) { return; }
var containerHeight = this._container.offsetHeight,
layerPos = new L.Point(
this._containerLeft,
-containerHeight - this._containerBottom),
containerPos = this._map.layerPointToContainerPoint(layerPos),
adjustOffset = new L.Point(0, 0),
padding = this.options.autoPanPadding,
size = this._map.getSize();
if (containerPos.x < 0) {
adjustOffset.x = containerPos.x - padding.x;
}
if (containerPos.x + this._containerWidth > size.x) {
adjustOffset.x = containerPos.x + this._containerWidth - size.x + padding.x;
}
if (containerPos.y < 0) {
adjustOffset.y = containerPos.y - padding.y;
}
if (containerPos.y + containerHeight > size.y) {
adjustOffset.y = containerPos.y + containerHeight - size.y + padding.y;
}
if (adjustOffset.x || adjustOffset.y) {
this._map.panBy(adjustOffset);
}
},
_onCloseButtonClick: function(e) {
this._close();
L.DomEvent.stop(e);
}
});

View File

@ -0,0 +1,56 @@
L.Icon = L.Class.extend({
iconUrl: L.ROOT_URL + 'images/marker.png',
shadowUrl: L.ROOT_URL + 'images/marker-shadow.png',
iconSize: new L.Point(25, 41),
shadowSize: new L.Point(41, 41),
iconAnchor: new L.Point(13, 41),
popupAnchor: new L.Point(0, -33),
initialize: function(iconUrl) {
if (iconUrl) {
this.iconUrl = iconUrl;
}
},
createIcon: function() {
return this._createIcon('icon');
},
createShadow: function() {
return this._createIcon('shadow');
},
_createIcon: function(name) {
var size = this[name + 'Size'],
src = this[name + 'Url'],
img = this._createImg(src);
if (!src) { return null; }
img.className = 'leaflet-marker-' + name;
img.style.marginLeft = (-this.iconAnchor.x) + 'px';
img.style.marginTop = (-this.iconAnchor.y) + 'px';
if (size) {
img.style.width = size.x + 'px';
img.style.height = size.y + 'px';
}
return img;
},
_createImg: function(src) {
var el;
if (!L.Browser.ie6) {
el = document.createElement('img');
el.src = src;
} else {
el = document.createElement('div');
el.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="' + src + '")';
}
return el;
}
});

View File

@ -0,0 +1,28 @@
/*
* Popup extension to L.Marker, adding openPopup & bindPopup methods.
*/
L.Marker.include({
openPopup: function() {
this._popup.setLatLng(this._latlng);
this._map.openPopup(this._popup);
return this;
},
closePopup: function() {
if (this._popup) {
this._popup._close();
}
},
bindPopup: function(content, options) {
options = L.Util.extend({offset: this.options.icon.popupAnchor}, options);
this._popup = new L.Popup(options);
this._popup.setContent(content);
this.on('click', this.openPopup, this);
return this;
}
});

View File

@ -0,0 +1,123 @@
/*
* L.Marker is used to display clickable/draggable icons on the map.
*/
L.Marker = L.Class.extend({
includes: L.Mixin.Events,
options: {
icon: new L.Icon(),
title: '',
clickable: true,
draggable: false
},
initialize: function(latlng, options) {
L.Util.setOptions(this, options);
this._latlng = latlng;
},
onAdd: function(map) {
this._map = map;
this._initIcon();
map.on('viewreset', this._reset, this);
this._reset();
},
onRemove: function(map) {
this._removeIcon();
map.off('viewreset', this._reset, this);
},
getLatLng: function() {
return this._latlng;
},
setLatLng: function(latlng) {
this._latlng = latlng;
this._reset();
},
setIcon: function(icon) {
this._removeIcon();
this._icon = this._shadow = null;
this.options.icon = icon;
this._initIcon();
},
_initIcon: function() {
if (!this._icon) {
this._icon = this.options.icon.createIcon();
if (this.options.title) {
this._icon.title = this.options.title;
}
this._initInteraction();
}
if (!this._shadow) {
this._shadow = this.options.icon.createShadow();
}
this._map._panes.markerPane.appendChild(this._icon);
if (this._shadow) {
this._map._panes.shadowPane.appendChild(this._shadow);
}
},
_removeIcon: function() {
this._map._panes.markerPane.removeChild(this._icon);
if (this._shadow) {
this._map._panes.shadowPane.removeChild(this._shadow);
}
},
_reset: function() {
var pos = this._map.latLngToLayerPoint(this._latlng).round();
L.DomUtil.setPosition(this._icon, pos);
if (this._shadow) {
L.DomUtil.setPosition(this._shadow, pos);
}
this._icon.style.zIndex = pos.y;
},
_initInteraction: function() {
if (this.options.clickable) {
this._icon.className += ' leaflet-clickable';
L.DomEvent.addListener(this._icon, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.addListener(this._icon, events[i], this._fireMouseEvent, this);
}
}
if (L.Handler.MarkerDrag) {
this.dragging = new L.Handler.MarkerDrag(this);
if (this.options.draggable) {
this.dragging.enable();
}
}
},
_onMouseClick: function(e) {
L.DomEvent.stopPropagation(e);
if (this.dragging && this.dragging.moved()) { return; }
this.fire(e.type);
},
_fireMouseEvent: function(e) {
this.fire(e.type);
L.DomEvent.stopPropagation(e);
}
});

View File

@ -0,0 +1,41 @@
L.TileLayer.Canvas = L.TileLayer.extend({
options: {
async: false
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
_createTileProto: function() {
this._canvasProto = L.DomUtil.create('canvas', 'leaflet-tile');
var tileSize = this.options.tileSize;
this._canvasProto.width = tileSize;
this._canvasProto.height = tileSize;
},
_createTile: function() {
var tile = this._canvasProto.cloneNode(false);
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
return tile;
},
_loadTile: function(tile, tilePoint, zoom) {
tile._layer = this;
this.drawTile(tile, tilePoint, zoom);
if (!this.options.async) {
this.tileDrawn(tile);
}
},
drawTile: function(tile, tilePoint, zoom) {
// override with rendering code
},
tileDrawn: function(tile) {
this._tileOnLoad.call(tile);
}
});

View File

@ -0,0 +1,47 @@
L.TileLayer.WMS = L.TileLayer.extend({
defaultWmsParams: {
service: 'WMS',
request: 'GetMap',
version: '1.1.1',
layers: '',
styles: '',
format: 'image/jpeg',
transparent: false
},
initialize: function(/*String*/ url, /*Object*/ options) {
this._url = url;
this.wmsParams = L.Util.extend({}, this.defaultWmsParams);
this.wmsParams.width = this.wmsParams.height = this.options.tileSize;
for (var i in options) {
// all keys that are not TileLayer options go to WMS params
if (!this.options.hasOwnProperty(i)) {
this.wmsParams[i] = options[i];
}
}
L.Util.setOptions(this, options);
},
onAdd: function(map) {
var projectionKey = (parseFloat(this.wmsParams.version) >= 1.3 ? 'crs' : 'srs');
this.wmsParams[projectionKey] = map.options.crs.code;
L.TileLayer.prototype.onAdd.call(this, map);
},
getTileUrl: function(/*Point*/ tilePoint, /*Number*/ zoom)/*-> String*/ {
var tileSize = this.options.tileSize,
nwPoint = tilePoint.multiplyBy(tileSize),
sePoint = nwPoint.add(new L.Point(tileSize, tileSize)),
nwMap = this._map.unproject(nwPoint, this._zoom, true),
seMap = this._map.unproject(sePoint, this._zoom, true),
nw = this._map.options.crs.project(nwMap),
se = this._map.options.crs.project(seMap),
bbox = [nw.x, se.y, se.x, nw.y].join(',');
return this._url + L.Util.getParamString(this.wmsParams) + "&bbox=" + bbox;
}
});

View File

@ -0,0 +1,262 @@
/*
* L.TileLayer is used for standard xyz-numbered tile layers.
*/
L.TileLayer = L.Class.extend({
includes: L.Mixin.Events,
options: {
minZoom: 0,
maxZoom: 18,
tileSize: 256,
subdomains: 'abc',
errorTileUrl: '',
attribution: '',
opacity: 1,
scheme: 'xyz',
noWrap: false,
unloadInvisibleTiles: L.Browser.mobileWebkit,
updateWhenIdle: L.Browser.mobileWebkit
},
initialize: function(url, options) {
L.Util.setOptions(this, options);
this._url = url;
if (typeof this.options.subdomains == 'string') {
this.options.subdomains = this.options.subdomains.split('');
}
},
onAdd: function(map) {
this._map = map;
// 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, this);
if (this.options.updateWhenIdle) {
map.on('moveend', this._update, this);
} else {
this._limitedUpdate = L.Util.limitExecByInterval(this._update, 100, this);
map.on('move', this._limitedUpdate, this);
}
this._reset();
this._update();
},
onRemove: function(map) {
this._map.getPanes().tilePane.removeChild(this._container);
this._container = null;
this._map.off('viewreset', this._reset, this);
if (this.options.updateWhenIdle) {
this._map.off('moveend', this._update, this);
} else {
this._map.off('move', this._limitedUpdate, this);
}
},
getAttribution: function() {
return this.options.attribution;
},
setOpacity: function(opacity) {
this.options.opacity = opacity;
this._setOpacity(opacity);
// stupid webkit hack to force redrawing of tiles
if (L.Browser.webkit) {
for (i in this._tiles) {
this._tiles[i].style.webkitTransform += ' translate(0,0)';
}
}
},
_setOpacity: function(opacity) {
if (opacity < 1) {
L.DomUtil.setOpacity(this._container, opacity);
}
},
_initContainer: function() {
var tilePane = this._map.getPanes().tilePane;
if (!this._container || tilePane.empty) {
this._container = L.DomUtil.create('div', 'leaflet-layer', tilePane);
this._setOpacity(this.options.opacity);
}
},
_reset: function() {
this._tiles = {};
this._initContainer();
this._container.innerHTML = '';
},
_update: function() {
var bounds = this._map.getPixelBounds(),
tileSize = this.options.tileSize;
var nwTilePoint = new L.Point(
Math.floor(bounds.min.x / tileSize),
Math.floor(bounds.min.y / tileSize)),
seTilePoint = new L.Point(
Math.floor(bounds.max.x / tileSize),
Math.floor(bounds.max.y / tileSize)),
tileBounds = new L.Bounds(nwTilePoint, seTilePoint);
this._addTilesFromCenterOut(tileBounds);
if (this.options.unloadInvisibleTiles) {
this._removeOtherTiles(tileBounds);
}
},
_addTilesFromCenterOut: function(bounds) {
var queue = [],
center = bounds.getCenter();
for (var j = bounds.min.y; j <= bounds.max.y; j++) {
for (var i = bounds.min.x; i <= bounds.max.x; i++) {
if ((i + ':' + j) in this._tiles) { continue; }
queue.push(new L.Point(i, j));
}
}
// load tiles in order of their distance to center
queue.sort(function(a, b) {
return a.distanceTo(center) - b.distanceTo(center);
});
this._tilesToLoad = queue.length;
for (var k = 0, len = this._tilesToLoad; k < len; k++) {
this._addTile(queue[k]);
}
},
_removeOtherTiles: function(bounds) {
var kArr, x, y, key;
for (key in this._tiles) {
if (this._tiles.hasOwnProperty(key)) {
kArr = key.split(':');
x = parseInt(kArr[0], 10);
y = parseInt(kArr[1], 10);
// remove tile if it's out of bounds
if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
this._tiles[key].src = '';
if (this._tiles[key].parentNode == this._container) {
this._container.removeChild(this._tiles[key]);
}
delete this._tiles[key];
}
}
}
},
_addTile: function(tilePoint) {
var tilePos = this._getTilePos(tilePoint),
zoom = this._map.getZoom(),
key = tilePoint.x + ':' + tilePoint.y;
// wrap tile coordinates
var tileLimit = (1 << zoom);
if (!this.options.noWrap) {
tilePoint.x = ((tilePoint.x % tileLimit) + tileLimit) % tileLimit;
}
if (tilePoint.y < 0 || tilePoint.y >= tileLimit) { return; }
// create tile
var tile = this._createTile();
L.DomUtil.setPosition(tile, tilePos);
this._tiles[key] = tile;
if (this.options.scheme == 'tms') {
tilePoint.y = tileLimit - tilePoint.y - 1;
}
this._loadTile(tile, tilePoint, zoom);
this._container.appendChild(tile);
},
_getTilePos: function(tilePoint) {
var origin = this._map.getPixelOrigin(),
tileSize = this.options.tileSize;
return tilePoint.multiplyBy(tileSize).subtract(origin);
},
// image-specific code (override to implement e.g. Canvas or SVG tile layer)
getTileUrl: function(tilePoint, zoom) {
var subdomains = this.options.subdomains,
s = this.options.subdomains[(tilePoint.x + tilePoint.y) % subdomains.length];
return this._url
.replace('{s}', s)
.replace('{z}', zoom)
.replace('{x}', tilePoint.x)
.replace('{y}', tilePoint.y);
},
_createTileProto: function() {
this._tileImg = L.DomUtil.create('img', 'leaflet-tile');
this._tileImg.galleryimg = 'no';
var tileSize = this.options.tileSize;
this._tileImg.style.width = tileSize + 'px';
this._tileImg.style.height = tileSize + 'px';
},
_createTile: function() {
var tile = this._tileImg.cloneNode(false);
tile.onselectstart = tile.onmousemove = L.Util.falseFn;
return tile;
},
_loadTile: function(tile, tilePoint, zoom) {
tile._layer = this;
tile.onload = this._tileOnLoad;
tile.onerror = this._tileOnError;
tile.src = this.getTileUrl(tilePoint, zoom);
},
_tileOnLoad: function(e) {
var layer = this._layer;
this.className += ' leaflet-tile-loaded';
layer.fire('tileload', {tile: this, url: this.src});
layer._tilesToLoad--;
if (!layer._tilesToLoad) {
layer.fire('load');
}
},
_tileOnError: function(e) {
var layer = this._layer;
layer.fire('tileerror', {tile: this, url: this.src});
var newUrl = layer.options.errorTileUrl;
if (newUrl) {
this.src = newUrl;
}
}
});

View File

@ -0,0 +1,51 @@
/*
* L.Circle is a circle overlay (with a certain radius in meters).
*/
L.Circle = L.Path.extend({
initialize: function(latlng, radius, options) {
L.Path.prototype.initialize.call(this, options);
this._latlng = latlng;
this._mRadius = radius;
},
options: {
fill: true
},
setLatLng: function(latlng) {
this._latlng = latlng;
this._redraw();
return this;
},
setRadius: function(radius) {
this._mRadius = radius;
this._redraw();
return this;
},
projectLatlngs: function() {
var equatorLength = 40075017,
scale = this._map.options.scale(this._map._zoom);
this._point = this._map.latLngToLayerPoint(this._latlng);
this._radius = (this._mRadius / equatorLength) * scale;
},
getPathString: function() {
var p = this._point,
r = this._radius;
if (L.Path.SVG) {
return "M" + p.x + "," + (p.y - r) +
"A" + r + "," + r + ",0,1,1," +
(p.x - 0.1) + "," + (p.y - r) + " z";
} else {
p._round();
r = Math.round(r);
return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
}
}
});

View File

@ -0,0 +1,25 @@
/*
* L.CircleMarker is a circle overlay with a permanent pixel radius.
*/
L.CircleMarker = L.Circle.extend({
options: {
radius: 10,
weight: 2
},
initialize: function(latlng, options) {
L.Circle.prototype.initialize.call(this, latlng, null, options);
this._radius = this.options.radius;
},
projectLatlngs: function() {
this._point = this._map.latLngToLayerPoint(this._latlng);
},
setRadius: function(radius) {
this._radius = radius;
this._redraw();
return this;
}
});

View File

@ -0,0 +1,27 @@
/*
* Contains L.MultiPolyline and L.MultiPolygon layers.
*/
(function() {
function createMulti(klass) {
return L.FeatureGroup.extend({
initialize: function(latlngs, options) {
this._layers = {};
for (var i = 0, len = latlngs.length; i < len; i++) {
this.addLayer(new klass(latlngs[i], options));
}
},
setStyle: function(style) {
for (var i in this._layers) {
if (this._layers.hasOwnProperty(i) && this._layers[i].setStyle) {
this._layers[i].setStyle(style);
}
}
}
});
}
L.MultiPolyline = createMulti(L.Polyline);
L.MultiPolygon = createMulti(L.Polygon);
}());

View File

@ -0,0 +1,24 @@
/*
* Popup extension to L.Path (polylines, polygons, circles), adding bindPopup method.
*/
L.Path.include({
bindPopup: function(content, options) {
if (!this._popup || this._popup.options !== options) {
this._popup = new L.Popup(options);
}
this._popup.setContent(content);
if (!this._openPopupAdded) {
this.on('click', this._openPopup, this);
this._openPopupAdded = true;
}
return this;
},
_openPopup: function(e) {
this._popup.setLatLng(e.latlng);
this._map.openPopup(this._popup);
}
});

View File

@ -0,0 +1,91 @@
/*
* Vector rendering for IE6-8 through VML.
* Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
*/
L.Path.VML = (function() {
var d = document.createElement('div'), s;
d.innerHTML = '<v:shape adj="1"/>';
s = d.firstChild;
s.style.behavior = 'url(#default#VML)';
return (s && (typeof s.adj == 'object'));
})();
L.Path = L.Path.SVG || !L.Path.VML ? L.Path : L.Path.extend({
statics: {
CLIP_PADDING: 0.02
},
_createElement: (function() {
try {
document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
return function(name) {
return document.createElement('<lvml:' + name + ' class="lvml">');
};
} catch (e) {
return function(name) {
return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
};
}
})(),
_initRoot: function() {
if (!this._map._pathRoot) {
this._map._pathRoot = document.createElement('div');
this._map._pathRoot.className = 'leaflet-vml-container';
this._map._panes.overlayPane.appendChild(this._map._pathRoot);
this._map.on('moveend', this._updateViewport, this);
this._updateViewport();
}
},
_initPath: function() {
this._container = this._createElement('shape');
this._container.className += ' leaflet-vml-shape' +
(this.options.clickable ? ' leaflet-clickable' : '');
this._container.coordsize = '1 1';
this._path = this._createElement('path');
this._container.appendChild(this._path);
this._map._pathRoot.appendChild(this._container);
},
_initStyle: function() {
if (this.options.stroke) {
this._stroke = this._createElement('stroke');
this._stroke.endcap = 'round';
this._container.appendChild(this._stroke);
} else {
this._container.stroked = false;
}
if (this.options.fill) {
this._container.filled = true;
this._fill = this._createElement('fill');
this._container.appendChild(this._fill);
} else {
this._container.filled = false;
}
this._updateStyle();
},
_updateStyle: function() {
if (this.options.stroke) {
this._stroke.weight = this.options.weight + 'px';
this._stroke.color = this.options.color;
this._stroke.opacity = this.options.opacity;
}
if (this.options.fill) {
this._fill.color = this.options.fillColor || this.options.color;
this._fill.opacity = this.options.fillOpacity;
}
},
_updatePath: function() {
this._container.style.display = 'none';
this._path.v = this.getPathString() + ' '; // the space fixes IE empty path string bug
this._container.style.display = '';
}
});

View File

@ -0,0 +1,207 @@
/*
* L.Path is a base class for rendering vector paths on a map. It's inherited by Polyline, Circle, etc.
*/
L.Path = L.Class.extend({
includes: [L.Mixin.Events],
statics: (function() {
var svgns = 'http://www.w3.org/2000/svg',
ce = 'createElementNS';
return {
SVG_NS: svgns,
SVG: !!(document[ce] && document[ce](svgns, 'svg').createSVGRect),
// how much to extend the clip area around the map view
// (relative to its size, e.g. 0.5 is half the screen in each direction)
CLIP_PADDING: 0.5
};
})(),
options: {
stroke: true,
color: '#0033ff',
weight: 5,
opacity: 0.5,
fill: false,
fillColor: null, //same as color by default
fillOpacity: 0.2,
clickable: true,
updateOnMoveEnd: false
},
initialize: function(options) {
L.Util.setOptions(this, options);
},
onAdd: function(map) {
this._map = map;
this._initElements();
this._initEvents();
this.projectLatlngs();
this._updatePath();
map.on('viewreset', this.projectLatlngs, this);
this._updateTrigger = this.options.updateOnMoveEnd ? 'moveend' : 'viewreset';
map.on(this._updateTrigger, this._updatePath, this);
},
onRemove: function(map) {
map._pathRoot.removeChild(this._container);
map.off('viewreset', this._projectLatlngs, this);
map.off(this._updateTrigger, this._updatePath, this);
},
projectLatlngs: function() {
// do all projection stuff here
},
getPathString: function() {
// form path string here
},
setStyle: function(style) {
L.Util.setOptions(this, style);
if (this._path) {
this._updateStyle();
}
},
_initElements: function() {
this._initRoot();
this._initPath();
this._initStyle();
},
_initRoot: function() {
if (!this._map._pathRoot) {
this._map._pathRoot = this._createElement('svg');
this._map._panes.overlayPane.appendChild(this._map._pathRoot);
this._map.on('moveend', this._updateSvgViewport, this);
this._updateSvgViewport();
}
},
_updateSvgViewport: function() {
this._updateViewport();
var vp = this._map._pathViewport,
min = vp.min,
max = vp.max,
width = max.x - min.x,
height = max.y - min.y,
root = this._map._pathRoot,
pane = this._map._panes.overlayPane;
// Hack to make flicker on drag end on mobile webkit less irritating
// Unfortunately I haven't found a good workaround for this yet
if (L.Browser.mobileWebkit) { pane.removeChild(root); }
L.DomUtil.setPosition(root, min);
root.setAttribute('width', width);
root.setAttribute('height', height);
root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
if (L.Browser.mobileWebkit) { pane.appendChild(root); }
},
_updateViewport: function() {
var p = L.Path.CLIP_PADDING,
size = this._map.getSize(),
//TODO this._map._getMapPanePos()
panePos = L.DomUtil.getPosition(this._map._mapPane),
min = panePos.multiplyBy(-1).subtract(size.multiplyBy(p)),
max = min.add(size.multiplyBy(1 + p * 2));
this._map._pathViewport = new L.Bounds(min, max);
},
_initPath: function() {
this._container = this._createElement('g');
this._path = this._createElement('path');
this._container.appendChild(this._path);
this._map._pathRoot.appendChild(this._container);
},
_initStyle: function() {
if (this.options.stroke) {
this._path.setAttribute('stroke-linejoin', 'round');
this._path.setAttribute('stroke-linecap', 'round');
}
if (this.options.fill) {
this._path.setAttribute('fill-rule', 'evenodd');
} else {
this._path.setAttribute('fill', 'none');
}
this._updateStyle();
},
_updateStyle: function() {
if (this.options.stroke) {
this._path.setAttribute('stroke', this.options.color);
this._path.setAttribute('stroke-opacity', this.options.opacity);
this._path.setAttribute('stroke-width', this.options.weight);
}
if (this.options.fill) {
this._path.setAttribute('fill', this.options.fillColor || this.options.color);
this._path.setAttribute('fill-opacity', this.options.fillOpacity);
}
},
_updatePath: function() {
var str = this.getPathString();
if (!str) {
// fix webkit empty string parsing bug
str = 'M0 0';
}
this._path.setAttribute('d', str);
},
_createElement: function(name) {
return document.createElementNS(L.Path.SVG_NS, name);
},
// TODO remove duplication with L.Map
_initEvents: function() {
if (this.options.clickable) {
if (!L.Path.VML) {
this._path.setAttribute('class', 'leaflet-clickable');
}
L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseover', 'mouseout'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this);
}
}
},
_onMouseClick: function(e) {
if (this._map.dragging && this._map.dragging.moved()) { return; }
this._fireMouseEvent(e);
},
_fireMouseEvent: function(e) {
if (!this.hasEventListeners(e.type)) { return; }
this.fire(e.type, {
latlng: this._map.mouseEventToLatLng(e),
layerPoint: this._map.mouseEventToLayerPoint(e)
});
L.DomEvent.stopPropagation(e);
},
_redraw: function() {
this.projectLatlngs();
this._updatePath();
}
});

View File

@ -0,0 +1,58 @@
/*
* L.Polygon is used to display polygons on a map.
*/
L.Polygon = L.Polyline.extend({
options: {
fill: true
},
initialize: function(latlngs, options) {
L.Polyline.prototype.initialize.call(this, latlngs, options);
if (latlngs[0] instanceof Array) {
this._latlngs = latlngs[0];
this._holes = latlngs.slice(1);
}
},
projectLatlngs: function() {
L.Polyline.prototype.projectLatlngs.call(this);
// project polygon holes points
// TODO move this logic to Polyline to get rid of duplication
this._holePoints = [];
if (!this._holes) return;
for (var i = 0, len = this._holes.length, hole; i < len; i++) {
this._holePoints[i] = [];
for(var j = 0, len2 = this._holes[i].length; j < len2; j++) {
this._holePoints[i][j] = this._map.latLngToLayerPoint(this._holes[i][j]);
}
}
},
_clipPoints: function() {
var points = this._originalPoints,
newParts = [];
this._parts = [points].concat(this._holePoints);
if (this.options.noClip) return;
for (var i = 0, len = this._parts.length; i < len; i++) {
var clipped = L.PolyUtil.clipPolygon(this._parts[i], this._map._pathViewport);
if (!clipped.length) continue;
newParts.push(clipped);
}
this._parts = newParts;
},
_getPathPartStr: function(points) {
var str = L.Polyline.prototype._getPathPartStr.call(this, points);
return str + (L.Path.SVG ? 'z' : 'x');
}
});

View File

@ -0,0 +1,112 @@
L.Polyline = L.Path.extend({
initialize: function(latlngs, options) {
L.Path.prototype.initialize.call(this, options);
this._latlngs = latlngs;
},
options: {
// how much to simplify the polyline on each zoom level
// more = better performance and smoother look, less = more accurate
smoothFactor: 1.0,
noClip: false,
updateOnMoveEnd: true
},
projectLatlngs: function() {
this._originalPoints = [];
for (var i = 0, len = this._latlngs.length; i < len; i++) {
this._originalPoints[i] = this._map.latLngToLayerPoint(this._latlngs[i]);
}
},
getPathString: function() {
for (var i = 0, len = this._parts.length, str = ''; i < len; i++) {
str += this._getPathPartStr(this._parts[i]);
}
return str;
},
getLatLngs: function() {
return this._latlngs;
},
setLatLngs: function(latlngs) {
this._latlngs = latlngs;
this._redraw();
return this;
},
addLatLng: function(latlng) {
this._latlngs.push(latlng);
this._redraw();
return this;
},
spliceLatLngs: function(index, howMany) {
var removed = [].splice.apply(this._latlngs, arguments);
this._redraw();
return removed;
},
_getPathPartStr: function(points) {
var round = L.Path.VML;
for (var j = 0, len2 = points.length, str = '', p; j < len2; j++) {
p = points[j];
if (round) p._round();
str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
}
return str;
},
_clipPoints: function() {
var points = this._originalPoints,
len = points.length,
i, k, segment;
if (this.options.noClip) {
this._parts = [points];
return;
}
this._parts = [];
var parts = this._parts,
vp = this._map._pathViewport,
lu = L.LineUtil;
for (i = 0, k = 0; i < len - 1; i++) {
segment = lu.clipSegment(points[i], points[i+1], vp, i);
if (!segment) continue;
parts[k] = parts[k] || [];
parts[k].push(segment[0]);
// if segment goes out of screen, or it's the last one, it's the end of the line part
if ((segment[1] != points[i+1]) || (i == len - 2)) {
parts[k].push(segment[1]);
k++;
}
}
},
// simplify each clipped part of the polyline
_simplifyPoints: function() {
var parts = this._parts,
lu = L.LineUtil;
for (var i = 0, len = parts.length; i < len; i++) {
parts[i] = lu.simplify(parts[i], this.options.smoothFactor);
}
},
_updatePath: function() {
this._clipPoints();
this._simplifyPoints();
L.Path.prototype._updatePath.call(this);
}
});

View File

@ -0,0 +1,464 @@
/*
* L.Map is the central class of the API - it is used to create a map.
*/
L.Map = L.Class.extend({
includes: L.Mixin.Events,
options: {
// projection
crs: L.CRS.EPSG3857 || L.CRS.EPSG4326,
scale: function(zoom) { return 256 * (1 << zoom); },
// state
center: null,
zoom: null,
layers: [],
// interaction
dragging: true,
touchZoom: L.Browser.mobileWebkit && !L.Browser.android,
scrollWheelZoom: !L.Browser.mobileWebkit,
doubleClickZoom: true,
shiftDragZoom: true,
// controls
zoomControl: true,
attributionControl: true,
// animation
fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android,
zoomAnimation: L.DomUtil.TRANSITION && !L.Browser.android && !L.Browser.mobileOpera,
// misc
trackResize: true,
closePopupOnClick: true
},
// constructor
initialize: function(/*HTMLElement or String*/ id, /*Object*/ options) {
L.Util.setOptions(this, options);
this._container = L.DomUtil.get(id);
this._initLayout();
if (L.DomEvent) {
this._initEvents();
if (L.Handler) { this._initInteraction(); }
if (L.Control) { this._initControls(); }
}
var center = this.options.center,
zoom = this.options.zoom;
if (center !== null && zoom !== null) {
this.setView(center, zoom, true);
}
var layers = this.options.layers;
layers = (layers instanceof Array ? layers : [layers]);
this._tileLayersNum = 0;
this._initLayers(layers);
},
// public methods that modify map state
// replaced by animation-powered implementation in Map.PanAnimation.js
setView: function(center, zoom, forceReset) {
// reset the map view
this._resetView(center, this._limitZoom(zoom));
return this;
},
setZoom: function(/*Number*/ zoom) {
return this.setView(this.getCenter(), zoom);
},
zoomIn: function() {
return this.setZoom(this._zoom + 1);
},
zoomOut: function() {
return this.setZoom(this._zoom - 1);
},
fitBounds: function(/*LatLngBounds*/ bounds) {
var zoom = this.getBoundsZoom(bounds);
return this.setView(bounds.getCenter(), zoom);
},
fitWorld: function() {
var sw = new L.LatLng(-60, -170),
ne = new L.LatLng(85, 179);
return this.fitBounds(new L.LatLngBounds(sw, ne));
},
panTo: function(/*LatLng*/ center) {
return this.setView(center, this._zoom);
},
panBy: function(/*Point*/ offset) {
// replaced with animated panBy in Map.Animation.js
this.fire('movestart');
this._rawPanBy(offset);
this.fire('move');
this.fire('moveend');
return this;
},
addLayer: function(layer) {
var id = L.Util.stamp(layer);
if (this._layers[id]) return this;
this._layers[id] = layer;
if (layer.options && !isNaN(layer.options.maxZoom)) {
this._layersMaxZoom = Math.max(this._layersMaxZoom || 0, layer.options.maxZoom);
}
if (layer.options && !isNaN(layer.options.minZoom)) {
this._layersMinZoom = Math.min(this._layersMinZoom || Infinity, layer.options.minZoom);
}
//TODO getMaxZoom, getMinZoom in ILayer (instead of options)
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum++;
layer.on('load', this._onTileLayerLoad, this);
}
if (this.attributionControl && layer.getAttribution) {
this.attributionControl.addAttribution(layer.getAttribution());
}
var onMapLoad = function() {
layer.onAdd(this);
this.fire('layeradd', {layer: layer});
};
if (this._loaded) {
onMapLoad.call(this);
} else {
this.on('load', onMapLoad, this);
}
return this;
},
removeLayer: function(layer) {
var id = L.Util.stamp(layer);
if (this._layers[id]) {
layer.onRemove(this);
delete this._layers[id];
if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
this._tileLayersNum--;
layer.off('load', this._onTileLayerLoad, this);
}
if (this.attributionControl && layer.getAttribution) {
this.attributionControl.removeAttribution(layer.getAttribution());
}
this.fire('layerremove', {layer: layer});
}
return this;
},
invalidateSize: function() {
this._sizeChanged = true;
this.fire('move');
clearTimeout(this._sizeTimer);
this._sizeTimer = setTimeout(L.Util.bind(function() {
this.fire('moveend');
}, this), 200);
return this;
},
// public methods for getting map state
getCenter: function(/*Boolean*/ unbounded) {
var viewHalf = this.getSize().divideBy(2),
centerPoint = this._getTopLeftPoint().add(viewHalf);
return this.unproject(centerPoint, this._zoom, unbounded);
},
getZoom: function() {
return this._zoom;
},
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));
return new L.LatLngBounds(sw, ne);
},
getMinZoom: function() {
return isNaN(this.options.minZoom) ? this._layersMinZoom || 0 : this.options.minZoom;
},
getMaxZoom: function() {
return isNaN(this.options.maxZoom) ? this._layersMaxZoom || Infinity : this.options.maxZoom;
},
getBoundsZoom: function(/*LatLngBounds*/ bounds) {
var size = this.getSize(),
zoom = this.getMinZoom(),
maxZoom = this.getMaxZoom(),
ne = bounds.getNorthEast(),
sw = bounds.getSouthWest(),
boundsSize,
nePoint, swPoint;
do {
zoom++;
nePoint = this.project(ne, zoom);
swPoint = this.project(sw, zoom);
boundsSize = new L.Point(nePoint.x - swPoint.x, swPoint.y - nePoint.y);
} while ((boundsSize.x <= size.x) &&
(boundsSize.y <= size.y) && (zoom <= maxZoom));
return zoom - 1;
},
getSize: function() {
if (!this._size || this._sizeChanged) {
this._size = new L.Point(this._container.clientWidth, this._container.clientHeight);
this._sizeChanged = false;
}
return this._size;
},
getPixelBounds: function() {
var topLeftPoint = this._getTopLeftPoint(),
size = this.getSize();
return new L.Bounds(topLeftPoint, topLeftPoint.add(size));
},
getPixelOrigin: function() {
return this._initialTopLeftPoint;
},
getPanes: function() {
return this._panes;
},
// conversion methods
mouseEventToContainerPoint: function(/*MouseEvent*/ e) {
return L.DomEvent.getMousePosition(e, this._container);
},
mouseEventToLayerPoint: function(/*MouseEvent*/ e) {
return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
},
mouseEventToLatLng: function(/*MouseEvent*/ e) {
return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
},
containerPointToLayerPoint: function(/*Point*/ point) {
return point.subtract(L.DomUtil.getPosition(this._mapPane));
},
layerPointToContainerPoint: function(/*Point*/ point) {
return point.add(L.DomUtil.getPosition(this._mapPane));
},
layerPointToLatLng: function(/*Point*/ point) {
return this.unproject(point.add(this._initialTopLeftPoint));
},
latLngToLayerPoint: function(/*LatLng*/ latlng) {
return this.project(latlng)._subtract(this._initialTopLeftPoint);
},
project: function(/*LatLng*/ latlng, /*(optional) Number*/ zoom)/*-> Point*/ {
zoom = (typeof zoom == 'undefined' ? this._zoom : zoom);
return this.options.crs.latLngToPoint(latlng, this.options.scale(zoom));
},
unproject: function(/*Point*/ point, /*(optional) Number*/ zoom, /*(optional) Boolean*/ unbounded)/*-> Object*/ {
zoom = (typeof zoom == 'undefined' ? this._zoom : zoom);
return this.options.crs.pointToLatLng(point, this.options.scale(zoom), unbounded);
},
// private methods that modify map state
_initLayout: function() {
var container = this._container;
container.className += ' leaflet-container';
if (this.options.fadeAnimation) {
container.className += ' leaflet-fade-anim';
}
var position = L.DomUtil.getStyle(container, 'position');
if (position != 'absolute' && position != 'relative') {
container.style.position = 'relative';
}
this._initPanes();
if (this._initControlPos) this._initControlPos();
},
_initPanes: function() {
var panes = this._panes = {};
this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
this._objectsPane = panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
panes.shadowPane = this._createPane('leaflet-shadow-pane');
panes.overlayPane = this._createPane('leaflet-overlay-pane');
panes.markerPane = this._createPane('leaflet-marker-pane');
panes.popupPane = this._createPane('leaflet-popup-pane');
},
_createPane: function(className, container) {
return L.DomUtil.create('div', className, container || this._objectsPane);
},
_resetView: function(center, zoom, preserveMapOffset) {
var zoomChanged = (this._zoom != zoom);
this.fire('movestart');
this._zoom = zoom;
this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
if (!preserveMapOffset) {
L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
} else {
var offset = L.DomUtil.getPosition(this._mapPane);
this._initialTopLeftPoint._add(offset);
}
this._tileLayersToLoad = this._tileLayersNum;
this.fire('viewreset');
this.fire('move');
if (zoomChanged) { this.fire('zoomend'); }
this.fire('moveend');
if (!this._loaded) {
this._loaded = true;
this.fire('load');
}
},
_initLayers: function(layers) {
this._layers = {};
for (var i = 0, len = layers.length; i < len; i++) {
this.addLayer(layers[i]);
}
},
_initControls: function() {
if (this.options.zoomControl) {
this.addControl(new L.Control.Zoom());
}
if (this.options.attributionControl) {
this.attributionControl = new L.Control.Attribution();
this.addControl(this.attributionControl);
}
},
_rawPanBy: function(offset) {
var mapPaneOffset = L.DomUtil.getPosition(this._mapPane);
L.DomUtil.setPosition(this._mapPane, mapPaneOffset.subtract(offset));
},
// map events
_initEvents: function() {
L.DomEvent.addListener(this._container, 'click', this._onMouseClick, this);
var events = ['dblclick', 'mousedown', 'mouseenter', 'mouseleave', 'mousemove'];
for (var i = 0; i < events.length; i++) {
L.DomEvent.addListener(this._container, events[i], this._fireMouseEvent, this);
}
if (this.options.trackResize) {
L.DomEvent.addListener(window, 'resize', this.invalidateSize, this);
}
},
_onMouseClick: function(e) {
if (this.dragging && this.dragging.moved()) { return; }
this.fire('pre' + e.type);
this._fireMouseEvent(e);
},
_fireMouseEvent: function(e) {
var type = e.type;
type = (type == 'mouseenter' ? 'mouseover' : (type == 'mouseleave' ? 'mouseout' : type));
if (!this.hasEventListeners(type)) { return; }
this.fire(type, {
latlng: this.mouseEventToLatLng(e),
layerPoint: this.mouseEventToLayerPoint(e)
});
},
_initInteraction: function() {
var handlers = {
dragging: L.Handler.MapDrag,
touchZoom: L.Handler.TouchZoom,
doubleClickZoom: L.Handler.DoubleClickZoom,
scrollWheelZoom: L.Handler.ScrollWheelZoom,
shiftDragZoom: L.Handler.ShiftDragZoom
};
for (var i in handlers) {
if (handlers.hasOwnProperty(i) && handlers[i]) {
this[i] = new handlers[i](this);
if (this.options[i]) this[i].enable();
}
}
},
_onTileLayerLoad: function() {
// clear scaled tiles after all new tiles are loaded (for performance)
this._tileLayersToLoad--;
if (this._tileLayersNum && !this._tileLayersToLoad && this._tileBg) {
clearTimeout(this._clearTileBgTimer);
this._clearTileBgTimer = setTimeout(L.Util.bind(this._clearTileBg, this), 500);
}
},
// private methods for getting map state
_getTopLeftPoint: function() {
if (!this._loaded) throw new Error('Set map center and zoom first.');
var offset = L.DomUtil.getPosition(this._mapPane);
return this._initialTopLeftPoint.subtract(offset);
},
_getNewTopLeftPoint: function(center) {
var viewHalf = this.getSize().divideBy(2);
return this.project(center).subtract(viewHalf).round();
},
_limitZoom: function(zoom) {
var min = this.getMinZoom();
var max = this.getMaxZoom();
return Math.max(min, Math.min(max, zoom));
}
});

View File

@ -0,0 +1,50 @@
L.Map.include({
addControl: function(control) {
control.onAdd(this);
var pos = control.getPosition(),
corner = this._controlCorners[pos],
container = control.getContainer();
L.DomUtil.addClass(container, 'leaflet-control');
if (pos.indexOf('bottom') != -1) {
corner.insertBefore(container, corner.firstChild);
} else {
corner.appendChild(container);
}
return this;
},
removeControl: function(control) {
var pos = control.getPosition(),
corner = this._controlCorners[pos],
container = control.getContainer();
corner.removeChild(container);
if (control.onRemove) {
control.onRemove(this);
}
return this;
},
_initControlPos: function() {
var corners = this._controlCorners = {},
classPart = 'leaflet-',
top = classPart + 'top',
bottom = classPart + 'bottom',
left = classPart + 'left',
right = classPart + 'right',
controlContainer = L.DomUtil.create('div', classPart + 'control-container', this._container);
if (L.Browser.mobileWebkit) {
controlContainer.className += ' ' + classPart + 'big-buttons';
}
corners.topLeft = L.DomUtil.create('div', top + ' ' + left, controlContainer);
corners.topRight = L.DomUtil.create('div', top + ' ' + right, controlContainer);
corners.bottomLeft = L.DomUtil.create('div', bottom + ' ' + left, controlContainer);
corners.bottomRight = L.DomUtil.create('div', bottom + ' ' + right, controlContainer);
}
});

View File

@ -0,0 +1,69 @@
/*
* Provides L.Map with convenient shortcuts for W3C geolocation.
*/
L.Map.include({
locate: function(/*Object*/ options) {
// W3C Geolocation API Spec position options, http://dev.w3.org/geo/api/spec-source.html#position-options
var opts = {timeout: 10000};
L.Util.extend(opts, options);
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
L.Util.bind(this._handleGeolocationResponse, this),
L.Util.bind(this._handleGeolocationError, this),
opts);
} else {
this.fire('locationerror', {
code: 0,
message: "Geolocation not supported."
});
}
return this;
},
locateAndSetView: function(maxZoom, options) {
this._setViewOnLocate = true;
this._maxLocateZoom = maxZoom || Infinity;
return this.locate(options);
},
_handleGeolocationError: function(error) {
var c = error.code,
message = (c == 1 ? "permission denied" :
(c == 2 ? "position unavailable" : "timeout"));
if (this._setViewOnLocate) {
this.fitWorld();
this._setViewOnLocate = false;
}
this.fire('locationerror', {
code: c,
message: "Geolocation error: " + message + "."
});
},
_handleGeolocationResponse: function(pos) {
var latAccuracy = 180 * pos.coords.accuracy / 4e7,
lngAccuracy = latAccuracy * 2,
lat = pos.coords.latitude,
lng = pos.coords.longitude;
var sw = new L.LatLng(lat - latAccuracy, lng - lngAccuracy),
ne = new L.LatLng(lat + latAccuracy, lng + lngAccuracy),
bounds = new L.LatLngBounds(sw, ne);
if (this._setViewOnLocate) {
var zoom = Math.min(this.getBoundsZoom(bounds), this._maxLocateZoom);
this.setView(bounds.getCenter(), zoom);
this._setViewOnLocate = false;
}
this.fire('locationfound', {
latlng: new L.LatLng(lat, lng),
bounds: bounds,
accuracy: pos.coords.accuracy
});
}
});

View File

@ -0,0 +1,62 @@
L.Map.include(!(L.Transition && L.Transition.implemented()) ? {} : {
setView: function(center, zoom, forceReset) {
zoom = this._limitZoom(zoom);
var zoomChanged = (this._zoom != zoom);
if (this._loaded && !forceReset && this._layers) {
// difference between the new and current centers in pixels
var offset = this._getNewTopLeftPoint(center).subtract(this._getTopLeftPoint());
var done = (zoomChanged ?
!!this._zoomToIfCenterInView && this._zoomToIfCenterInView(center, zoom, offset) :
this._panByIfClose(offset));
// exit if animated pan or zoom started
if (done) { return this; }
}
// reset the map view
this._resetView(center, zoom);
return this;
},
panBy: function(offset) {
if (!this._panTransition) {
this._panTransition = new L.Transition(this._mapPane, {duration: 0.3});
this._panTransition.on('step', this._onPanTransitionStep, this);
this._panTransition.on('end', this._onPanTransitionEnd, this);
}
this.fire(this, 'movestart');
this._panTransition.run({
position: L.DomUtil.getPosition(this._mapPane).subtract(offset)
});
return this;
},
_onPanTransitionStep: function() {
this.fire('move');
},
_onPanTransitionEnd: function() {
this.fire('moveend');
},
_panByIfClose: function(offset) {
if (this._offsetIsWithinView(offset)) {
this.panBy(offset);
return true;
}
return false;
},
_offsetIsWithinView: function(offset, multiplyFactor) {
var m = multiplyFactor || 1,
size = this.getSize();
return (Math.abs(offset.x) <= size.x * m) &&
(Math.abs(offset.y) <= size.y * m);
}
});

View File

@ -0,0 +1,15 @@
L.Map.include({
openPopup: function(popup) {
this.closePopup();
this._popup = popup;
return this.addLayer(popup);
},
closePopup: function() {
if (this._popup) {
this.removeLayer(this._popup);
}
return this;
}
});

View File

@ -0,0 +1,124 @@
L.Map.include(!L.DomUtil.TRANSITION ? {} : {
_zoomToIfCenterInView: function(center, zoom, centerOffset) {
if (this._animatingZoom) { return true; }
if (!this.options.zoomAnimation) { return false; }
var zoomDelta = zoom - this._zoom,
scale = Math.pow(2, zoomDelta),
offset = centerOffset.divideBy(1 - 1/scale);
//if offset does not exceed half of the view
if (!this._offsetIsWithinView(offset, 1)) { return false; }
this._mapPane.className += ' leaflet-zoom-anim';
var centerPoint = this.containerPointToLayerPoint(this.getSize().divideBy(2)),
origin = centerPoint.add(offset);
this._prepareTileBg();
this._runAnimation(center, zoom, scale, origin);
return true;
},
_runAnimation: function(center, zoom, scale, origin) {
this._animatingZoom = true;
this._animateToCenter = center;
this._animateToZoom = zoom;
var transform = L.DomUtil.TRANSFORM;
//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)';
}
var scaleStr;
// Android doesn't like translate/scale chains, transformOrigin + scale works better but
// it breaks touch zoom which Anroid doesn't support anyway, so that's a really ugly hack
// TODO work around this prettier
if (L.Browser.android) {
this._tileBg.style[transform + 'Origin'] = origin.x + 'px ' + origin.y + 'px';
scaleStr = 'scale(' + scale + ')';
} else {
scaleStr = L.DomUtil.getScaleString(scale, origin);
}
L.Util.falseFn(this._tileBg.offsetWidth); //hack to make sure transform is updated before running animation
var options = {};
options[transform] = this._tileBg.style[transform] + ' ' + scaleStr;
this._tileBg.transition.run(options);
},
_prepareTileBg: function() {
if (!this._tileBg) {
this._tileBg = this._createPane('leaflet-tile-pane', this._mapPane);
this._tileBg.style.zIndex = 1;
}
var tilePane = this._tilePane,
tileBg = this._tileBg;
// prepare the background pane to become the main tile pane
//tileBg.innerHTML = '';
tileBg.style[L.DomUtil.TRANSFORM] = '';
tileBg.style.visibility = 'hidden';
// tells tile layers to reinitialize their containers
tileBg.empty = true;
tilePane.empty = false;
this._tilePane = this._panes.tilePane = tileBg;
this._tileBg = tilePane;
if (!this._tileBg.transition) {
this._tileBg.transition = new L.Transition(this._tileBg, {duration: 0.3, easing: 'cubic-bezier(0.25,0.1,0.25,0.75)'});
this._tileBg.transition.on('end', this._onZoomTransitionEnd, this);
}
this._stopLoadingBgTiles();
},
// stops loading all tiles in the background layer
_stopLoadingBgTiles: function() {
var tiles = [].slice.call(this._tileBg.getElementsByTagName('img'));
for (var i = 0, len = tiles.length; i < len; i++) {
if (!tiles[i].complete) {
tiles[i].src = '';
tiles[i].parentNode.removeChild(tiles[i]);
}
}
},
_onZoomTransitionEnd: function() {
this._restoreTileFront();
L.Util.falseFn(this._tileBg.offsetWidth);
this._resetView(this._animateToCenter, this._animateToZoom, true);
//TODO clear tileBg on map layersload
this._mapPane.className = this._mapPane.className.replace(' leaflet-zoom-anim', ''); //TODO toggleClass util
this._animatingZoom = false;
},
_restoreTileFront: function() {
this._tilePane.innerHTML = '';
this._tilePane.style.visibility = '';
this._tilePane.style.zIndex = 2;
this._tileBg.style.zIndex = 1;
},
_clearTileBg: function() {
if (!this._animatingZoom && !this.touchZoom._zooming) {
this._tileBg.innerHTML = '';
}
}
});

View File

@ -0,0 +1 @@
../../../extlib/leaflet/dist/