leaflet-polylinedecorator
Advanced tools
Comparing version 1.2.0 to 1.3.0
{ | ||
"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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
434823
1
6
14
1008
+ Addedleaflet-rotatedmarker@^0.2.0
+ Addedleaflet-rotatedmarker@0.2.0(transitive)