leaflet-polylinedecorator
Advanced tools
Comparing version 0.7.2 to 1.0.1
@@ -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 | ||
![screenshot](https://raw.github.com/bbecquet/Leaflet.PolylineDecorator/master/screenshot.png "Screenshot showing different applications of the library") | ||
## 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 | ||
![screenshot](https://raw.github.com/bbecquet/Leaflet.PolylineDecorator/master/screenshot.png "Screenshot showing different applications of the library") | ||
* `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
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
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
1188
1
70
442197
2