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