diff --git a/leaflet.polylineDecorator.js b/leaflet.polylineDecorator.js index 887b4b8..868a1f1 100644 --- a/leaflet.polylineDecorator.js +++ b/leaflet.polylineDecorator.js @@ -1,324 +1,326 @@ - -L.LineUtil.PolylineDecorator = { - computeAngle: function(a, b) { - return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI) + 90; - }, - - getPointPathPixelLength: function(pts) { - var nbPts = pts.length; - if(nbPts < 2) { - return 0; - } - var dist = 0, - prevPt = pts[0], - pt; - for(var i=1; i 0) { - // 3. consider only the rest of the path, starting at the previous point - var remainingPath = pts; - remainingPath = remainingPath.slice(previous.predecessor); - remainingPath[0] = previous.pt; - var remainingLength = this.getPointPathPixelLength(remainingPath); - // 4. project as a ratio of the remaining length, - // and repeat while there is room for another point of the pattern - while(repeatIntervalLength <= remainingLength) { - previous = this.interpolateOnPointPath(remainingPath, repeatIntervalLength/remainingLength); - positions.push(previous); - remainingPath = remainingPath.slice(previous.predecessor); - remainingPath[0] = previous.pt; - remainingLength = this.getPointPathPixelLength(remainingPath); - } - } - return positions; - }, - - /** - * pts: array of L.Point - * ratio: the ratio of the total length where the point should be computed - * Returns null if ll has less than 2 LatLng, or an object with the following properties: - * latLng: the LatLng of the interpolated point - * predecessor: the index of the previous vertex on the path - * heading: the heading of the path at this point, in degrees - */ - interpolateOnPointPath: function (pts, ratio) { - var nbVertices = pts.length; - - if (nbVertices < 2) { - return null; - } - // easy limit cases: ratio negative/zero => first vertex - if (ratio <= 0) { - return { - pt: pts[0], - predecessor: 0, - heading: this.computeAngle(pts[0], pts[1]) - }; - } - // ratio >=1 => last vertex - if (ratio >= 1) { - return { - pt: pts[nbVertices - 1], - predecessor: nbVertices - 1, - heading: this.computeAngle(pts[nbVertices - 2], pts[nbVertices - 1]) - }; - } - // 1-segment-only path => direct linear interpolation - if (nbVertices == 2) { - return { - pt: this.interpolateBetweenPoints(pts[0], pts[1], ratio), - predecessor: 0, - heading: this.computeAngle(pts[0], pts[1]) - }; - } - - var pathLength = this.getPointPathPixelLength(pts); - var a = pts[0], b = a, - ratioA = 0, ratioB = 0, - distB = 0; - // follow the path segments until we find the one - // on which the point must lie => [ab] - var i = 1; - for (; i < nbVertices && ratioB < ratio; i++) { - a = b; - ratioA = ratioB; - b = pts[i]; - distB += a.distanceTo(b); - ratioB = distB / pathLength; - } - - // compute the ratio relative to the segment [ab] - var segmentRatio = (ratio - ratioA) / (ratioB - ratioA); - - return { - pt: this.interpolateBetweenPoints(a, b, segmentRatio), - predecessor: i-2, - heading: this.computeAngle(a, b) - }; - }, - - /** - * Finds the point which lies on the segment defined by points A and B, - * at the given ratio of the distance from A to B, by linear interpolation. - */ - interpolateBetweenPoints: function (ptA, ptB, ratio) { - if(ptB.x != ptA.x) { - return new L.Point( - (ptA.x * (1 - ratio)) + (ratio * ptB.x), - (ptA.y * (1 - ratio)) + (ratio * ptB.y) - ); - } - // special case where points lie on the same vertical axis - return new L.Point(ptA.x, ptA.y + (ptB.y - ptA.y) * ratio); - } -}; -L.RotatedMarker = L.Marker.extend({ - options: { - angle: 0 - }, - - _setPos: function (pos) { - L.Marker.prototype._setPos.call(this, pos); - - if (L.DomUtil.TRANSFORM) { - // use the CSS transform rule if available - this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)'; - } else if(L.Browser.ie) { - // fallback for IE6, IE7, IE8 - var rad = this.options.angle * (Math.PI / 180), - costheta = Math.cos(rad), - sintheta = Math.sin(rad); - this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' + - costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')'; - } - } -}); - -L.rotatedMarker = function (pos, options) { - return new L.RotatedMarker(pos, options); -};/** -* Defines several classes of symbol factories, -* to be used with L.PolylineDecorator -*/ - -L.Symbol = L.Symbol || {}; - -/** -* A simple dash symbol, drawn as a Polyline. -* Can also be used for dots, if 'pixelSize' option is given the 0 value. -*/ -L.Symbol.Dash = L.Class.extend({ - isZoomDependant: true, - - options: { - pixelSize: 10, - pathOptions: { } - }, - - initialize: function (options) { - L.Util.setOptions(this, options); - this.options.pathOptions.clickable = false; - }, - - buildSymbol: function(dirPoint, latLngs, map, index, total) { - var opts = this.options, - d2r = Math.PI / 180; - - // for a dot, nothing more to compute - if(opts.pixelSize <= 1) { - return new L.Polyline([dirPoint.latLng, dirPoint.latLng], opts.pathOptions); - } - - var midPoint = map.project(dirPoint.latLng); - var angle = (-(dirPoint.heading - 90)) * d2r; - var a = new L.Point( - midPoint.x + opts.pixelSize * Math.cos(angle + Math.PI) / 2, - midPoint.y + opts.pixelSize * Math.sin(angle) / 2 - ); - // compute second point by central symmetry to avoid unecessary cos/sin - var b = midPoint.add(midPoint.subtract(a)); - return new L.Polyline([map.unproject(a), map.unproject(b)], opts.pathOptions); - } -}); - -L.Symbol.dash = function (options) { - return new L.Symbol.Dash(options); -}; - -L.Symbol.ArrowHead = L.Class.extend({ - isZoomDependant: true, - - options: { - polygon: true, - pixelSize: 10, - headAngle: 60, - pathOptions: { - stroke: false, - weight: 2 - } - }, - - initialize: function (options) { - L.Util.setOptions(this, options); - this.options.pathOptions.clickable = false; - }, - - buildSymbol: function(dirPoint, latLngs, map, index, total) { - var opts = this.options; - var path; - if(opts.polygon) { - path = new L.Polygon(this._buildArrowPath(dirPoint, map), opts.pathOptions); - } else { - path = new L.Polyline(this._buildArrowPath(dirPoint, map), opts.pathOptions); - } - return path; - }, - - _buildArrowPath: function (dirPoint, map) { - var d2r = Math.PI / 180; - var tipPoint = map.project(dirPoint.latLng); - var direction = (-(dirPoint.heading - 90)) * d2r; - var radianArrowAngle = this.options.headAngle / 2 * d2r; - - var headAngle1 = direction + radianArrowAngle, - headAngle2 = direction - radianArrowAngle; - var arrowHead1 = new L.Point( - tipPoint.x - this.options.pixelSize * Math.cos(headAngle1), - tipPoint.y + this.options.pixelSize * Math.sin(headAngle1)), - arrowHead2 = new L.Point( - tipPoint.x - this.options.pixelSize * Math.cos(headAngle2), - tipPoint.y + this.options.pixelSize * Math.sin(headAngle2)); - - return [ - map.unproject(arrowHead1), - dirPoint.latLng, - map.unproject(arrowHead2) - ]; - } -}); - -L.Symbol.arrowHead = function (options) { - return new L.Symbol.ArrowHead(options); -}; - -L.Symbol.Marker = L.Class.extend({ - isZoomDependant: false, - - options: { - markerOptions: { }, - rotate: false - }, - - initialize: function (options) { - L.Util.setOptions(this, options); - this.options.markerOptions.clickable = false; - this.options.markerOptions.draggable = false; - this.isZoomDependant = (L.Browser.ie && this.options.rotate); - }, - - buildSymbol: function(directionPoint, latLngs, map, index, total) { - if(!this.options.rotate) { - return new L.Marker(directionPoint.latLng, this.options.markerOptions); - } - else { - this.options.markerOptions.angle = directionPoint.heading; - return new L.RotatedMarker(directionPoint.latLng, this.options.markerOptions); - } - } -}); - -L.Symbol.marker = function (options) { - return new L.Symbol.Marker(options); -}; - - + +L.LineUtil.PolylineDecorator = { + computeAngle: function(a, b) { + return (Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI) + 90; + }, + + getPointPathPixelLength: function(pts) { + var nbPts = pts.length; + if(nbPts < 2) { + return 0; + } + var dist = 0, + prevPt = pts[0], + pt; + for(var i=1; i 0 && previous != null) { + // 3. consider only the rest of the path, starting at the previous point + var remainingPath = pts; + remainingPath = remainingPath.slice(previous.predecessor); + remainingPath[0] = previous.pt; + var remainingLength = this.getPointPathPixelLength(remainingPath); + // 4. project as a ratio of the remaining length, + // and repeat while there is room for another point of the pattern + while(repeatIntervalLength <= remainingLength) { + previous = this.interpolateOnPointPath(remainingPath, repeatIntervalLength/remainingLength); + positions.push(previous); + remainingPath = remainingPath.slice(previous.predecessor); + remainingPath[0] = previous.pt; + remainingLength = this.getPointPathPixelLength(remainingPath); + } + } + return positions; + }, + + /** + * pts: array of L.Point + * ratio: the ratio of the total length where the point should be computed + * Returns null if ll has less than 2 LatLng, or an object with the following properties: + * latLng: the LatLng of the interpolated point + * predecessor: the index of the previous vertex on the path + * heading: the heading of the path at this point, in degrees + */ + interpolateOnPointPath: function (pts, ratio) { + var nbVertices = pts.length; + + if (nbVertices < 2) { + return null; + } + // easy limit cases: ratio negative/zero => first vertex + if (ratio <= 0) { + return { + pt: pts[0], + predecessor: 0, + heading: this.computeAngle(pts[0], pts[1]) + }; + } + // ratio >=1 => last vertex + if (ratio >= 1) { + return { + pt: pts[nbVertices - 1], + predecessor: nbVertices - 1, + heading: this.computeAngle(pts[nbVertices - 2], pts[nbVertices - 1]) + }; + } + // 1-segment-only path => direct linear interpolation + if (nbVertices == 2) { + return { + pt: this.interpolateBetweenPoints(pts[0], pts[1], ratio), + predecessor: 0, + heading: this.computeAngle(pts[0], pts[1]) + }; + } + + var pathLength = this.getPointPathPixelLength(pts); + var a = pts[0], b = a, + ratioA = 0, ratioB = 0, + distB = 0; + // follow the path segments until we find the one + // on which the point must lie => [ab] + var i = 1; + for (; i < nbVertices && ratioB < ratio; i++) { + a = b; + ratioA = ratioB; + b = pts[i]; + distB += a.distanceTo(b); + ratioB = distB / pathLength; + } + + // compute the ratio relative to the segment [ab] + var segmentRatio = (ratio - ratioA) / (ratioB - ratioA); + + return { + pt: this.interpolateBetweenPoints(a, b, segmentRatio), + predecessor: i-2, + heading: this.computeAngle(a, b) + }; + }, + + /** + * Finds the point which lies on the segment defined by points A and B, + * at the given ratio of the distance from A to B, by linear interpolation. + */ + interpolateBetweenPoints: function (ptA, ptB, ratio) { + if(ptB.x != ptA.x) { + return new L.Point( + (ptA.x * (1 - ratio)) + (ratio * ptB.x), + (ptA.y * (1 - ratio)) + (ratio * ptB.y) + ); + } + // special case where points lie on the same vertical axis + return new L.Point(ptA.x, ptA.y + (ptB.y - ptA.y) * ratio); + } +}; +L.RotatedMarker = L.Marker.extend({ + options: { + angle: 0 + }, + + _setPos: function (pos) { + L.Marker.prototype._setPos.call(this, pos); + + if (L.DomUtil.TRANSFORM) { + // use the CSS transform rule if available + this._icon.style[L.DomUtil.TRANSFORM] += ' rotate(' + this.options.angle + 'deg)'; + } else if(L.Browser.ie) { + // fallback for IE6, IE7, IE8 + var rad = this.options.angle * (Math.PI / 180), + costheta = Math.cos(rad), + sintheta = Math.sin(rad); + this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' + + costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')'; + } + } +}); + +L.rotatedMarker = function (pos, options) { + return new L.RotatedMarker(pos, options); +};/** +* Defines several classes of symbol factories, +* to be used with L.PolylineDecorator +*/ + +L.Symbol = L.Symbol || {}; + +/** +* A simple dash symbol, drawn as a Polyline. +* Can also be used for dots, if 'pixelSize' option is given the 0 value. +*/ +L.Symbol.Dash = L.Class.extend({ + isZoomDependant: true, + + options: { + pixelSize: 10, + pathOptions: { } + }, + + initialize: function (options) { + L.Util.setOptions(this, options); + this.options.pathOptions.clickable = false; + }, + + buildSymbol: function(dirPoint, latLngs, map, index, total) { + var opts = this.options, + d2r = Math.PI / 180; + + // for a dot, nothing more to compute + if(opts.pixelSize <= 1) { + return new L.Polyline([dirPoint.latLng, dirPoint.latLng], opts.pathOptions); + } + + var midPoint = map.project(dirPoint.latLng); + var angle = (-(dirPoint.heading - 90)) * d2r; + var a = new L.Point( + midPoint.x + opts.pixelSize * Math.cos(angle + Math.PI) / 2, + midPoint.y + opts.pixelSize * Math.sin(angle) / 2 + ); + // compute second point by central symmetry to avoid unecessary cos/sin + var b = midPoint.add(midPoint.subtract(a)); + return new L.Polyline([map.unproject(a), map.unproject(b)], opts.pathOptions); + } +}); + +L.Symbol.dash = function (options) { + return new L.Symbol.Dash(options); +}; + +L.Symbol.ArrowHead = L.Class.extend({ + isZoomDependant: true, + + options: { + polygon: true, + pixelSize: 10, + headAngle: 60, + pathOptions: { + stroke: false, + weight: 2 + } + }, + + initialize: function (options) { + L.Util.setOptions(this, options); + this.options.pathOptions.clickable = false; + }, + + buildSymbol: function(dirPoint, latLngs, map, index, total) { + var opts = this.options; + var path; + if(opts.polygon) { + path = new L.Polygon(this._buildArrowPath(dirPoint, map), opts.pathOptions); + } else { + path = new L.Polyline(this._buildArrowPath(dirPoint, map), opts.pathOptions); + } + return path; + }, + + _buildArrowPath: function (dirPoint, map) { + var d2r = Math.PI / 180; + var tipPoint = map.project(dirPoint.latLng); + var direction = (-(dirPoint.heading - 90)) * d2r; + var radianArrowAngle = this.options.headAngle / 2 * d2r; + + var headAngle1 = direction + radianArrowAngle, + headAngle2 = direction - radianArrowAngle; + var arrowHead1 = new L.Point( + tipPoint.x - this.options.pixelSize * Math.cos(headAngle1), + tipPoint.y + this.options.pixelSize * Math.sin(headAngle1)), + arrowHead2 = new L.Point( + tipPoint.x - this.options.pixelSize * Math.cos(headAngle2), + tipPoint.y + this.options.pixelSize * Math.sin(headAngle2)); + + return [ + map.unproject(arrowHead1), + dirPoint.latLng, + map.unproject(arrowHead2) + ]; + } +}); + +L.Symbol.arrowHead = function (options) { + return new L.Symbol.ArrowHead(options); +}; + +L.Symbol.Marker = L.Class.extend({ + isZoomDependant: false, + + options: { + markerOptions: { }, + rotate: false + }, + + initialize: function (options) { + L.Util.setOptions(this, options); + this.options.markerOptions.clickable = false; + this.options.markerOptions.draggable = false; + this.isZoomDependant = (L.Browser.ie && this.options.rotate); + }, + + buildSymbol: function(directionPoint, latLngs, map, index, total) { + if(!this.options.rotate) { + return new L.Marker(directionPoint.latLng, this.options.markerOptions); + } + else { + this.options.markerOptions.angle = directionPoint.heading; + return new L.RotatedMarker(directionPoint.latLng, this.options.markerOptions); + } + } +}); + +L.Symbol.marker = function (options) { + return new L.Symbol.Marker(options); +}; + + L.PolylineDecorator = L.LayerGroup.extend({ options: { @@ -550,10 +552,13 @@ L.PolylineDecorator = L.LayerGroup.extend({ var directionPoints, symbols; for(var i=0; i < this._paths.length; i++) { directionPoints = this._getDirectionPoints(i, pattern); - symbols = this._buildSymbols(this._paths[i], pattern.symbolFactory, directionPoints); - for(var j=0; j < symbols.length; j++) { - this.addLayer(symbols[j]); + if (symbols[0] != null) { + symbols = this._buildSymbols(this._paths[i], pattern.symbolFactory, directionPoints); + for (var j = 0; j < symbols.length; j++) { + this.addLayer(symbols[j]); + } } + } },