leaflet-polylinedecorator
Advanced tools
Comparing version
@@ -28,4 +28,4 @@ | ||
// --- Polygon --- | ||
var polygon = L.polygon([[54, -6], [55, -7], [56, -2], [55, 1], [53, 0], [54, -6]], {color: "#ff7800", weight: 1}).addTo(map); | ||
// --- Polygon, with an inner ring --- | ||
var polygon = L.polygon([[[54, -6], [55, -7], [56, -2], [55, 1], [53, 0]], [[54, -3], [54, -2], [55, -1], [55, -5]]], {color: "#ff7800", weight: 1}).addTo(map); | ||
var pd = L.polylineDecorator(polygon, { | ||
@@ -55,3 +55,3 @@ patterns: [ | ||
}).addTo(map); | ||
// --- Example with a rotated marker --- | ||
@@ -88,15 +88,3 @@ var pathPattern = L.polylineDecorator( | ||
}).addTo(map); | ||
// --- Example with a MultiPolygon --- | ||
var multiCoords2 = [ | ||
[[55.4788, 4.1748], [53.7487, 4.5263], [52.4560, 7.3388], [56.3165, 7.8662]], | ||
[[53.9302, 9.2724] , [52.8027, 9.8876], [52.1604, 12.0849], [53.5141, 14.5019], [54.9523, 14.3261], [55.5037, 10.5908]] | ||
]; | ||
var multiPl = L.multiPolygon(multiCoords2, {weight: 0, fillOpacity: 0}).addTo(map); | ||
L.polylineDecorator(multiPl, { | ||
patterns: [ | ||
{offset: 0, repeat: 10, symbol: L.Symbol.dash({pixelSize: 0, pathOptions: {color: '#080'}})} | ||
] | ||
}).addTo(map); | ||
} | ||
@@ -40,7 +40,9 @@ | ||
* offsetRatio: the ratio of the total pixel length where the pattern will start | ||
* endOffsetRatio: the ratio of the total pixel length where the pattern will end | ||
* repeatRatio: the ratio of the total pixel length between two points of the pattern | ||
* map: the map, to access the current projection state | ||
*/ | ||
projectPatternOnPath: function (path, offsetRatio, repeatRatio, map) { | ||
projectPatternOnPath: function (path, offsetRatio, endOffsetRatio, repeatRatio, map) { | ||
var pathAsPoints = [], i; | ||
for(i=0, l=path.length; i<l; i++) { | ||
@@ -50,3 +52,3 @@ pathAsPoints[i] = map.project(path[i]); | ||
// project the pattern as pixel points | ||
var pattern = this.projectPatternOnPointPath(pathAsPoints, offsetRatio, repeatRatio); | ||
var pattern = this.projectPatternOnPointPath(pathAsPoints, offsetRatio, endOffsetRatio, repeatRatio); | ||
// and convert it to latlngs; | ||
@@ -59,8 +61,10 @@ for(i=0, l=pattern.length; i<l; i++) { | ||
projectPatternOnPointPath: function (pts, offsetRatio, repeatRatio) { | ||
projectPatternOnPointPath: function (pts, offsetRatio, endOffsetRatio, repeatRatio) { | ||
var positions = []; | ||
// 1. compute the absolute interval length in pixels | ||
var repeatIntervalLength = this.getPointPathPixelLength(pts) * repeatRatio; | ||
// 2. find the starting point by using the offsetRatio | ||
// 2. find the starting point by using the offsetRatio and find the last pixel using endOffsetRatio | ||
var previous = this.interpolateOnPointPath(pts, offsetRatio); | ||
var endOffsetPixels = endOffsetRatio > 0 ? this.getPointPathPixelLength(pts) * endOffsetRatio : 0; | ||
positions.push(previous); | ||
@@ -71,7 +75,10 @@ if(repeatRatio > 0) { | ||
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) { | ||
while(repeatIntervalLength <= remainingLength-endOffsetPixels) { | ||
previous = this.interpolateOnPointPath(remainingPath, repeatIntervalLength/remainingLength); | ||
@@ -166,160 +173,3 @@ positions.push(previous); | ||
}; | ||
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 * L.LatLng.DEG_TO_RAD, | ||
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; | ||
// 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)) * L.LatLng.DEG_TO_RAD; | ||
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 tipPoint = map.project(dirPoint.latLng); | ||
var direction = (-(dirPoint.heading - 90)) * L.LatLng.DEG_TO_RAD; | ||
var radianArrowAngle = this.options.headAngle / 2 * L.LatLng.DEG_TO_RAD; | ||
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({ | ||
@@ -341,3 +191,3 @@ options: { | ||
* array of LatLng, array of 2-number arrays, Polyline, Polygon, | ||
* array of one of the previous, MultiPolyline, MultiPolygon. | ||
* array of one of the previous. | ||
*/ | ||
@@ -347,8 +197,3 @@ _initPaths: function(p) { | ||
var isPolygon = false; | ||
if(p instanceof L.MultiPolyline || (isPolygon = (p instanceof L.MultiPolygon))) { | ||
var lines = p.getLatLngs(); | ||
for(var i=0; i<lines.length; i++) { | ||
this._initPath(lines[i], isPolygon); | ||
} | ||
} else if(p instanceof L.Polyline) { | ||
if(p instanceof L.Polyline) { | ||
this._initPath(p.getLatLngs(), (p instanceof L.Polygon)); | ||
@@ -368,3 +213,3 @@ } else if(L.Util.isArray(p) && p.length > 0) { | ||
return(L.Util.isArray(ll) && ll.length > 0 && ( | ||
ll[0] instanceof L.LatLng || | ||
ll[0] instanceof L.LatLng || | ||
(L.Util.isArray(ll[0]) && ll[0].length == 2 && typeof ll[0][0] === 'number') | ||
@@ -391,3 +236,3 @@ )); | ||
} | ||
}, | ||
}, | ||
@@ -405,2 +250,3 @@ _initPatterns: function() { | ||
pattern.isOffsetInPixels || | ||
pattern.isEndOffsetInPixels || | ||
pattern.isRepeatInPixels || | ||
@@ -412,3 +258,3 @@ pattern.symbolFactory.isZoomDependant; | ||
/** | ||
* Changes the patterns used by this decorator | ||
* Changes the patterns used by this decorator | ||
* and redraws the new one. | ||
@@ -423,3 +269,3 @@ */ | ||
/** | ||
* Changes the patterns used by this decorator | ||
* Changes the patterns used by this decorator | ||
* and redraws the new one. | ||
@@ -440,5 +286,6 @@ */ | ||
isOffsetInPixels: false, | ||
isEndOffsetInPixels: false, | ||
isRepeatInPixels: false | ||
}; | ||
// Parse offset and repeat values, managing the two cases: | ||
@@ -449,7 +296,13 @@ // absolute (in pixels) or relative (in percentage of the polyline length) | ||
} else { | ||
pattern.offset = parseFloat(patternDef.offset); | ||
pattern.offset = patternDef.offset ? parseFloat(patternDef.offset) : 0; | ||
pattern.isOffsetInPixels = (pattern.offset > 0); | ||
} | ||
if(typeof patternDef.endOffset === 'string' && patternDef.endOffset.indexOf('%') != -1) { | ||
pattern.endOffset = parseFloat(patternDef.endOffset) / 100; | ||
} else { | ||
pattern.endOffset = patternDef.endOffset ? parseFloat(patternDef.endOffset) : 0; | ||
pattern.isEndOffsetInPixels = (pattern.endOffset > 0); | ||
} | ||
if(typeof patternDef.repeat === 'string' && patternDef.repeat.indexOf('%') != -1) { | ||
@@ -461,5 +314,3 @@ pattern.repeat = parseFloat(patternDef.repeat) / 100; | ||
} | ||
// TODO: 0 => not pixel dependant => 0% | ||
return(pattern); | ||
@@ -507,3 +358,3 @@ }, | ||
* that define positions and directions of the symbols | ||
* on the path | ||
* on the path | ||
*/ | ||
@@ -517,3 +368,3 @@ _getDirectionPoints: function(pathIndex, pattern) { | ||
var offset, repeat, pathPixelLength = null, latLngs = this._paths[pathIndex]; | ||
var offset, endOffset, repeat, pathPixelLength = null, latLngs = this._paths[pathIndex]; | ||
if(pattern.isOffsetInPixels) { | ||
@@ -525,12 +376,18 @@ pathPixelLength = L.LineUtil.PolylineDecorator.getPixelLength(latLngs, this._map); | ||
} | ||
if(pattern.isEndOffsetInPixels) { | ||
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.LineUtil.PolylineDecorator.getPixelLength(latLngs, this._map); | ||
endOffset = pattern.endOffset/pathPixelLength; | ||
} else { | ||
endOffset = pattern.endOffset; | ||
} | ||
if(pattern.isRepeatInPixels) { | ||
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.LineUtil.PolylineDecorator.getPixelLength(latLngs, this._map); | ||
repeat = pattern.repeat/pathPixelLength; | ||
repeat = pattern.repeat/pathPixelLength; | ||
} else { | ||
repeat = pattern.repeat; | ||
} | ||
dirPoints = L.LineUtil.PolylineDecorator.projectPatternOnPath(latLngs, offset, repeat, this._map); | ||
dirPoints = L.LineUtil.PolylineDecorator.projectPatternOnPath(latLngs, offset, endOffset, repeat, this._map); | ||
// save in cache to avoid recomputing this | ||
pattern.cache[zoom][pathIndex] = dirPoints; | ||
return dirPoints; | ||
@@ -545,6 +402,6 @@ }, | ||
}, | ||
/** | ||
* "Soft" redraw, called internally for example on zoom changes, | ||
* keeping the cache. | ||
* keeping the cache. | ||
*/ | ||
@@ -554,3 +411,3 @@ _softRedraw: function() { | ||
}, | ||
_redraw: function(clearCache) { | ||
@@ -567,3 +424,3 @@ if(this._map === null) | ||
}, | ||
/** | ||
@@ -598,1 +455,179 @@ * Draw a single pattern | ||
}; | ||
L.RotatedMarker = L.Marker.extend({ | ||
options: { | ||
angle: 0 | ||
}, | ||
statics: { | ||
TRANSFORM_ORIGIN: L.DomUtil.testProp( | ||
['transformOrigin', 'WebkitTransformOrigin', 'OTransformOrigin', 'MozTransformOrigin', 'msTransformOrigin']) | ||
}, | ||
_initIcon: function() { | ||
L.Marker.prototype._initIcon.call(this); | ||
this._icon.style[L.RotatedMarker.TRANSFORM_ORIGIN] = '50% 50%'; | ||
}, | ||
_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 + ')'; | ||
} | ||
}, | ||
setAngle: function (ang) { | ||
this.options.angle = ang; | ||
} | ||
}); | ||
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 + (this.options.angleCorrection || 0); | ||
return new L.RotatedMarker(directionPoint.latLng, this.options.markerOptions); | ||
} | ||
} | ||
}); | ||
L.Symbol.marker = function (options) { | ||
return new L.Symbol.Marker(options); | ||
}; | ||
{ | ||
"name": "leaflet-polylinedecorator", | ||
"description": "A plug-in for the Leaflet map library to define patterns (like dashes, arrows, icons, etc.) on Polylines", | ||
"keywords": ["leaflet", "map", "polyline", "decorator", "patterns", "overlay"], | ||
"version": "0.7.2", | ||
"version": "1.0.1", | ||
"repository": "bbecquet/Leaflet.PolylineDecorator", | ||
"main": "leaflet.polylineDecorator.js", | ||
"devDependencies": { | ||
"gulp": "^3.9.0", | ||
"gulp-concat": "^2.6.0" | ||
}, | ||
"license": "MIT" | ||
} |
@@ -5,2 +5,8 @@ # Leaflet PolylineDecorator | ||
## Compatibility with Leaflet versions | ||
The development version of the plugin (on the `master` branch) is targeted at the 1.x version of Leaflet. | ||
For a version of the plugin compatible with the 0.7.x Leaflet release, use the `leaflet-0.7.2` branch. | ||
## Features | ||
@@ -13,21 +19,49 @@ | ||
## Screenshot | ||
 | ||
## Usage | ||
```javascript | ||
var polyline = L.polyline([...]).addTo(map); | ||
var decorator = L.polylineDecorator(polyline, { | ||
patterns: [ | ||
// define a pattern of 10px-wide dashes, repeated every 20px on the line | ||
{offset: 0, repeat: '20px', symbol: new L.Symbol.Dash({pixelSize: 10})} | ||
] | ||
}).addTo(map); | ||
``` | ||
To create a decorator and add it to the map: `L.polylineDecorator(latlngs, options).addTo(map);` | ||
The `polyline` parameter can be a single array of `L.LatLng` or, with Leaflet's simplified syntax, an array of 2-cells arrays of coordinates. | ||
It is useful if you don't want to actually display a polyline, but just a pattern following coordinates, like a dotted line. | ||
* `latlngs` can be one of the following types: | ||
## Screenshot | ||
* `L.Polyline` | ||
* `L.Polygon` | ||
* an array of `L.LatLng`, or with Leaflet's simplified syntax, an array of 2-cells arrays of coordinates (useful if you just want to draw patterns following coordinates, but not the line itself) | ||
* an array of any of these previous types, to apply the same patterns to multiple lines | ||
 | ||
* `options` has a single property `patterns`, which is an array of `Pattern` objects. | ||
### `Pattern` definition | ||
Property | Type | Required | Description | ||
--- | --- | --- | --- | ||
`offset`| *see below* | No | Offset of the first pattern symbol, from the start point of the line. Default: 0. | ||
`endOffset`| *see below* | No | Minimum offset of the last pattern symbol, from the end point of the line. Default: 0. | ||
`repeat`| *see below* | Yes | Repetition interval of the pattern symbols. Defines the distance between each consecutive symbol's anchor point. | ||
`symbol`| Symbol factory | Yes | Instance of a symbol factory class. | ||
`offset`, `endOffset` and `repeat` can be each defined as a number, in pixels, or in percentage of the line's length, as a string (ex: `'10%'`). | ||
### Methods | ||
Method | Description | ||
--- | --- | ||
`setPaths(latlngs)` | Changes the path(s) the decorator applies to. `latlngs` can be all the types supported by the constructor. Useful for example if you remove polyline from a set, or coordinates change. | ||
`setPatterns(<Pattern[]> patterns)` | Changes the decorator's pattern definitions, and update the symbols accordingly. | ||
## Example | ||
```javascript | ||
var polyline = L.polyline([...]).addTo(map); | ||
var decorator = L.polylineDecorator(polyline, { | ||
patterns: [ | ||
// defines a pattern of 10px-wide dashes, repeated every 20px on the line | ||
{offset: 0, repeat: 20, symbol: L.Symbol.dash({pixelSize: 10})} | ||
] | ||
}).addTo(map); | ||
``` | ||
## Performance note | ||
@@ -38,9 +72,1 @@ | ||
In cases where it's applicable (dash patterns), you should probably use instead the `dashArray` property of `L.Path`, as it's natively drawn by the browser. | ||
## TODO | ||
* Documentation | ||
* Optimize rendering and mem footprint | ||
* Other symbol types | ||
* Animations(?) | ||
@@ -40,7 +40,9 @@ | ||
* offsetRatio: the ratio of the total pixel length where the pattern will start | ||
* endOffsetRatio: the ratio of the total pixel length where the pattern will end | ||
* repeatRatio: the ratio of the total pixel length between two points of the pattern | ||
* map: the map, to access the current projection state | ||
*/ | ||
projectPatternOnPath: function (path, offsetRatio, repeatRatio, map) { | ||
projectPatternOnPath: function (path, offsetRatio, endOffsetRatio, repeatRatio, map) { | ||
var pathAsPoints = [], i; | ||
for(i=0, l=path.length; i<l; i++) { | ||
@@ -50,3 +52,3 @@ pathAsPoints[i] = map.project(path[i]); | ||
// project the pattern as pixel points | ||
var pattern = this.projectPatternOnPointPath(pathAsPoints, offsetRatio, repeatRatio); | ||
var pattern = this.projectPatternOnPointPath(pathAsPoints, offsetRatio, endOffsetRatio, repeatRatio); | ||
// and convert it to latlngs; | ||
@@ -59,8 +61,10 @@ for(i=0, l=pattern.length; i<l; i++) { | ||
projectPatternOnPointPath: function (pts, offsetRatio, repeatRatio) { | ||
projectPatternOnPointPath: function (pts, offsetRatio, endOffsetRatio, repeatRatio) { | ||
var positions = []; | ||
// 1. compute the absolute interval length in pixels | ||
var repeatIntervalLength = this.getPointPathPixelLength(pts) * repeatRatio; | ||
// 2. find the starting point by using the offsetRatio | ||
// 2. find the starting point by using the offsetRatio and find the last pixel using endOffsetRatio | ||
var previous = this.interpolateOnPointPath(pts, offsetRatio); | ||
var endOffsetPixels = endOffsetRatio > 0 ? this.getPointPathPixelLength(pts) * endOffsetRatio : 0; | ||
positions.push(previous); | ||
@@ -71,7 +75,10 @@ if(repeatRatio > 0) { | ||
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) { | ||
while(repeatIntervalLength <= remainingLength-endOffsetPixels) { | ||
previous = this.interpolateOnPointPath(remainingPath, repeatIntervalLength/remainingLength); | ||
@@ -165,2 +172,2 @@ positions.push(previous); | ||
} | ||
}; | ||
}; |
@@ -18,3 +18,3 @@ | ||
* array of LatLng, array of 2-number arrays, Polyline, Polygon, | ||
* array of one of the previous, MultiPolyline, MultiPolygon. | ||
* array of one of the previous. | ||
*/ | ||
@@ -24,8 +24,3 @@ _initPaths: function(p) { | ||
var isPolygon = false; | ||
if(p instanceof L.MultiPolyline || (isPolygon = (p instanceof L.MultiPolygon))) { | ||
var lines = p.getLatLngs(); | ||
for(var i=0; i<lines.length; i++) { | ||
this._initPath(lines[i], isPolygon); | ||
} | ||
} else if(p instanceof L.Polyline) { | ||
if(p instanceof L.Polyline) { | ||
this._initPath(p.getLatLngs(), (p instanceof L.Polygon)); | ||
@@ -45,3 +40,3 @@ } else if(L.Util.isArray(p) && p.length > 0) { | ||
return(L.Util.isArray(ll) && ll.length > 0 && ( | ||
ll[0] instanceof L.LatLng || | ||
ll[0] instanceof L.LatLng || | ||
(L.Util.isArray(ll[0]) && ll[0].length == 2 && typeof ll[0][0] === 'number') | ||
@@ -68,3 +63,3 @@ )); | ||
} | ||
}, | ||
}, | ||
@@ -82,2 +77,3 @@ _initPatterns: function() { | ||
pattern.isOffsetInPixels || | ||
pattern.isEndOffsetInPixels || | ||
pattern.isRepeatInPixels || | ||
@@ -89,3 +85,3 @@ pattern.symbolFactory.isZoomDependant; | ||
/** | ||
* Changes the patterns used by this decorator | ||
* Changes the patterns used by this decorator | ||
* and redraws the new one. | ||
@@ -100,3 +96,3 @@ */ | ||
/** | ||
* Changes the patterns used by this decorator | ||
* Changes the patterns used by this decorator | ||
* and redraws the new one. | ||
@@ -117,5 +113,6 @@ */ | ||
isOffsetInPixels: false, | ||
isEndOffsetInPixels: false, | ||
isRepeatInPixels: false | ||
}; | ||
// Parse offset and repeat values, managing the two cases: | ||
@@ -126,7 +123,13 @@ // absolute (in pixels) or relative (in percentage of the polyline length) | ||
} else { | ||
pattern.offset = parseFloat(patternDef.offset); | ||
pattern.offset = patternDef.offset ? parseFloat(patternDef.offset) : 0; | ||
pattern.isOffsetInPixels = (pattern.offset > 0); | ||
} | ||
if(typeof patternDef.endOffset === 'string' && patternDef.endOffset.indexOf('%') != -1) { | ||
pattern.endOffset = parseFloat(patternDef.endOffset) / 100; | ||
} else { | ||
pattern.endOffset = patternDef.endOffset ? parseFloat(patternDef.endOffset) : 0; | ||
pattern.isEndOffsetInPixels = (pattern.endOffset > 0); | ||
} | ||
if(typeof patternDef.repeat === 'string' && patternDef.repeat.indexOf('%') != -1) { | ||
@@ -138,5 +141,3 @@ pattern.repeat = parseFloat(patternDef.repeat) / 100; | ||
} | ||
// TODO: 0 => not pixel dependant => 0% | ||
return(pattern); | ||
@@ -184,3 +185,3 @@ }, | ||
* that define positions and directions of the symbols | ||
* on the path | ||
* on the path | ||
*/ | ||
@@ -194,3 +195,3 @@ _getDirectionPoints: function(pathIndex, pattern) { | ||
var offset, repeat, pathPixelLength = null, latLngs = this._paths[pathIndex]; | ||
var offset, endOffset, repeat, pathPixelLength = null, latLngs = this._paths[pathIndex]; | ||
if(pattern.isOffsetInPixels) { | ||
@@ -202,12 +203,18 @@ pathPixelLength = L.LineUtil.PolylineDecorator.getPixelLength(latLngs, this._map); | ||
} | ||
if(pattern.isEndOffsetInPixels) { | ||
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.LineUtil.PolylineDecorator.getPixelLength(latLngs, this._map); | ||
endOffset = pattern.endOffset/pathPixelLength; | ||
} else { | ||
endOffset = pattern.endOffset; | ||
} | ||
if(pattern.isRepeatInPixels) { | ||
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.LineUtil.PolylineDecorator.getPixelLength(latLngs, this._map); | ||
repeat = pattern.repeat/pathPixelLength; | ||
repeat = pattern.repeat/pathPixelLength; | ||
} else { | ||
repeat = pattern.repeat; | ||
} | ||
dirPoints = L.LineUtil.PolylineDecorator.projectPatternOnPath(latLngs, offset, repeat, this._map); | ||
dirPoints = L.LineUtil.PolylineDecorator.projectPatternOnPath(latLngs, offset, endOffset, repeat, this._map); | ||
// save in cache to avoid recomputing this | ||
pattern.cache[zoom][pathIndex] = dirPoints; | ||
return dirPoints; | ||
@@ -222,6 +229,6 @@ }, | ||
}, | ||
/** | ||
* "Soft" redraw, called internally for example on zoom changes, | ||
* keeping the cache. | ||
* keeping the cache. | ||
*/ | ||
@@ -231,3 +238,3 @@ _softRedraw: function() { | ||
}, | ||
_redraw: function(clearCache) { | ||
@@ -244,3 +251,3 @@ if(this._map === null) | ||
}, | ||
/** | ||
@@ -247,0 +254,0 @@ * Draw a single pattern |
@@ -5,5 +5,17 @@ L.RotatedMarker = L.Marker.extend({ | ||
}, | ||
statics: { | ||
TRANSFORM_ORIGIN: L.DomUtil.testProp( | ||
['transformOrigin', 'WebkitTransformOrigin', 'OTransformOrigin', 'MozTransformOrigin', 'msTransformOrigin']) | ||
}, | ||
_initIcon: function() { | ||
L.Marker.prototype._initIcon.call(this); | ||
this._icon.style[L.RotatedMarker.TRANSFORM_ORIGIN] = '50% 50%'; | ||
}, | ||
_setPos: function (pos) { | ||
L.Marker.prototype._setPos.call(this, pos); | ||
if (L.DomUtil.TRANSFORM) { | ||
@@ -14,8 +26,12 @@ // use the CSS transform rule if available | ||
// fallback for IE6, IE7, IE8 | ||
var rad = this.options.angle * L.LatLng.DEG_TO_RAD, | ||
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 + ')'; | ||
this._icon.style.filter += ' progid:DXImageTransform.Microsoft.Matrix(sizingMethod=\'auto expand\', M11=' + | ||
costheta + ', M12=' + (-sintheta) + ', M21=' + sintheta + ', M22=' + costheta + ')'; | ||
} | ||
}, | ||
setAngle: function (ang) { | ||
this.options.angle = ang; | ||
} | ||
@@ -26,2 +42,2 @@ }); | ||
return new L.RotatedMarker(pos, options); | ||
}; | ||
}; |
@@ -26,3 +26,4 @@ /** | ||
buildSymbol: function(dirPoint, latLngs, map, index, total) { | ||
var opts = this.options; | ||
var opts = this.options, | ||
d2r = Math.PI / 180; | ||
@@ -35,3 +36,3 @@ // for a dot, nothing more to compute | ||
var midPoint = map.project(dirPoint.latLng); | ||
var angle = (-(dirPoint.heading - 90)) * L.LatLng.DEG_TO_RAD; | ||
var angle = (-(dirPoint.heading - 90)) * d2r; | ||
var a = new L.Point( | ||
@@ -81,5 +82,6 @@ midPoint.x + opts.pixelSize * Math.cos(angle + Math.PI) / 2, | ||
_buildArrowPath: function (dirPoint, map) { | ||
var d2r = Math.PI / 180; | ||
var tipPoint = map.project(dirPoint.latLng); | ||
var direction = (-(dirPoint.heading - 90)) * L.LatLng.DEG_TO_RAD; | ||
var radianArrowAngle = this.options.headAngle / 2 * L.LatLng.DEG_TO_RAD; | ||
var direction = (-(dirPoint.heading - 90)) * d2r; | ||
var radianArrowAngle = this.options.headAngle / 2 * d2r; | ||
@@ -127,3 +129,3 @@ var headAngle1 = direction + radianArrowAngle, | ||
else { | ||
this.options.markerOptions.angle = directionPoint.heading; | ||
this.options.markerOptions.angle = directionPoint.heading + (this.options.angleCorrection || 0); | ||
return new L.RotatedMarker(directionPoint.latLng, this.options.markerOptions); | ||
@@ -130,0 +132,0 @@ } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
15
7.14%1188
2.86%1
-50%70
59.09%442197
-0.77%2
Infinity%