offset polylines for other neighbours of self
This commit is contained in:
@ -20,6 +20,7 @@
|
||||
<!-- leaflet map -->
|
||||
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.3/dist/leaflet.css" integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI=" crossorigin="" />
|
||||
<script src="https://unpkg.com/leaflet@1.9.3/dist/leaflet.js" integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM=" crossorigin=""></script>
|
||||
<script src="plugins/leaflet.polylineoffset.js"></script>
|
||||
<script src="plugins/leaflet.markercluster/leaflet.markercluster.js"></script>
|
||||
<link rel="stylesheet" href="plugins/leaflet.markercluster/MarkerCluster.css"/>
|
||||
<link rel="stylesheet" href="plugins/leaflet.markercluster/MarkerCluster.Default.css"/>
|
||||
@ -2288,6 +2289,7 @@
|
||||
}
|
||||
|
||||
// add node neighbours
|
||||
var polylineOffset = 0;
|
||||
const neighbours = node.neighbours ?? [];
|
||||
for(const neighbour of neighbours){
|
||||
|
||||
@ -2319,8 +2321,12 @@
|
||||
], {
|
||||
color: '#2563eb',
|
||||
opacity: 0.5,
|
||||
offset: polylineOffset,
|
||||
}).addTo(neighboursLayerGroup);
|
||||
|
||||
// increase offset so next neighbour does not overlay other neighbours from self
|
||||
polylineOffset += 2;
|
||||
|
||||
// default to showing distance in meters
|
||||
var distance = `${distanceInMeters} meters`;
|
||||
|
||||
|
227
src/public/plugins/leaflet.polylineoffset.js
Normal file
227
src/public/plugins/leaflet.polylineoffset.js
Normal file
@ -0,0 +1,227 @@
|
||||
(function (factory, window) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['leaflet'], factory);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = factory(require('leaflet'));
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.L) {
|
||||
window.L.PolylineOffset = factory(L);
|
||||
}
|
||||
}(function (L) {
|
||||
|
||||
function forEachPair(list, callback) {
|
||||
if (!list || list.length < 1) { return; }
|
||||
for (var i = 1, l = list.length; i < l; i++) {
|
||||
callback(list[i-1], list[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Find the coefficients (a,b) of a line of equation y = a.x + b,
|
||||
or the constant x for vertical lines
|
||||
Return null if there's no equation possible
|
||||
*/
|
||||
function lineEquation(pt1, pt2) {
|
||||
if (pt1.x === pt2.x) {
|
||||
return pt1.y === pt2.y ? null : { x: pt1.x };
|
||||
}
|
||||
|
||||
var a = (pt2.y - pt1.y) / (pt2.x - pt1.x);
|
||||
return {
|
||||
a: a,
|
||||
b: pt1.y - a * pt1.x,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
Return the intersection point of two lines defined by two points each
|
||||
Return null when there's no unique intersection
|
||||
*/
|
||||
function intersection(l1a, l1b, l2a, l2b) {
|
||||
var line1 = lineEquation(l1a, l1b);
|
||||
var line2 = lineEquation(l2a, l2b);
|
||||
|
||||
if (line1 === null || line2 === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (line1.hasOwnProperty('x')) {
|
||||
return line2.hasOwnProperty('x')
|
||||
? null
|
||||
: {
|
||||
x: line1.x,
|
||||
y: line2.a * line1.x + line2.b,
|
||||
};
|
||||
}
|
||||
if (line2.hasOwnProperty('x')) {
|
||||
return {
|
||||
x: line2.x,
|
||||
y: line1.a * line2.x + line1.b,
|
||||
};
|
||||
}
|
||||
|
||||
if (line1.a === line2.a) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var x = (line2.b - line1.b) / (line1.a - line2.a);
|
||||
return {
|
||||
x: x,
|
||||
y: line1.a * x + line1.b,
|
||||
};
|
||||
}
|
||||
|
||||
function translatePoint(pt, dist, heading) {
|
||||
return {
|
||||
x: pt.x + dist * Math.cos(heading),
|
||||
y: pt.y + dist * Math.sin(heading),
|
||||
};
|
||||
}
|
||||
|
||||
var PolylineOffset = {
|
||||
offsetPointLine: function(points, distance) {
|
||||
var offsetSegments = [];
|
||||
|
||||
forEachPair(points, L.bind(function(a, b) {
|
||||
if (a.x === b.x && a.y === b.y) { return; }
|
||||
|
||||
// angles in (-PI, PI]
|
||||
var segmentAngle = Math.atan2(a.y - b.y, a.x - b.x);
|
||||
var offsetAngle = segmentAngle - Math.PI/2;
|
||||
|
||||
offsetSegments.push({
|
||||
offsetAngle: offsetAngle,
|
||||
original: [a, b],
|
||||
offset: [
|
||||
translatePoint(a, distance, offsetAngle),
|
||||
translatePoint(b, distance, offsetAngle)
|
||||
]
|
||||
});
|
||||
}, this));
|
||||
|
||||
return offsetSegments;
|
||||
},
|
||||
|
||||
offsetPoints: function(pts, options) {
|
||||
var offsetSegments = this.offsetPointLine(L.LineUtil.simplify(pts, options.smoothFactor), options.offset);
|
||||
return this.joinLineSegments(offsetSegments, options.offset);
|
||||
},
|
||||
|
||||
/**
|
||||
Join 2 line segments defined by 2 points each with a circular arc
|
||||
*/
|
||||
joinSegments: function(s1, s2, offset) {
|
||||
// TODO: different join styles
|
||||
return this.circularArc(s1, s2, offset)
|
||||
.filter(function(x) { return x; })
|
||||
},
|
||||
|
||||
joinLineSegments: function(segments, offset) {
|
||||
var joinedPoints = [];
|
||||
var first = segments[0];
|
||||
var last = segments[segments.length - 1];
|
||||
|
||||
if (first && last) {
|
||||
joinedPoints.push(first.offset[0]);
|
||||
forEachPair(segments, L.bind(function(s1, s2) {
|
||||
joinedPoints = joinedPoints.concat(this.joinSegments(s1, s2, offset));
|
||||
}, this));
|
||||
joinedPoints.push(last.offset[1]);
|
||||
}
|
||||
|
||||
return joinedPoints;
|
||||
},
|
||||
|
||||
segmentAsVector: function(s) {
|
||||
return {
|
||||
x: s[1].x - s[0].x,
|
||||
y: s[1].y - s[0].y,
|
||||
};
|
||||
},
|
||||
|
||||
getSignedAngle: function(s1, s2) {
|
||||
const a = this.segmentAsVector(s1);
|
||||
const b = this.segmentAsVector(s2);
|
||||
return Math.atan2(a.x * b.y - a.y * b.x, a.x * b.x + a.y * b.y);
|
||||
},
|
||||
|
||||
/**
|
||||
Interpolates points between two offset segments in a circular form
|
||||
*/
|
||||
circularArc: function(s1, s2, distance) {
|
||||
// if the segments are the same angle,
|
||||
// there should be a single join point
|
||||
if (s1.offsetAngle === s2.offsetAngle) {
|
||||
return [s1.offset[1]];
|
||||
}
|
||||
|
||||
const signedAngle = this.getSignedAngle(s1.offset, s2.offset);
|
||||
// for inner angles, just find the offset segments intersection
|
||||
if ((signedAngle * distance > 0) &&
|
||||
(signedAngle * this.getSignedAngle(s1.offset, [s1.offset[0], s2.offset[1]]) > 0)) {
|
||||
return [intersection(s1.offset[0], s1.offset[1], s2.offset[0], s2.offset[1])];
|
||||
}
|
||||
|
||||
// draws a circular arc with R = offset distance, C = original meeting point
|
||||
var points = [];
|
||||
var center = s1.original[1];
|
||||
// ensure angles go in the anti-clockwise direction
|
||||
var rightOffset = distance > 0;
|
||||
var startAngle = rightOffset ? s2.offsetAngle : s1.offsetAngle;
|
||||
var endAngle = rightOffset ? s1.offsetAngle : s2.offsetAngle;
|
||||
// and that the end angle is bigger than the start angle
|
||||
if (endAngle < startAngle) {
|
||||
endAngle += Math.PI * 2;
|
||||
}
|
||||
var step = Math.PI / 8;
|
||||
for (var alpha = startAngle; alpha < endAngle; alpha += step) {
|
||||
points.push(translatePoint(center, distance, alpha));
|
||||
}
|
||||
points.push(translatePoint(center, distance, endAngle));
|
||||
|
||||
return rightOffset ? points.reverse() : points;
|
||||
}
|
||||
}
|
||||
|
||||
// Modify the L.Polyline class by overwriting the projection function
|
||||
L.Polyline.include({
|
||||
_projectLatlngs: function (latlngs, result, projectedBounds) {
|
||||
var isFlat = latlngs.length > 0 && latlngs[0] instanceof L.LatLng;
|
||||
|
||||
if (isFlat) {
|
||||
var ring = latlngs.map(L.bind(function(ll) {
|
||||
var point = this._map.latLngToLayerPoint(ll);
|
||||
if (projectedBounds) {
|
||||
projectedBounds.extend(point);
|
||||
}
|
||||
return point;
|
||||
}, this));
|
||||
|
||||
// Offset management hack ---
|
||||
if (this.options.offset) {
|
||||
ring = L.PolylineOffset.offsetPoints(ring, this.options);
|
||||
}
|
||||
// Offset management hack END ---
|
||||
|
||||
result.push(ring.map(function (xy) {
|
||||
return L.point(xy.x, xy.y);
|
||||
}));
|
||||
} else {
|
||||
latlngs.forEach(L.bind(function(ll) {
|
||||
this._projectLatlngs(ll, result, projectedBounds);
|
||||
}, this));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
L.Polyline.include({
|
||||
setOffset: function(offset) {
|
||||
this.options.offset = offset;
|
||||
this.redraw();
|
||||
return this;
|
||||
}
|
||||
});
|
||||
|
||||
return PolylineOffset;
|
||||
|
||||
}, window));
|
Reference in New Issue
Block a user