Socket
Socket
Sign inDemoInstall

leaflet-polylinedecorator

Package Overview
Dependencies
1
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.2.0 to 1.3.0

rollup.config.js

2

bower.json
{
"name": "leaflet-polylinedecorator",
"main": "leaflet.polylineDecorator.js",
"version": "1.1.0",
"version": "1.3.0",
"authors": [

@@ -6,0 +6,0 @@ "Benjamin Becquet"

@@ -0,2 +1,334 @@

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
typeof define === 'function' && define.amd ? define(factory) :
(factory());
}(this, (function () { 'use strict';
function computeAngle(a, b) {
return Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI + 90;
}
function getPointPathPixelLength(pts) {
return pts.reduce(function (distance, pt, i) {
return i === 0 ? 0 : distance + pt.distanceTo(pts[i - 1]);
}, 0);
}
function getPixelLength(pl, map) {
var latLngs = pl instanceof L.Polyline ? pl.getLatLngs() : pl;
var points = latLngs.map(function (latLng) {
return map.project(latLng);
});
return getPointPathPixelLength(points);
}
/**
* path: array of L.LatLng
* ratios is an object with the following fields:
* offset: the ratio of the total pixel length where the pattern will start
* endOffset: the ratio of the total pixel length where the pattern will end
* repeat: the ratio of the total pixel length between two points of the pattern
* map: the map, to access the current projection state
*/
function projectPatternOnPath(path, ratios, map) {
var pathAsPoints = path.map(function (latLng) {
return map.project(latLng);
});
// project the pattern as pixel points
var pattern = projectPatternOnPointPath(pathAsPoints, ratios);
// and convert it to latlngs;
pattern.forEach(function (point) {
point.latLng = map.unproject(point.pt);
});
return pattern;
}
function projectPatternOnPointPath(pts, _ref) {
var offset = _ref.offset,
endOffset = _ref.endOffset,
repeat = _ref.repeat;
var positions = [];
// 1. compute the absolute interval length in pixels
var repeatIntervalLength = getPointPathPixelLength(pts) * repeat;
// 2. find the starting point by using the offset and find the last pixel using endOffset
var previous = interpolateOnPointPath(pts, offset);
var endOffsetPixels = endOffset > 0 ? getPointPathPixelLength(pts) * endOffset : 0;
positions.push(previous);
if (repeat > 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 = 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 - endOffsetPixels) {
previous = interpolateOnPointPath(remainingPath, repeatIntervalLength / remainingLength);
positions.push(previous);
remainingPath = remainingPath.slice(previous.predecessor);
remainingPath[0] = previous.pt;
remainingLength = 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
*/
function interpolateOnPointPath(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: computeAngle(pts[0], pts[1])
};
}
// ratio >=1 => last vertex
if (ratio >= 1) {
return {
pt: pts[nbVertices - 1],
predecessor: nbVertices - 1,
heading: computeAngle(pts[nbVertices - 2], pts[nbVertices - 1])
};
}
// 1-segment-only path => direct linear interpolation
if (nbVertices == 2) {
return {
pt: interpolateBetweenPoints(pts[0], pts[1], ratio),
predecessor: 0,
heading: computeAngle(pts[0], pts[1])
};
}
var pathLength = 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: interpolateBetweenPoints(a, b, segmentRatio),
predecessor: i - 2,
heading: 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.
*/
function interpolateBetweenPoints(ptA, ptB, ratio) {
if (ptB.x != ptA.x) {
return 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 L.point(ptA.x, ptA.y + (ptB.y - ptA.y) * ratio);
}
(function() {
// save these original methods before they are overwritten
var proto_initIcon = L.Marker.prototype._initIcon;
var proto_setPos = L.Marker.prototype._setPos;
var oldIE = (L.DomUtil.TRANSFORM === 'msTransform');
L.Marker.addInitHook(function () {
var iconOptions = this.options.icon && this.options.icon.options;
var iconAnchor = iconOptions && this.options.icon.options.iconAnchor;
if (iconAnchor) {
iconAnchor = (iconAnchor[0] + 'px ' + iconAnchor[1] + 'px');
}
this.options.rotationOrigin = this.options.rotationOrigin || iconAnchor || 'center bottom' ;
this.options.rotationAngle = this.options.rotationAngle || 0;
// Ensure marker keeps rotated during dragging
this.on('drag', function(e) { e.target._applyRotation(); });
});
L.Marker.include({
_initIcon: function() {
proto_initIcon.call(this);
},
_setPos: function (pos) {
proto_setPos.call(this, pos);
this._applyRotation();
},
_applyRotation: function () {
if(this.options.rotationAngle) {
this._icon.style[L.DomUtil.TRANSFORM+'Origin'] = this.options.rotationOrigin;
if(oldIE) {
// for IE 9, use the 2D rotation
this._icon.style[L.DomUtil.TRANSFORM] = 'rotate(' + this.options.rotationAngle + 'deg)';
} else {
// for modern browsers, prefer the 3D accelerated version
this._icon.style[L.DomUtil.TRANSFORM] += ' rotateZ(' + this.options.rotationAngle + 'deg)';
}
}
},
setRotationAngle: function(angle) {
this.options.rotationAngle = angle;
this.update();
return this;
},
setRotationOrigin: function(origin) {
this.options.rotationOrigin = origin;
this.update();
return this;
}
});
})();
// enable rotationAngle and rotationOrigin support on L.Marker
/**
* 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 initialize(options) {
L.Util.setOptions(this, options);
this.options.pathOptions.clickable = false;
},
buildSymbol: function buildSymbol(dirPoint, latLngs, map, index, total) {
var opts = this.options;
var d2r = Math.PI / 180;
// for a dot, nothing more to compute
if (opts.pixelSize <= 1) {
return L.polyline([dirPoint.latLng, dirPoint.latLng], opts.pathOptions);
}
var midPoint = map.project(dirPoint.latLng);
var angle = -(dirPoint.heading - 90) * d2r;
var a = 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 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 initialize(options) {
L.Util.setOptions(this, options);
this.options.pathOptions.clickable = false;
},
buildSymbol: function buildSymbol(dirPoint, latLngs, map, index, total) {
return this.options.polygon ? L.polygon(this._buildArrowPath(dirPoint, map), this.options.pathOptions) : L.polyline(this._buildArrowPath(dirPoint, map), this.options.pathOptions);
},
_buildArrowPath: function _buildArrowPath(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;
var headAngle2 = direction - radianArrowAngle;
var arrowHead1 = L.point(tipPoint.x - this.options.pixelSize * Math.cos(headAngle1), tipPoint.y + this.options.pixelSize * Math.sin(headAngle1));
var arrowHead2 = 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 initialize(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 buildSymbol(directionPoint, latLngs, map, index, total) {
if (this.options.rotate) {
this.options.markerOptions.rotationAngle = directionPoint.heading + (this.options.angleCorrection || 0);
}
return L.marker(directionPoint.latLng, this.options.markerOptions);
}
});
L.Symbol.marker = function (options) {
return new L.Symbol.Marker(options);
};
L.PolylineDecorator = L.FeatureGroup.extend({

@@ -7,3 +339,3 @@ options: {

initialize: function(paths, options) {
initialize: function initialize(paths, options) {
L.FeatureGroup.prototype.initialize.call(this);

@@ -21,12 +353,13 @@ L.Util.setOptions(this, options);

*/
_initPaths: function(p) {
_initPaths: function _initPaths(p) {
var _this = this;
this._paths = [];
var isPolygon = false;
if(p instanceof L.Polyline) {
this._initPath(p.getLatLngs(), (p instanceof L.Polygon));
} else if(L.Util.isArray(p) && p.length > 0) {
if(p[0] instanceof L.Polyline) {
for(var i=0; i<p.length; i++) {
this._initPath(p[i].getLatLngs(), (p[i] instanceof L.Polygon));
}
if (p instanceof L.Polyline) {
this._initPath(p.getLatLngs(), p instanceof L.Polygon);
} else if (L.Util.isArray(p) && p.length > 0) {
if (p[0] instanceof L.Polyline) {
p.forEach(function (singleP) {
_this._initPath(singleP, single instanceof L.Polygon);
});
} else {

@@ -38,22 +371,15 @@ this._initPath(p);

_isCoordArray: function(ll) {
return(L.Util.isArray(ll) && ll.length > 0 && (
ll[0] instanceof L.LatLng ||
(L.Util.isArray(ll[0]) && ll[0].length == 2 && typeof ll[0][0] === 'number')
));
_isCoordArray: function _isCoordArray(ll) {
return L.Util.isArray(ll) && ll.length > 0 && (ll[0] instanceof L.LatLng || L.Util.isArray(ll[0]) && ll[0].length == 2 && typeof ll[0][0] === 'number');
},
_initPath: function(path, isPolygon) {
var latLngs;
_initPath: function _initPath(path, isPolygon) {
// It may still be an array of array of coordinates
// (ex: polygon with rings)
if(this._isCoordArray(path)) {
latLngs = [path];
} else {
latLngs = path;
}
for(var i=0; i<latLngs.length; i++) {
var latLngs = this._isCoordArray(path) ? [path] : path;
for (var i = 0; i < latLngs.length; i++) {
// As of Leaflet >= v0.6, last polygon vertex (=first) isn't repeated.
// Our algorithm needs it, so we add it back explicitly.
if(isPolygon) {
if (isPolygon) {
latLngs[i].push(latLngs[i][0]);

@@ -65,17 +391,12 @@ }

_initPatterns: function() {
this._isZoomDependant = false;
_initPatterns: function _initPatterns() {
var _this2 = this;
this._patterns = [];
var pattern;
var pattern = void 0;
// parse pattern definitions and precompute some values
for(var i=0;i<this.options.patterns.length;i++) {
pattern = this._parsePatternDef(this.options.patterns[i]);
this._patterns.push(pattern);
// determines if we have to recompute the pattern on each zoom change
this._isZoomDependant = this._isZoomDependant ||
pattern.isOffsetInPixels ||
pattern.isEndOffsetInPixels ||
pattern.isRepeatInPixels ||
pattern.symbolFactory.isZoomDependant;
}
this.options.patterns.forEach(function (patternDef) {
pattern = _this2._parsePatternDef(patternDef);
_this2._patterns.push(pattern);
});
},

@@ -87,6 +408,6 @@

*/
setPatterns: function(patterns) {
setPatterns: function setPatterns(patterns) {
this.options.patterns = patterns;
this._initPatterns();
this._softRedraw();
this.redraw();
},

@@ -98,3 +419,3 @@

*/
setPaths: function(paths) {
setPaths: function setPaths(paths) {
this._initPaths(paths);

@@ -104,52 +425,38 @@ this.redraw();

_parseRelativeOrAbsoluteValue: function _parseRelativeOrAbsoluteValue(value) {
if (typeof value === 'string' && value.indexOf('%') !== -1) {
return {
value: parseFloat(value) / 100,
isInPixels: false
};
}
var parsedValue = value ? parseFloat(value) : 0;
return {
value: parsedValue,
isInPixels: parsedValue > 0
};
},
/**
* Parse the pattern definition
*/
_parsePatternDef: function(patternDef, latLngs) {
var pattern = {
cache: [],
_parsePatternDef: function _parsePatternDef(patternDef, latLngs) {
return {
symbolFactory: patternDef.symbol,
isOffsetInPixels: false,
isEndOffsetInPixels: false,
isRepeatInPixels: false
// Parse offset and repeat values, managing the two cases:
// absolute (in pixels) or relative (in percentage of the polyline length)
offset: this._parseRelativeOrAbsoluteValue(patternDef.offset),
endOffset: this._parseRelativeOrAbsoluteValue(patternDef.endOffset),
repeat: this._parseRelativeOrAbsoluteValue(patternDef.repeat)
};
// Parse offset and repeat values, managing the two cases:
// absolute (in pixels) or relative (in percentage of the polyline length)
if(typeof patternDef.offset === 'string' && patternDef.offset.indexOf('%') != -1) {
pattern.offset = parseFloat(patternDef.offset) / 100;
} else {
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) {
pattern.repeat = parseFloat(patternDef.repeat) / 100;
} else {
pattern.repeat = parseFloat(patternDef.repeat);
pattern.isRepeatInPixels = (pattern.repeat > 0);
}
return(pattern);
},
onAdd: function (map) {
onAdd: function onAdd(map) {
this._map = map;
this._draw();
// listen to zoom changes to redraw pixel-spaced patterns
if(this._isZoomDependant) {
this._map.on('zoomend', this._softRedraw, this);
}
this._map.on('moveend', this.redraw, this);
},
onRemove: function (map) {
// remove optional map zoom listener
this._map.off('zoomend', this._softRedraw, this);
onRemove: function onRemove(map) {
this._map.off('moveend', this.redraw, this);
this._map = null;

@@ -162,17 +469,15 @@ L.LayerGroup.prototype.onRemove.call(this, map);

*/
_buildSymbols: function(latLngs, symbolFactory, directionPoints) {
var symbols = [];
for(var i=0, l=directionPoints.length; i<l; i++) {
symbols.push(symbolFactory.buildSymbol(directionPoints[i], latLngs, this._map, i, l));
}
return symbols;
_buildSymbols: function _buildSymbols(latLngs, symbolFactory, directionPoints) {
var _this3 = this;
return directionPoints.map(function (directionPoint, i) {
return symbolFactory.buildSymbol(directionPoint, latLngs, _this3._map, i, directionPoints.length);
});
},
_getCache: function(pattern, zoom, pathIndex) {
var zoomCache = pattern.cache[zoom];
if(typeof zoomCache === 'undefined') {
pattern.cache[zoom] = [];
return null;
}
return zoomCache[pathIndex];
_asRatioToPathLength: function _asRatioToPathLength(_ref, totalPathLength) {
var value = _ref.value,
isInPixels = _ref.isInPixels;
return isInPixels ? value / totalPathLength : value;
},

@@ -185,33 +490,16 @@

*/
_getDirectionPoints: function(pathIndex, pattern) {
var zoom = this._map.getZoom();
var dirPoints = this._getCache(pattern, zoom, pathIndex);
if(dirPoints) {
return dirPoints;
_getDirectionPoints: function _getDirectionPoints(pathIndex, pattern) {
var latLngs = this._paths[pathIndex];
if (latLngs.length < 2) {
return [];
}
var offset, endOffset, repeat, pathPixelLength = null, latLngs = this._paths[pathIndex];
if(pattern.isOffsetInPixels) {
pathPixelLength = L.PolylineDecoratorUtil.getPixelLength(latLngs, this._map);
offset = pattern.offset/pathPixelLength;
} else {
offset = pattern.offset;
}
if(pattern.isEndOffsetInPixels) {
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.PolylineDecoratorUtil.getPixelLength(latLngs, this._map);
endOffset = pattern.endOffset/pathPixelLength;
} else {
endOffset = pattern.endOffset;
}
if(pattern.isRepeatInPixels) {
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.PolylineDecoratorUtil.getPixelLength(latLngs, this._map);
repeat = pattern.repeat/pathPixelLength;
} else {
repeat = pattern.repeat;
}
dirPoints = L.PolylineDecoratorUtil.projectPatternOnPath(latLngs, offset, endOffset, repeat, this._map);
// save in cache to avoid recomputing this
pattern.cache[zoom][pathIndex] = dirPoints;
var pathPixelLength = getPixelLength(latLngs, this._map);
var ratios = {
offset: this._asRatioToPathLength(pattern.offset, pathPixelLength),
endOffset: this._asRatioToPathLength(pattern.endOffset, pathPixelLength),
repeat: this._asRatioToPathLength(pattern.repeat, pathPixelLength)
};
return dirPoints;
return projectPatternOnPath(latLngs, ratios, this._map);
},

@@ -222,23 +510,7 @@

*/
redraw: function() {
this._redraw(true);
},
/**
* "Soft" redraw, called internally for example on zoom changes,
* keeping the cache.
*/
_softRedraw: function() {
this._redraw(false);
},
_redraw: function(clearCache) {
if(this._map === null)
redraw: function redraw() {
if (!this._map) {
return;
}
this.clearLayers();
if(clearCache) {
for(var i=0; i<this._patterns.length; i++) {
this._patterns[i].cache = [];
}
}
this._draw();

@@ -248,13 +520,19 @@ },

/**
* Draw a single pattern
* Returns all symbols for a given pattern as an array of LayerGroup
*/
_drawPattern: function(pattern) {
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]);
}
}
_getPatternLayers: function _getPatternLayers(pattern) {
var _this4 = this;
var directionPoints = void 0,
symbols = void 0;
var mapBounds = this._map.getBounds().pad(0.1);
return this._paths.map(function (path, i) {
directionPoints = _this4._getDirectionPoints(i, pattern)
// filter out invisible points
.filter(function (point) {
return mapBounds.contains(point.latLng);
});
return L.layerGroup(_this4._buildSymbols(path, pattern.symbolFactory, directionPoints));
});
},

@@ -265,6 +543,9 @@

*/
_draw: function () {
for(var i=0; i<this._patterns.length; i++) {
this._drawPattern(this._patterns[i]);
}
_draw: function _draw() {
var _this5 = this;
this._patterns.forEach(function (pattern) {
var layers = _this5._getPatternLayers(pattern);
_this5.addLayer(L.layerGroup(layers));
});
}

@@ -279,357 +560,2 @@ });

L.PolylineDecoratorUtil = {
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<nbPts; i++) {
dist += prevPt.distanceTo(pt = pts[i]);
prevPt = pt;
}
return dist;
},
getPixelLength: function(pl, map) {
var ll = (pl instanceof L.Polyline) ? pl.getLatLngs() : pl,
nbPts = ll.length;
if(nbPts < 2) {
return 0;
}
var dist = 0,
prevPt = map.project(ll[0]), pt;
for(var i=1; i<nbPts; i++) {
dist += prevPt.distanceTo(pt = map.project(ll[i]));
prevPt = pt;
}
return dist;
},
/**
* path: array of L.LatLng
* 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, endOffsetRatio, repeatRatio, map) {
var pathAsPoints = [], i;
for(i=0, l=path.length; i<l; i++) {
pathAsPoints[i] = map.project(path[i]);
}
// project the pattern as pixel points
var pattern = this.projectPatternOnPointPath(pathAsPoints, offsetRatio, endOffsetRatio, repeatRatio);
// and convert it to latlngs;
for(i=0, l=pattern.length; i<l; i++) {
pattern[i].latLng = map.unproject(pattern[i].pt);
}
return pattern;
},
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 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);
if(repeatRatio > 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-endOffsetPixels) {
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
},
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] = this._getTransformOrigin();
},
_getTransformOrigin: function() {
var iconAnchor = this.options.icon.options.iconAnchor;
if (!iconAnchor) {
return '50% 50%';
}
return iconAnchor[0] + 'px ' + iconAnchor[1] + 'px';
},
_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);
};
})));

function init() {
var map = new L.Map('map', {
var map = L.map('map', {
center: [52.0, -11.0],

@@ -5,0 +5,0 @@ zoom: 5,

{
"name": "leaflet-polylinedecorator",
"version": "1.2.0",
"version": "1.3.0",
"repository": "bbecquet/Leaflet.PolylineDecorator",
"main": "./dist/leaflet.polylineDecorator.js",
"scripts": {
"prepublish": "gulp"
"build": "rollup -c",
"build:watch": "rollup -c -w"
},
"devDependencies": {
"gulp": "^3.9.0",
"gulp-concat": "^2.6.0"
"babel-plugin-external-helpers": "^6.22.0",
"babel-preset-latest": "^6.24.1",
"rollup": "^0.45.2",
"rollup-plugin-babel": "^2.7.1",
"rollup-plugin-node-resolve": "^3.0.0",
"rollup-watch": "^4.3.1"
},
"license": "MIT"
"dependencies": {
"leaflet-rotatedmarker": "^0.2.0"
},
"license": "MIT",
"babel": {
"presets": [
[
"latest",
{
"es2015": {
"modules": false
}
}
]
],
"plugins": [
"external-helpers"
]
}
}

@@ -0,1 +1,6 @@

import {
getPixelLength,
projectPatternOnPath,
} from './utils.js';
import './L.Symbol.js';

@@ -22,10 +27,9 @@ L.PolylineDecorator = L.FeatureGroup.extend({

this._paths = [];
var isPolygon = false;
if(p instanceof L.Polyline) {
if (p instanceof L.Polyline) {
this._initPath(p.getLatLngs(), (p instanceof L.Polygon));
} else if(L.Util.isArray(p) && p.length > 0) {
if(p[0] instanceof L.Polyline) {
for(var i=0; i<p.length; i++) {
this._initPath(p[i].getLatLngs(), (p[i] instanceof L.Polygon));
}
} else if (L.Util.isArray(p) && p.length > 0) {
if (p[0] instanceof L.Polyline) {
p.forEach(singleP => {
this._initPath(singleP, (single instanceof L.Polygon));
});
} else {

@@ -38,3 +42,3 @@ this._initPath(p);

_isCoordArray: function(ll) {
return(L.Util.isArray(ll) && ll.length > 0 && (
return (L.Util.isArray(ll) && ll.length > 0 && (
ll[0] instanceof L.LatLng ||

@@ -46,14 +50,10 @@ (L.Util.isArray(ll[0]) && ll[0].length == 2 && typeof ll[0][0] === 'number')

_initPath: function(path, isPolygon) {
var latLngs;
// It may still be an array of array of coordinates
// (ex: polygon with rings)
if(this._isCoordArray(path)) {
latLngs = [path];
} else {
latLngs = path;
}
for(var i=0; i<latLngs.length; i++) {
const latLngs = this._isCoordArray(path) ? [path] : path;
for(let i=0; i<latLngs.length; i++) {
// As of Leaflet >= v0.6, last polygon vertex (=first) isn't repeated.
// Our algorithm needs it, so we add it back explicitly.
if(isPolygon) {
if (isPolygon) {
latLngs[i].push(latLngs[i][0]);

@@ -66,16 +66,9 @@ }

_initPatterns: function() {
this._isZoomDependant = false;
this._patterns = [];
var pattern;
let pattern;
// parse pattern definitions and precompute some values
for(var i=0;i<this.options.patterns.length;i++) {
pattern = this._parsePatternDef(this.options.patterns[i]);
this.options.patterns.forEach(patternDef => {
pattern = this._parsePatternDef(patternDef);
this._patterns.push(pattern);
// determines if we have to recompute the pattern on each zoom change
this._isZoomDependant = this._isZoomDependant ||
pattern.isOffsetInPixels ||
pattern.isEndOffsetInPixels ||
pattern.isRepeatInPixels ||
pattern.symbolFactory.isZoomDependant;
}
});
},

@@ -90,3 +83,3 @@

this._initPatterns();
this._softRedraw();
this.redraw();
},

@@ -103,2 +96,16 @@

_parseRelativeOrAbsoluteValue: function(value) {
if (typeof value === 'string' && value.indexOf('%') !== -1) {
return {
value: parseFloat(value) / 100,
isInPixels: false,
};
}
const parsedValue = value ? parseFloat(value) : 0;
return {
value: parsedValue,
isInPixels: parsedValue > 0,
};
},
/**

@@ -108,34 +115,10 @@ * Parse the pattern definition

_parsePatternDef: function(patternDef, latLngs) {
var pattern = {
cache: [],
return {
symbolFactory: patternDef.symbol,
isOffsetInPixels: false,
isEndOffsetInPixels: false,
isRepeatInPixels: false
// Parse offset and repeat values, managing the two cases:
// absolute (in pixels) or relative (in percentage of the polyline length)
offset: this._parseRelativeOrAbsoluteValue(patternDef.offset),
endOffset: this._parseRelativeOrAbsoluteValue(patternDef.endOffset),
repeat: this._parseRelativeOrAbsoluteValue(patternDef.repeat),
};
// Parse offset and repeat values, managing the two cases:
// absolute (in pixels) or relative (in percentage of the polyline length)
if(typeof patternDef.offset === 'string' && patternDef.offset.indexOf('%') != -1) {
pattern.offset = parseFloat(patternDef.offset) / 100;
} else {
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) {
pattern.repeat = parseFloat(patternDef.repeat) / 100;
} else {
pattern.repeat = parseFloat(patternDef.repeat);
pattern.isRepeatInPixels = (pattern.repeat > 0);
}
return(pattern);
},

@@ -146,11 +129,7 @@

this._draw();
// listen to zoom changes to redraw pixel-spaced patterns
if(this._isZoomDependant) {
this._map.on('zoomend', this._softRedraw, this);
}
this._map.on('moveend', this.redraw, this);
},
onRemove: function (map) {
// remove optional map zoom listener
this._map.off('zoomend', this._softRedraw, this);
this._map.off('moveend', this.redraw, this);
this._map = null;

@@ -164,16 +143,9 @@ L.LayerGroup.prototype.onRemove.call(this, map);

_buildSymbols: function(latLngs, symbolFactory, directionPoints) {
var symbols = [];
for(var i=0, l=directionPoints.length; i<l; i++) {
symbols.push(symbolFactory.buildSymbol(directionPoints[i], latLngs, this._map, i, l));
}
return symbols;
return directionPoints.map((directionPoint, i) =>
symbolFactory.buildSymbol(directionPoint, latLngs, this._map, i, directionPoints.length)
);
},
_getCache: function(pattern, zoom, pathIndex) {
var zoomCache = pattern.cache[zoom];
if(typeof zoomCache === 'undefined') {
pattern.cache[zoom] = [];
return null;
}
return zoomCache[pathIndex];
_asRatioToPathLength: function({ value, isInPixels }, totalPathLength) {
return isInPixels ? value / totalPathLength : value;
},

@@ -187,32 +159,15 @@

_getDirectionPoints: function(pathIndex, pattern) {
var zoom = this._map.getZoom();
var dirPoints = this._getCache(pattern, zoom, pathIndex);
if(dirPoints) {
return dirPoints;
const latLngs = this._paths[pathIndex];
if (latLngs.length < 2) {
return [];
}
var offset, endOffset, repeat, pathPixelLength = null, latLngs = this._paths[pathIndex];
if(pattern.isOffsetInPixels) {
pathPixelLength = L.PolylineDecoratorUtil.getPixelLength(latLngs, this._map);
offset = pattern.offset/pathPixelLength;
} else {
offset = pattern.offset;
}
if(pattern.isEndOffsetInPixels) {
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.PolylineDecoratorUtil.getPixelLength(latLngs, this._map);
endOffset = pattern.endOffset/pathPixelLength;
} else {
endOffset = pattern.endOffset;
}
if(pattern.isRepeatInPixels) {
pathPixelLength = (pathPixelLength !== null) ? pathPixelLength : L.PolylineDecoratorUtil.getPixelLength(latLngs, this._map);
repeat = pattern.repeat/pathPixelLength;
} else {
repeat = pattern.repeat;
}
dirPoints = L.PolylineDecoratorUtil.projectPatternOnPath(latLngs, offset, endOffset, repeat, this._map);
// save in cache to avoid recomputing this
pattern.cache[zoom][pathIndex] = dirPoints;
const pathPixelLength = getPixelLength(latLngs, this._map);
const ratios = {
offset: this._asRatioToPathLength(pattern.offset, pathPixelLength),
endOffset: this._asRatioToPathLength(pattern.endOffset, pathPixelLength),
repeat: this._asRatioToPathLength(pattern.repeat, pathPixelLength),
};
return dirPoints;
return projectPatternOnPath(latLngs, ratios, this._map);
},

@@ -224,22 +179,6 @@

redraw: function() {
this._redraw(true);
},
/**
* "Soft" redraw, called internally for example on zoom changes,
* keeping the cache.
*/
_softRedraw: function() {
this._redraw(false);
},
_redraw: function(clearCache) {
if(this._map === null)
if (!this._map) {
return;
}
this.clearLayers();
if(clearCache) {
for(var i=0; i<this._patterns.length; i++) {
this._patterns[i].cache = [];
}
}
this._draw();

@@ -249,13 +188,14 @@ },

/**
* Draw a single pattern
* Returns all symbols for a given pattern as an array of LayerGroup
*/
_drawPattern: function(pattern) {
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]);
}
}
_getPatternLayers: function(pattern) {
let directionPoints, symbols;
const mapBounds = this._map.getBounds().pad(0.1);
return this._paths.map((path, i) => {
directionPoints = this._getDirectionPoints(i, pattern)
// filter out invisible points
.filter(point => mapBounds.contains(point.latLng));
return L.layerGroup(this._buildSymbols(path, pattern.symbolFactory, directionPoints));
});
},

@@ -267,5 +207,6 @@

_draw: function () {
for(var i=0; i<this._patterns.length; i++) {
this._drawPattern(this._patterns[i]);
}
this._patterns.forEach(pattern => {
const layers = this._getPatternLayers(pattern);
this.addLayer(L.layerGroup(layers));
});
}

@@ -272,0 +213,0 @@ });

@@ -1,2 +0,5 @@

/**
// enable rotationAngle and rotationOrigin support on L.Marker
import 'leaflet-rotatedmarker';
/**
* Defines several classes of symbol factories,

@@ -14,3 +17,3 @@ * to be used with L.PolylineDecorator

isZoomDependant: true,
options: {

@@ -20,3 +23,3 @@ pixelSize: 10,

},
initialize: function (options) {

@@ -28,19 +31,19 @@ L.Util.setOptions(this, options);

buildSymbol: function(dirPoint, latLngs, map, index, total) {
var opts = this.options,
d2r = Math.PI / 180;
const opts = this.options;
const 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);
return 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
);
const midPoint = map.project(dirPoint.latLng);
const angle = (-(dirPoint.heading - 90)) * d2r;
const a = 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);
const b = midPoint.add(midPoint.subtract(a));
return L.polyline([map.unproject(a), map.unproject(b)], opts.pathOptions);
}

@@ -55,3 +58,3 @@ });

isZoomDependant: true,
options: {

@@ -66,3 +69,3 @@ polygon: true,

},
initialize: function (options) {

@@ -74,27 +77,22 @@ L.Util.setOptions(this, options);

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;
return this.options.polygon
? L.polygon(this._buildArrowPath(dirPoint, map), this.options.pathOptions)
: L.polyline(this._buildArrowPath(dirPoint, map), this.options.pathOptions);
},
_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));
const d2r = Math.PI / 180;
const tipPoint = map.project(dirPoint.latLng);
const direction = (-(dirPoint.heading - 90)) * d2r;
const radianArrowAngle = this.options.headAngle / 2 * d2r;
const headAngle1 = direction + radianArrowAngle;
const headAngle2 = direction - radianArrowAngle;
const arrowHead1 = L.point(
tipPoint.x - this.options.pixelSize * Math.cos(headAngle1),
tipPoint.y + this.options.pixelSize * Math.sin(headAngle1));
const arrowHead2 = L.point(
tipPoint.x - this.options.pixelSize * Math.cos(headAngle2),
tipPoint.y + this.options.pixelSize * Math.sin(headAngle2));
return [

@@ -119,3 +117,3 @@ map.unproject(arrowHead1),

},
initialize: function (options) {

@@ -129,9 +127,6 @@ L.Util.setOptions(this, options);

buildSymbol: function(directionPoint, latLngs, map, index, total) {
if(!this.options.rotate) {
return new L.Marker(directionPoint.latLng, this.options.markerOptions);
if(this.options.rotate) {
this.options.markerOptions.rotationAngle = directionPoint.heading + (this.options.angleCorrection || 0);
}
else {
this.options.markerOptions.angle = directionPoint.heading + (this.options.angleCorrection || 0);
return new L.RotatedMarker(directionPoint.latLng, this.options.markerOptions);
}
return L.marker(directionPoint.latLng, this.options.markerOptions);
}

@@ -143,3 +138,1 @@ });

};

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc