diff --git a/CHANGELOG.md b/CHANGELOG.md
index 738a09a2..1174bdb0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,15 +1,32 @@
Leaflet Changelog
=================
-## 0.2 (master)
+## 0.3 (master)
-### Highlights
+### Major features
+
+ * Added **Canvas backend** for vector layers (polylines, polygons, circles). This enables vector support on Android < 3, and it can also be optionally preferred over SVG for a performance gain in some cases. Thanks to [@florianf](https://github.com/florianf) for a big part of this work.
+
+### Improvements
+
+ * Improved `LatLng` constructor to be more tolerant (and throw descriptive error if latitude or longitude can't be interpreted as a number). [#136](https://github.com/CloudMade/Leaflet/issues/136)
+
+### Bugfixes
+
+ * Fixed a bug where static properties of a child class would not override the parent ones.
+ * Fixed broken popup `closePopup` option (by [@jgerigmeyer](https://github.com/jgerigmeyer)).
+
+## 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.
- * Added **MultiPolyline** and **MultiPolygon** layers. [#77](https://github.com/CloudMade/Leaflet/issues/77)
- * Added **TileLayer.Canvas** for easy creation of canvas-based tile layers.
### Improvements
@@ -22,6 +39,9 @@ Leaflet Changelog
#### 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.
diff --git a/build/build.bat b/build/build.bat
index a09d2259..5e989565 100644
--- a/build/build.bat
+++ b/build/build.bat
@@ -38,12 +38,17 @@ java -jar ../lib/closure-compiler/compiler.jar ^
--js ../src/layer/marker/Marker.js ^
--js ../src/layer/marker/Marker.Popup.js ^
--js ../src/layer/vector/Path.js ^
---js ../src/layer/vector/Path.VML.js ^
--js ../src/layer/vector/Path.Popup.js ^
+--js ../src/layer/vector/Path.SVG.js ^
+--js ../src/layer/vector/Path.VML.js ^
+--js ../src/layer/vector/canvas/Path.Canvas.js ^
--js ../src/layer/vector/Polyline.js ^
+--js ../src/layer/vector/canvas/Polyline.Canvas.js ^
--js ../src/layer/vector/Polygon.js ^
+--js ../src/layer/vector/canvas/Polygon.Canvas.js ^
--js ../src/layer/vector/MultiPoly.js ^
--js ../src/layer/vector/Circle.js ^
+--js ../src/layer/vector/canvas/Circle.Canvas.js ^
--js ../src/layer/vector/CircleMarker.js ^
--js ../src/layer/GeoJSON.js ^
--js ../src/handler/Handler.js ^
diff --git a/debug/leaflet-include.js b/debug/leaflet-include.js
index 9ae8f458..db699cca 100644
--- a/debug/leaflet-include.js
+++ b/debug/leaflet-include.js
@@ -50,12 +50,17 @@
'layer/marker/Marker.Popup.js',
'layer/vector/Path.js',
- 'layer/vector/Path.VML.js',
'layer/vector/Path.Popup.js',
+ 'layer/vector/Path.SVG.js',
+ 'layer/vector/Path.VML.js',
+ 'layer/vector/canvas/Path.Canvas.js',
'layer/vector/Polyline.js',
+ 'layer/vector/canvas/Polyline.Canvas.js',
'layer/vector/Polygon.js',
+ 'layer/vector/canvas/Polygon.Canvas.js',
'layer/vector/MultiPoly.js',
'layer/vector/Circle.js',
+ 'layer/vector/canvas/Circle.Canvas.js',
'layer/vector/CircleMarker.js',
'layer/GeoJSON.js',
@@ -97,4 +102,4 @@
for (var i = 0; i < scripts.length; i++) {
document.writeln("");
}
-})();
\ No newline at end of file
+})();
diff --git a/debug/vector/vector-canvas.html b/debug/vector/vector-canvas.html
new file mode 100644
index 00000000..9e82c67a
--- /dev/null
+++ b/debug/vector/vector-canvas.html
@@ -0,0 +1,83 @@
+
+
+
+ Leaflet debug page
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dist/leaflet.js b/dist/leaflet.js
index d4aaa510..328bae6b 100644
--- a/dist/leaflet.js
+++ b/dist/leaflet.js
@@ -6,8 +6,8 @@
(function(a){var b={VERSION:"0.2",ROOT_URL:function(){for(var a=document.getElementsByTagName("script"),b=/^(.*\/)leaflet-?([\w-]*)\.js.*$/,e=0,f=a.length;e0},removeEventListener:function(a,b,c){if(!this.hasEventListeners(a))return this;for(var d=0,e=this._leaflet_events,f=e[a].length;d=b.lat&&a.lat<=c.lat&&d.lng>=b.lng&&a.lng<=c.lng}});L.Projection={};L.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(a){var b=L.LatLng.DEG_TO_RAD,c=this.MAX_LATITUDE,d=a.lng*b,a=Math.max(Math.min(c,a.lat),-c)*b,a=Math.log(Math.tan(Math.PI/4+a/2));return new L.Point(d,a)},unproject:function(a,b){var c=L.LatLng.RAD_TO_DEG;return new L.LatLng((2*Math.atan(Math.exp(a.y))-Math.PI/2)*c,a.x*c,b)}};L.Projection.LonLat={project:function(a){return new L.Point(a.lng,a.lat)},unproject:function(a,b){return new L.LatLng(a.y,a.x,b)}};L.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.3142,R_MAJOR:6378137,project:function(a){var b=L.LatLng.DEG_TO_RAD,c=this.MAX_LATITUDE,d=this.R_MAJOR,e=a.lng*b*d,a=Math.max(Math.min(c,a.lat),-c)*b,b=this.R_MINOR/d,b=Math.sqrt(1-b*b),c=b*Math.sin(a),c=Math.pow((1-c)/(1+c),b*0.5),a=-d*Math.log(Math.tan(0.5*(Math.PI*0.5-a))/c);return new L.Point(e,a)},unproject:function(a,b){for(var c=L.LatLng.RAD_TO_DEG,d=this.R_MAJOR,e=a.x*c/d,f=this.R_MINOR/d,f=Math.sqrt(1-f*f),d=Math.exp(-a.y/d),
g=Math.PI/2-2*Math.atan(d),h=15,j=0.1;Math.abs(j)>1.0E-7&&--h>0;)j=f*Math.sin(g),j=Math.PI/2-2*Math.atan(d*Math.pow((1-j)/(1+j),0.5*f))-g,g+=j;return new L.LatLng(g*c,e,b)}};L.CRS={latLngToPoint:function(a,b){return this.transformation._transform(this.projection.project(a),b)},pointToLatLng:function(a,b,c){return this.projection.unproject(this.transformation.untransform(a,b),c)},project:function(a){return this.projection.project(a)}};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(a){return this.projection.project(a).multiplyBy(6378137)}});L.CRS.EPSG900913=L.Util.extend({},L.CRS.EPSG3857,{code:"EPSG:900913"});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)});L.CRS.EPSG3395=L.Util.extend({},L.CRS,{code:"EPSG:3395",projection:L.Projection.Mercator,transformation:function(){var a=L.Projection.Mercator;return new L.Transformation(0.5/(Math.PI*a.R_MAJOR),0.5,-0.5/(Math.PI*a.R_MINOR),0.5)}()});L.LayerGroup=L.Class.extend({initialize:function(a){this._layers={};if(a)for(var b=0,c=a.length;bthis.options.maxWidth?this.options.maxWidth:a)+"px";this._container.style.whiteSpace="";this._containerWidth=this._container.offsetWidth},_updatePosition:function(){var a=this._map.latLngToLayerPoint(this._latlng);this._containerBottom=-a.y-this.options.offset.y;this._containerLeft=a.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){var a=this._container.offsetHeight,b=this._map.layerPointToContainerPoint(new L.Point(this._containerLeft,-a-this._containerBottom)),c=new L.Point(0,0),d=this.options.autoPanPadding,e=this._map.getSize();if(b.x<0)c.x=b.x-d.x;if(b.x+this._containerWidth>e.x)c.x=b.x+this._containerWidth-e.x+d.x;if(b.y<0)c.y=b.y-d.y;if(b.y+a>e.y)c.y=b.y+a-e.y+d.y;(c.x||c.y)&&this._map.panBy(c)}},_onCloseButtonClick:function(a){this._close();
L.DomEvent.stop(a)}});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(a){if(a)this.iconUrl=a},createIcon:function(){return this._createIcon("icon")},createShadow:function(){return this._createIcon("shadow")},_createIcon:function(a){var b=this[a+"Size"],c=this[a+"Url"],d=this._createImg(c);if(!c)return null;d.className="leaflet-marker-"+
a;d.style.marginLeft=-this.iconAnchor.x+"px";d.style.marginTop=-this.iconAnchor.y+"px";if(b)d.style.width=b.x+"px",d.style.height=b.y+"px";return d},_createImg:function(a){var b;L.Browser.ie6?(b=document.createElement("div"),b.style.filter='progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+a+'")'):(b=document.createElement("img"),b.src=a);return b}});L.Marker=L.Class.extend({includes:L.Mixin.Events,options:{icon:new L.Icon,title:"",clickable:!0,draggable:!1},initialize:function(a,b){L.Util.setOptions(this,b);this._latlng=a},onAdd:function(a){this._map=a;this._initIcon();a.on("viewreset",this._reset,this);this._reset()},onRemove:function(a){this._removeIcon();a.off("viewreset",this._reset,this)},getLatLng:function(){return this._latlng},setLatLng:function(a){this._latlng=a;this._reset()},setIcon:function(a){this._removeIcon();this._icon=this._shadow=
null;this.options.icon=a;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);this._shadow&&this._map._panes.shadowPane.appendChild(this._shadow)},_removeIcon:function(){this._map._panes.markerPane.removeChild(this._icon);this._shadow&&this._map._panes.shadowPane.removeChild(this._shadow)},
_reset:function(){var a=this._map.latLngToLayerPoint(this._latlng).round();L.DomUtil.setPosition(this._icon,a);this._shadow&&L.DomUtil.setPosition(this._shadow,a);this._icon.style.zIndex=a.y},_initInteraction:function(){if(this.options.clickable){this._icon.className+=" leaflet-clickable";L.DomEvent.addListener(this._icon,"click",this._onMouseClick,this);for(var a=["dblclick","mousedown","mouseover","mouseout"],b=0;b';a=a.firstChild;a.style.behavior="url(#default#VML)";return a&&typeof a.adj=="object"}();
-L.Path=L.Path.SVG||!L.Path.VML?L.Path:L.Path.extend({statics:{CLIP_PADDING:0.02},_createElement:function(){try{return document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(a){return document.createElement("')}}catch(a){return function(a){return document.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initRoot:function(){if(!this._map._pathRoot)this._map._pathRoot=document.createElement("div"),this._map._pathRoot.className=
+new L.Handler.MarkerDrag(this),this.options.draggable&&this.dragging.enable()},_onMouseClick:function(a){L.DomEvent.stopPropagation(a);(!this.dragging||!this.dragging.moved())&&this.fire(a.type)},_fireMouseEvent:function(a){this.fire(a.type);L.DomEvent.stopPropagation(a)}});L.Marker.include({openPopup:function(){this._popup.setLatLng(this._latlng);this._map.openPopup(this._popup);return this},closePopup:function(){this._popup&&this._popup._close()},bindPopup:function(a,b){b=L.Util.extend({offset:this.options.icon.popupAnchor},b);this._popup=new L.Popup(b);this._popup.setContent(a);this.on("click",this.openPopup,this);return this}});L.Path=L.Class.extend({includes:[L.Mixin.Events],statics:{CLIP_PADDING:0.5},options:{stroke:!0,color:"#0033ff",weight:5,opacity:0.5,fill:!1,fillColor:null,fillOpacity:0.2,clickable:!0,updateOnMoveEnd:!1},initialize:function(a){L.Util.setOptions(this,a)},onAdd:function(a){this._map=a;this._initElements();this._initEvents();this.projectLatlngs();this._updatePath();a.on("viewreset",this.projectLatlngs,this);this._updateTrigger=this.options.updateOnMoveEnd?"moveend":"viewreset";a.on(this._updateTrigger,
+this._updatePath,this)},onRemove:function(a){a._pathRoot.removeChild(this._container);a.off("viewreset",this._projectLatlngs,this);a.off(this._updateTrigger,this._updatePath,this)},projectLatlngs:function(){},setStyle:function(a){L.Util.setOptions(this,a);this._container&&this._updateStyle()},_initElements:function(){this._initRoot();this._initPath();this._initStyle()},_updateViewport:function(){var a=L.Path.CLIP_PADDING,b=this._map.getSize(),c=L.DomUtil.getPosition(this._map._mapPane).multiplyBy(-1).subtract(b.multiplyBy(a)),
+a=c.add(b.multiplyBy(1+a*2));this._map._pathViewport=new L.Bounds(c,a)},_redraw:function(){this.projectLatlngs();this._updatePath()}});L.Path.include({bindPopup:function(a,b){if(!this._popup||this._popup.options!==b)this._popup=new L.Popup(b);this._popup.setContent(a);if(!this._openPopupAdded)this.on("click",this._openPopup,this),this._openPopupAdded=!0;return this},_openPopup:function(a){this._popup.setLatLng(a.latlng);this._map.openPopup(this._popup)}});L.Path.SVG_NS="http://www.w3.org/2000/svg";L.Browser.svg=!(!document.createElementNS||!document.createElementNS(L.Path.SVG_NS,"svg").createSVGRect);
+L.Path=L.Path.extend({statics:{SVG:L.Browser.svg},getPathString:function(){},_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 a=this._map._pathViewport,b=a.min,c=a.max,a=c.x-b.x,c=c.y-
+b.y,d=this._map._pathRoot,e=this._map._panes.overlayPane;L.Browser.mobileWebkit&&e.removeChild(d);L.DomUtil.setPosition(d,b);d.setAttribute("width",a);d.setAttribute("height",c);d.setAttribute("viewBox",[b.x,b.y,a,c].join(" "));L.Browser.mobileWebkit&&e.appendChild(d)},_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(){this.options.stroke&&
+(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round"));this.options.fill?this._path.setAttribute("fill-rule","evenodd"):this._path.setAttribute("fill","none");this._updateStyle()},_updateStyle:function(){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));this.options.fill&&(this._path.setAttribute("fill",
+this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity))},_updatePath:function(){var a=this.getPathString();a||(a="M0 0");this._path.setAttribute("d",a)},_createElement:function(a){return document.createElementNS(L.Path.SVG_NS,a)},_initEvents:function(){if(this.options.clickable){L.Browser.vml||this._path.setAttribute("class","leaflet-clickable");L.DomEvent.addListener(this._container,"click",this._onMouseClick,this);for(var a=["dblclick","mousedown",
+"mouseover","mouseout"],b=0;b';a=a.firstChild;a.style.behavior="url(#default#VML)";return a&&typeof a.adj=="object"}();
+L.Path=L.Browser.svg||!L.Browser.vml?L.Path:L.Path.extend({statics:{VML:!0,CLIP_PADDING:0.02},_createElement:function(){try{return document.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(a){return document.createElement("')}}catch(a){return function(a){return document.createElement("<"+a+' 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(){this.options.stroke?
(this._stroke=this._createElement("stroke"),this._stroke.endcap="round",this._container.appendChild(this._stroke)):this._container.stroked=!1;this.options.fill?(this._container.filled=!0,this._fill=this._createElement("fill"),this._container.appendChild(this._fill)):this._container.filled=!1;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()+" ";this._container.style.display=""}});L.Path.include({bindPopup:function(a,b){if(!this._popup||this._popup.options!==b)this._popup=new L.Popup(b);this._popup.setContent(a);if(!this._openPopupAdded)this.on("click",this._openPopup,this),this._openPopupAdded=!0;return this},_openPopup:function(a){this._popup.setLatLng(a.latlng);this._map.openPopup(this._popup)}});L.Polyline=L.Path.extend({initialize:function(a,b){L.Path.prototype.initialize.call(this,b);this._latlngs=a},options:{smoothFactor:1,noClip:!1,updateOnMoveEnd:!0},projectLatlngs:function(){this._originalPoints=[];for(var a=0,b=this._latlngs.length;aa.y!=e.y>a.y&&a.x<(e.x-d.x)*(a.y-d.y)/(e.y-d.y)+d.x&&(b=!b)}return b}});(function(){function a(a){return L.FeatureGroup.extend({initialize:function(c,d){this._layers={};for(var e=0,f=c.length;ea.max.x||c.y-b>a.max.y||c.x+b Class*/ {
// add class name
//proto.className = props;
+ //inherit parent's statics
+ for (var i in this) {
+ if (this.hasOwnProperty(i) && i != 'prototype') {
+ NewClass[i] = this[i];
+ }
+ }
+
// mix static properties into the class
if (props.statics) {
L.Util.extend(NewClass, props.statics);
@@ -55,12 +62,5 @@ L.Class.extend = function(/*Object*/ props) /*-> Class*/ {
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;
};
\ No newline at end of file
diff --git a/src/geo/LatLng.js b/src/geo/LatLng.js
index fb916547..978ea44f 100644
--- a/src/geo/LatLng.js
+++ b/src/geo/LatLng.js
@@ -2,7 +2,14 @@
CM.LatLng represents a geographical point with latitude and longtitude coordinates.
*/
-L.LatLng = function(/*Number*/ lat, /*Number*/ lng, /*Boolean*/ noWrap) {
+L.LatLng = function(/*Number*/ rawLat, /*Number*/ rawLng, /*Boolean*/ noWrap) {
+ var lat = parseFloat(rawLat),
+ lng = parseFloat(rawLng);
+
+ if (isNaN(lat) || isNaN(lng)) {
+ throw new Error('Invalid LatLng object: (' + rawLat + ', ' + rawLng + ')');
+ }
+
if (noWrap !== true) {
lat = Math.max(Math.min(lat, 90), -90); // clamp latitude into -90..90
lng = (lng + 180) % 360 + (lng < -180 ? 180 : -180); // wrap longtitude into -180..180
diff --git a/src/layer/Popup.js b/src/layer/Popup.js
index 4cb14e3c..1c47ccee 100644
--- a/src/layer/Popup.js
+++ b/src/layer/Popup.js
@@ -1,27 +1,26 @@
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);
@@ -30,22 +29,22 @@ L.Popup = L.Class.extend({
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) {
@@ -53,7 +52,7 @@ L.Popup = L.Class.extend({
}
return this;
},
-
+
setContent: function(content) {
this._content = content;
if (this._opened) {
@@ -61,43 +60,45 @@ L.Popup = L.Class.extend({
}
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);
-
+
+ if (this.options.closeButton) {
+ 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 {
@@ -105,41 +106,41 @@ L.Popup = L.Class.extend({
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,
+ 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;
}
@@ -152,12 +153,12 @@ L.Popup = L.Class.extend({
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);
diff --git a/src/layer/vector/Circle.js b/src/layer/vector/Circle.js
index c737c191..5c86ffa2 100644
--- a/src/layer/vector/Circle.js
+++ b/src/layer/vector/Circle.js
@@ -38,7 +38,9 @@ L.Circle = L.Path.extend({
var p = this._point,
r = this._radius;
- if (L.Path.SVG) {
+ if (this._checkIfEmpty()) { return ''; }
+
+ if (L.Browser.svg) {
return "M" + p.x + "," + (p.y - r) +
"A" + r + "," + r + ",0,1,1," +
(p.x - 0.1) + "," + (p.y - r) + " z";
@@ -47,5 +49,14 @@ L.Circle = L.Path.extend({
r = Math.round(r);
return "AL " + p.x + "," + p.y + " " + r + "," + r + " 0," + (65535 * 360);
}
+ },
+
+ _checkIfEmpty: function() {
+ var vp = this._map._pathViewport,
+ r = this._radius,
+ p = this._point;
+
+ return p.x - r > vp.max.x || p.y - r > vp.max.y ||
+ p.x + r < vp.min.x || p.y + r < vp.min.y;
}
-});
\ No newline at end of file
+});
diff --git a/src/layer/vector/Path.SVG.js b/src/layer/vector/Path.SVG.js
new file mode 100644
index 00000000..d6129dc0
--- /dev/null
+++ b/src/layer/vector/Path.SVG.js
@@ -0,0 +1,130 @@
+
+L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
+
+L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
+
+L.Path = L.Path.extend({
+ statics: {
+ SVG: L.Browser.svg
+ },
+
+ getPathString: function() {
+ // form path string here
+ },
+
+ _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); }
+ },
+
+ _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.Browser.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);
+ }
+});
\ No newline at end of file
diff --git a/src/layer/vector/Path.VML.js b/src/layer/vector/Path.VML.js
index 8481d994..ff7f69f8 100644
--- a/src/layer/vector/Path.VML.js
+++ b/src/layer/vector/Path.VML.js
@@ -3,7 +3,7 @@
* Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
*/
-L.Path.VML = (function() {
+L.Browser.vml = (function() {
var d = document.createElement('div'), s;
d.innerHTML = '';
s = d.firstChild;
@@ -12,8 +12,9 @@ L.Path.VML = (function() {
return (s && (typeof s.adj == 'object'));
})();
-L.Path = L.Path.SVG || !L.Path.VML ? L.Path : L.Path.extend({
+L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
statics: {
+ VML: true,
CLIP_PADDING: 0.02
},
diff --git a/src/layer/vector/Path.js b/src/layer/vector/Path.js
index 3d4837cc..3cf1108e 100644
--- a/src/layer/vector/Path.js
+++ b/src/layer/vector/Path.js
@@ -5,19 +5,11 @@
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
- };
- })(),
+ statics: {
+ // 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,
@@ -62,13 +54,9 @@ L.Path = L.Class.extend({
// do all projection stuff here
},
- getPathString: function() {
- // form path string here
- },
-
setStyle: function(style) {
L.Util.setOptions(this, style);
- if (this._path) {
+ if (this._container) {
this._updateStyle();
}
},
@@ -79,39 +67,6 @@ L.Path = L.Class.extend({
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(),
@@ -123,83 +78,6 @@ L.Path = L.Class.extend({
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();
diff --git a/src/layer/vector/Polygon.js b/src/layer/vector/Polygon.js
index 52bf2d6b..40c684a5 100644
--- a/src/layer/vector/Polygon.js
+++ b/src/layer/vector/Polygon.js
@@ -53,6 +53,6 @@ L.Polygon = L.Polyline.extend({
_getPathPartStr: function(points) {
var str = L.Polyline.prototype._getPathPartStr.call(this, points);
- return str + (L.Path.SVG ? 'z' : 'x');
+ return str + (L.Browser.svg ? 'z' : 'x');
}
});
\ No newline at end of file
diff --git a/src/layer/vector/canvas/Circle.Canvas.js b/src/layer/vector/canvas/Circle.Canvas.js
new file mode 100644
index 00000000..aecd4386
--- /dev/null
+++ b/src/layer/vector/canvas/Circle.Canvas.js
@@ -0,0 +1,18 @@
+/*
+ * Circle canvas specific drawing parts.
+ */
+
+L.Circle.include(!L.Path.CANVAS ? {} : {
+ _drawPath: function() {
+ var p = this._point;
+ this._ctx.beginPath();
+ this._ctx.arc(p.x, p.y, this._radius, 0, Math.PI * 2);
+ },
+
+ _containsPoint: function(p) {
+ var center = this._point,
+ w2 = this.options.stroke ? this.options.weight / 2 : 0;
+
+ return (p.distanceTo(center) <= this._radius + w2);
+ }
+});
diff --git a/src/layer/vector/canvas/Path.Canvas.js b/src/layer/vector/canvas/Path.Canvas.js
new file mode 100644
index 00000000..a6cff273
--- /dev/null
+++ b/src/layer/vector/canvas/Path.Canvas.js
@@ -0,0 +1,142 @@
+/*
+ * Vector rendering for all browsers that support canvas.
+ */
+
+L.Browser.canvas = (function() {
+ return !!document.createElement('canvas').getContext;
+})();
+
+L.Path = (L.Path.SVG && !window.L_PREFER_CANVAS) || !L.Browser.canvas ? L.Path : L.Path.extend({
+ statics: {
+ //CLIP_PADDING: 0.02, // not sure if there's a need to set it to a small value
+ CANVAS: true,
+ SVG: false
+ },
+
+ options: {
+ updateOnMoveEnd: true
+ },
+
+ _initElements: function() {
+ this._initRoot();
+ },
+
+ _initRoot: function() {
+ var root = this._map._pathRoot,
+ ctx = this._map._canvasCtx;
+
+ if (!root) {
+ root = this._map._pathRoot = document.createElement("canvas");
+ ctx = this._map._canvasCtx = root.getContext('2d');
+
+ ctx.lineCap = "round";
+ ctx.lineJoin = "round";
+
+ this._map._panes.overlayPane.appendChild(root);
+
+ this._map.on('moveend', this._updateCanvasViewport, this);
+ this._updateCanvasViewport();
+ }
+
+ this._ctx = ctx;
+ },
+
+ _updateStyle: function() {
+ if (this.options.stroke) {
+ this._ctx.lineWidth = this.options.weight;
+ this._ctx.strokeStyle = this.options.color;
+ }
+ if (this.options.fill) {
+ this._ctx.fillStyle = this.options.fillColor || this.options.color;
+ }
+ },
+
+ _drawPath: function() {
+ var i, j, len, len2, point, drawMethod;
+
+ this._ctx.beginPath();
+
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ for (j = 0, len2 = this._parts[i].length; j < len2; j++) {
+ point = this._parts[i][j];
+ drawMethod = (j === 0 ? 'move' : 'line') + 'To';
+
+ this._ctx[drawMethod](point.x, point.y);
+ }
+ // TODO refactor ugly hack
+ if (this instanceof L.Polygon) {
+ this._ctx.closePath();
+ }
+ }
+ },
+
+ _checkIfEmpty: function() {
+ return !this._parts.length;
+ },
+
+ _updatePath: function() {
+ if (this._checkIfEmpty()) {
+ console.log('not drawn');
+ return;
+ }
+
+ this._drawPath();
+
+ this._ctx.save();
+
+ this._updateStyle();
+
+ var opacity = this.options.opacity,
+ fillOpacity = this.options.fillOpacity;
+
+ if (this.options.fill) {
+ if (fillOpacity < 1) {
+ this._ctx.globalAlpha = fillOpacity;
+ }
+ this._ctx.fill();
+ }
+
+ if (this.options.stroke) {
+ if (opacity < 1) {
+ this._ctx.globalAlpha = opacity;
+ }
+ this._ctx.stroke();
+ }
+
+ this._ctx.restore();
+
+ /*
+ * TODO not sure if possible to implement, but a great optimization would be to do
+ * 1 fill/stroke for all features with equal style instead of 1 for each feature
+ */
+ },
+
+ _updateCanvasViewport: function() {
+ this._updateViewport();
+
+ var vp = this._map._pathViewport,
+ min = vp.min,
+ size = vp.max.subtract(min),
+ root = this._map._pathRoot;
+
+ //TODO check if it's works properly on mobile webkit
+ L.DomUtil.setPosition(root, min);
+ root.width = size.x;
+ root.height = size.y;
+ root.getContext('2d').translate(-min.x, -min.y);
+ },
+
+ _initEvents: function() {
+ if (this.options.clickable) {
+ // TODO hand cursor
+ // TODO mouseover, mouseout, dblclick
+ this._map.on('click', this._onClick, this);
+ }
+ },
+
+ _onClick: function(e) {
+ if (this._containsPoint(e.layerPoint)) {
+ this.fire('click', e);
+ }
+ }
+});
diff --git a/src/layer/vector/canvas/Polygon.Canvas.js b/src/layer/vector/canvas/Polygon.Canvas.js
new file mode 100644
index 00000000..23782e36
--- /dev/null
+++ b/src/layer/vector/canvas/Polygon.Canvas.js
@@ -0,0 +1,34 @@
+
+L.Polygon.include(!L.Path.CANVAS ? {} : {
+ _containsPoint: function(p) {
+ var inside = false,
+ part, p1, p2,
+ i, j, k,
+ len, len2;
+
+ // TODO optimization: check if within bounds first
+
+ if (L.Polyline.prototype._containsPoint.call(this, p, true)) {
+ // click on polygon border
+ return true;
+ }
+
+ // ray casting algorithm for detecting if point is in polygon
+
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ part = this._parts[i];
+
+ for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+ p1 = part[j];
+ p2 = part[k];
+
+ if (((p1.y > p.y) != (p2.y > p.y)) &&
+ (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
+ inside = !inside;
+ }
+ }
+ }
+
+ return inside;
+ }
+});
\ No newline at end of file
diff --git a/src/layer/vector/canvas/Polyline.Canvas.js b/src/layer/vector/canvas/Polyline.Canvas.js
new file mode 100644
index 00000000..961c314b
--- /dev/null
+++ b/src/layer/vector/canvas/Polyline.Canvas.js
@@ -0,0 +1,25 @@
+
+L.Polyline.include(!L.Path.CANVAS ? {} : {
+ _containsPoint: function(p, closed) {
+ var i, j, k, len, len2, dist, part,
+ w = this.options.weight / 2;
+
+ if (L.Browser.touch) {
+ w += 10; // polyline click tolerance on touch devices
+ }
+
+ for (i = 0, len = this._parts.length; i < len; i++) {
+ part = this._parts[i];
+ for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
+ if (!closed && (j === 0)) { continue; }
+
+ dist = L.LineUtil.pointToSegmentDistance(p, part[k], part[j]);
+
+ if (dist <= w) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+});
\ No newline at end of file