leaflet
Version:
JavaScript library for mobile-friendly interactive maps
234 lines (186 loc) • 5.66 kB
JavaScript
/*
* L.Polyline implements polyline vector layer (a set of points connected with lines)
*/
L.Polyline = L.Path.extend({
options: {
// how much to simplify the polyline on each zoom level
// more = better performance and smoother look, less = more accurate
smoothFactor: 1.0
// noClip: false
},
initialize: function (latlngs, options) {
L.setOptions(this, options);
this._setLatLngs(latlngs);
},
getLatLngs: function () {
return this._latlngs;
},
setLatLngs: function (latlngs) {
this._setLatLngs(latlngs);
return this.redraw();
},
isEmpty: function () {
return !this._latlngs.length;
},
closestLayerPoint: function (p) {
var minDistance = Infinity,
minPoint = null,
closest = L.LineUtil._sqClosestPointOnSegment,
p1, p2;
for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
var points = this._parts[j];
for (var i = 1, len = points.length; i < len; i++) {
p1 = points[i - 1];
p2 = points[i];
var sqDist = closest(p, p1, p2, true);
if (sqDist < minDistance) {
minDistance = sqDist;
minPoint = closest(p, p1, p2);
}
}
}
if (minPoint) {
minPoint.distance = Math.sqrt(minDistance);
}
return minPoint;
},
getCenter: function () {
var i, halfDist, segDist, dist, p1, p2, ratio,
points = this._rings[0],
len = points.length;
if (!len) { return null; }
// polyline centroid algorithm; only uses the first ring if there are multiple
for (i = 0, halfDist = 0; i < len - 1; i++) {
halfDist += points[i].distanceTo(points[i + 1]) / 2;
}
// The line is so small in the current view that all points are on the same pixel.
if (halfDist === 0) {
return this._map.layerPointToLatLng(points[0]);
}
for (i = 0, dist = 0; i < len - 1; i++) {
p1 = points[i];
p2 = points[i + 1];
segDist = p1.distanceTo(p2);
dist += segDist;
if (dist > halfDist) {
ratio = (dist - halfDist) / segDist;
return this._map.layerPointToLatLng([
p2.x - ratio * (p2.x - p1.x),
p2.y - ratio * (p2.y - p1.y)
]);
}
}
},
getBounds: function () {
return this._bounds;
},
addLatLng: function (latlng, latlngs) {
latlngs = latlngs || this._defaultShape();
latlng = L.latLng(latlng);
latlngs.push(latlng);
this._bounds.extend(latlng);
return this.redraw();
},
_setLatLngs: function (latlngs) {
this._bounds = new L.LatLngBounds();
this._latlngs = this._convertLatLngs(latlngs);
},
_defaultShape: function () {
return L.Polyline._flat(this._latlngs) ? this._latlngs : this._latlngs[0];
},
// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
_convertLatLngs: function (latlngs) {
var result = [],
flat = L.Polyline._flat(latlngs);
for (var i = 0, len = latlngs.length; i < len; i++) {
if (flat) {
result[i] = L.latLng(latlngs[i]);
this._bounds.extend(result[i]);
} else {
result[i] = this._convertLatLngs(latlngs[i]);
}
}
return result;
},
_project: function () {
this._rings = [];
this._projectLatlngs(this._latlngs, this._rings);
// project bounds as well to use later for Canvas hit detection/etc.
var w = this._clickTolerance(),
p = new L.Point(w, -w);
if (this._bounds.isValid()) {
this._pxBounds = new L.Bounds(
this._map.latLngToLayerPoint(this._bounds.getSouthWest())._subtract(p),
this._map.latLngToLayerPoint(this._bounds.getNorthEast())._add(p));
}
},
// recursively turns latlngs into a set of rings with projected coordinates
_projectLatlngs: function (latlngs, result) {
var flat = latlngs[0] instanceof L.LatLng,
len = latlngs.length,
i, ring;
if (flat) {
ring = [];
for (i = 0; i < len; i++) {
ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
}
result.push(ring);
} else {
for (i = 0; i < len; i++) {
this._projectLatlngs(latlngs[i], result);
}
}
},
// clip polyline by renderer bounds so that we have less to render for performance
_clipPoints: function () {
var bounds = this._renderer._bounds;
this._parts = [];
if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
return;
}
if (this.options.noClip) {
this._parts = this._rings;
return;
}
var parts = this._parts,
i, j, k, len, len2, segment, points;
for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
points = this._rings[i];
for (j = 0, len2 = points.length; j < len2 - 1; j++) {
segment = L.LineUtil.clipSegment(points[j], points[j + 1], bounds, j, true);
if (!segment) { continue; }
parts[k] = parts[k] || [];
parts[k].push(segment[0]);
// if segment goes out of screen, or it's the last one, it's the end of the line part
if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
parts[k].push(segment[1]);
k++;
}
}
}
},
// simplify each clipped part of the polyline for performance
_simplifyPoints: function () {
var parts = this._parts,
tolerance = this.options.smoothFactor;
for (var i = 0, len = parts.length; i < len; i++) {
parts[i] = L.LineUtil.simplify(parts[i], tolerance);
}
},
_update: function () {
if (!this._map) { return; }
this._clipPoints();
this._simplifyPoints();
this._updatePath();
},
_updatePath: function () {
this._renderer._updatePoly(this);
}
});
L.polyline = function (latlngs, options) {
return new L.Polyline(latlngs, options);
};
L.Polyline._flat = function (latlngs) {
// true if it's a flat array of latlngs; false if nested
return !L.Util.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
};