Geometry中的类没有继承自Class,定义了基础的数据类型对象。
点 Point
表示一个点,x、y以像素为坐标。可以进行加、减、乘、除、缩放、取整等计算,也可以进行距离计算、相等判断、大小比较。
import {formatNum} from '../core/Util';
/*
* @class Point
* @aka L.Point
*
* 用 `x` and `y` 表示一个 像素坐标系 中的点
*
* @example
*
* ```js
* var point = L.point(200, 300);
* ```
*
* Leaflet中所有接受 Point 对象的方法和 option 都可以接受一个简单的数组 (除非有特别说明), 下面的代码是等效的:
*
* ```js
* map.panBy([200, 300]);
* map.panBy(L.point(200, 300));
* ```
*
* 注意: Point 没有继承自 Class 对象,因此 point 也不能扩展子类,也不能用 includes 扩展 point
*/
export function Point(x, y, round) {
// @property x: Number; The `x` coordinate of the point
this.x = (round ? Math.round(x) : x);
// @property y: Number; The `y` coordinate of the point
this.y = (round ? Math.round(y) : y);
}
const trunc = Math.trunc || function (v) {
return v > 0 ? Math.floor(v) : Math.ceil(v);
};
Point.prototype = {
// @method clone(): Point
// Returns a copy of the current point.
clone() {
return new Point(this.x, this.y);
},
// @method add(otherPoint: Point): Point
// Returns the result of addition of the current and the given points.
add(point) {
// 不会改变当前point, 返回了新的 point
return this.clone()._add(toPoint(point));
},
_add(point) {
// 具有破坏性,会改变当前point, used directly for performance in situations where it's safe to modify existing point
this.x += point.x;
this.y += point.y;
return this;
},
// @method subtract(otherPoint: Point): Point
// 当前减去point,返回结果.
subtract(point) {
return this.clone()._subtract(toPoint(point));
},
_subtract(point) {
this.x -= point.x;
this.y -= point.y;
return this;
},
// @method divideBy(num: Number): Point
// Returns the result of division of the current point by the given number.
divideBy(num) {
return this.clone()._divideBy(num);
},
_divideBy(num) {
this.x /= num;
this.y /= num;
return this;
},
// @method multiplyBy(num: Number): Point
// Returns the result of multiplication of the current point by the given number.
multiplyBy(num) {
return this.clone()._multiplyBy(num);
},
_multiplyBy(num) {
this.x *= num;
this.y *= num;
return this;
},
// @method scaleBy(scale: Point): Point
// Multiply each coordinate of the current point by each coordinate of
// `scale`. In linear algebra terms, multiply the point by the
// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
// defined by `scale`.
scaleBy(point) {
return new Point(this.x * point.x, this.y * point.y);
},
// @method unscaleBy(scale: Point): Point
// Inverse of `scaleBy`. Divide each coordinate of the current point by
// each coordinate of `scale`.
unscaleBy(point) {
return new Point(this.x / point.x, this.y / point.y);
},
// @method round(): Point
// Returns a copy of the current point with rounded coordinates.
round() {
return this.clone()._round();
},
_round() {
this.x = Math.round(this.x);
this.y = Math.round(this.y);
return this;
},
// @method floor(): Point
// Returns a copy of the current point with floored coordinates (rounded down).
floor() {
return this.clone()._floor();
},
_floor() {
this.x = Math.floor(this.x);
this.y = Math.floor(this.y);
return this;
},
// @method ceil(): Point
// Returns a copy of the current point with ceiled coordinates (rounded up).
ceil() {
return this.clone()._ceil();
},
_ceil() {
this.x = Math.ceil(this.x);
this.y = Math.ceil(this.y);
return this;
},
// @method trunc(): Point
// Returns a copy of the current point with truncated coordinates (rounded towards zero).
// 删除小数位数
trunc() {
return this.clone()._trunc();
},
_trunc() {
this.x = trunc(this.x);
this.y = trunc(this.y);
return this;
},
// @method distanceTo(otherPoint: Point): Number
// Returns the cartesian distance between the current and the given points.
distanceTo(point) {
point = toPoint(point);
const x = point.x - this.x,
y = point.y - this.y;
return Math.sqrt(x * x + y * y);
},
// @method equals(otherPoint: Point): Boolean
// Returns `true` if the given point has the same coordinates.
equals(point) {
point = toPoint(point);
return point.x === this.x &&
point.y === this.y;
},
// @method contains(otherPoint: Point): Boolean
// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
contains(point) {
point = toPoint(point);
return Math.abs(point.x) <= Math.abs(this.x) &&
Math.abs(point.y) <= Math.abs(this.y);
},
// @method toString(): String
// Returns a string representation of the point for debugging purposes.
toString() {
// formatNum对小数最后一位做了四舍五入
return `Point(${formatNum(this.x)}, ${formatNum(this.y)})`;
}
};
// @factory L.point(x: Number, y: Number, round?: Boolean)
// Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.
// @alternative
// @factory L.point(coords: Number[])
// Expects an array of the form `[x, y]` instead.
// @alternative
// @factory L.point(coords: Object)
// Expects a plain object of the form `{x: Number, y: Number}` instead.
// 根据接受的参数格式,按照 Point、Array、Object先后判断类型
export function toPoint(x, y, round) {
if (x instanceof Point) {
return x;
}
if (Array.isArray(x)) {
return new Point(x[0], x[1]);
}
if (x === undefined || x === null) {
return x;
}
if (typeof x === 'object' && 'x' in x && 'y' in x) {
return new Point(x.x, x.y);
}
return new Point(x, y, round);
}
边界 bounds
表示像素坐标中的矩形区域。可以确定一个四至范围。
import {Point, toPoint} from './Point';
/*
* @class Bounds
* @aka L.Bounds
*
* 表示像素坐标系中的一个矩形范围.
*
* @example
*
* ```js
* var p1 = L.point(10, 10),
* p2 = L.point(40, 60),
* bounds = L.bounds(p1, p2);
* ```
*
* Leaflet 接受 Bounds 参数的方法都接受一个简单的数组作为参数(除非有特殊说明),下面的代码是等效的
*
* ```js
* otherBounds.intersects([[10, 10], [40, 60]]);
* ```
*
* 注意: Bounds 没有继承自 Class 对象,因此 point 也不能扩展子类,也不能用 includes 扩展 point
*/
export function Bounds(a, b) {
if (!a) { return; }
const points = b ? [a, b] : a;
for (let i = 0, len = points.length; i < len; i++) {
this.extend(points[i]);
}
}
Bounds.prototype = {
// @method extend(point: Point): this
// Extends the bounds to contain the given point.
// @alternative
// @method extend(otherBounds: Bounds): this
// Extend the bounds to contain the given bounds
extend(obj) {
let min2, max2;
if (!obj) { return this; }
if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
// 传入的不是Bounds,是(point 数组 或者 对象)
min2 = max2 = toPoint(obj);
} else {
obj = toBounds(obj);
min2 = obj.min;
max2 = obj.max;
// 为啥要用 ! || !,而不用 && ?
if (!min2 || !max2) { return this; }
}
// @property min: Point
// The top left corner of the rectangle.
// @property max: Point
// The bottom right corner of the rectangle.
if (!this.min && !this.max) {
this.min = min2.clone();
this.max = max2.clone();
} else {
this.min.x = Math.min(min2.x, this.min.x);
this.max.x = Math.max(max2.x, this.max.x);
this.min.y = Math.min(min2.y, this.min.y);
this.max.y = Math.max(max2.y, this.max.y);
}
return this;
},
// @method getCenter(round?: Boolean): Point
// 获取中心点
getCenter(round) {
return toPoint(
(this.min.x + this.max.x) / 2,
(this.min.y + this.max.y) / 2, round);
},
// @method getBottomLeft(): Point
// 获取左下角
getBottomLeft() {
return toPoint(this.min.x, this.max.y);
},
// @method getTopRight(): Point
// 获取右上角
getTopRight() { // -> Point
return toPoint(this.max.x, this.min.y);
},
// @method getTopLeft(): Point
// 获取做上角,因为是像素坐标,所以mix就是左上角
getTopLeft() {
return this.min; // left, top
},
// @method getBottomRight(): Point
// 获取右下角
getBottomRight() {
return this.max; // right, bottom
},
// @method getSize(): Point
// 返回 x y 方向的长度,像素单位
getSize() {
return this.max.subtract(this.min);
},
// @method contains(otherBounds: Bounds): Boolean
// 判断 Bounds 是否包含另一个 Bounds
// @alternative
// @method contains(point: Point): Boolean
// 判断 Bounds 是否包含以个点
contains(obj) {
let min, max;
// 区分传入参数的类型
if (typeof obj[0] === 'number' || obj instanceof Point) {
obj = toPoint(obj);
} else {
obj = toBounds(obj);
}
if (obj instanceof Bounds) {
min = obj.min;
max = obj.max;
} else {
min = max = obj;
}
return (min.x >= this.min.x) &&
(max.x <= this.max.x) &&
(min.y >= this.min.y) &&
(max.y <= this.max.y);
},
// @method intersects(otherBounds: Bounds): Boolean
// 判断两个 Bounds 是否相交,只要有一个像素点重叠了,就认为两个算相交。
intersects(bounds) { // (Bounds) -> Boolean
bounds = toBounds(bounds);
const min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
return xIntersects && yIntersects;
},
// @method overlaps(otherBounds: Bounds): Boolean
// 判断两个 Bounds 是否相交。两个 Bounds的重叠区域要能构成面才算重叠。
overlaps(bounds) { // (Bounds) -> Boolean
bounds = toBounds(bounds);
const min = this.min,
max = this.max,
min2 = bounds.min,
max2 = bounds.max,
xOverlaps = (max2.x > min.x) && (min2.x < max.x),
yOverlaps = (max2.y > min.y) && (min2.y < max.y);
return xOverlaps && yOverlaps;
},
// @method isValid(): Boolean
// 判断是否存在逻辑错误
isValid() {
return !!(this.min && this.max);
},
// @method pad(bufferRatio: Number): Bounds
// 扩大或者缩小一定比例的 Bounds,保持中心不变。参数为负值的时候是缩小。
pad(bufferRatio) {
const min = this.min,
max = this.max,
heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
widthBuffer = Math.abs(min.y - max.y) * bufferRatio;
return toBounds(
toPoint(min.x - heightBuffer, min.y - widthBuffer),
toPoint(max.x + heightBuffer, max.y + widthBuffer));
},
// @method equals(otherBounds: Bounds): Boolean
// 判断两个 Bounds 的范围是否一致
equals(bounds) {
if (!bounds) { return false; }
bounds = toBounds(bounds);
return this.min.equals(bounds.getTopLeft()) &&
this.max.equals(bounds.getBottomRight());
},
};
// @factory L.bounds(corner1: Point, corner2: Point)
// Creates a Bounds object from two corners coordinate pairs.
// @alternative
// @factory L.bounds(points: Point[])
// Creates a Bounds object from the given array of points.
export function toBounds(a, b) {
if (!a || a instanceof Bounds) {
return a;
}
return new Bounds(a, b);
}
换算 Transformation
仿射变换:一组系数a,b,c,d,用于将形式(x,y)的点变换为(ax+b,cy+d)并进行相反的变换。由Leaflet 在坐标投影变换的时候使用。(参见 Geo 中的 CRS 的 transformation )
import {Point} from './Point';
/*
* @class Transformation
* @aka L.Transformation
*
* 表示仿射变换:一组系数a,b,c,d
* 用于将形式(x,y)的点变换为(x*a+b,y*c+d)并进行相反的变换。
* Leaflet 在投影变换相关的代码中使用
*
* @example
*
* ```js
* var transformation = L.transformation(2, 5, -1, 10),
* p = L.point(1, 2),
* p2 = transformation.transform(p), // L.point(7, 8)
* p3 = transformation.untransform(p2); // L.point(1, 2)
* ```
*/
// factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
// 使用给定的系数创建一个 Transformation
export function Transformation(a, b, c, d) {
if (Array.isArray(a)) {
// use array properties
this._a = a[0];
this._b = a[1];
this._c = a[2];
this._d = a[3];
return;
}
this._a = a;
this._b = b;
this._c = c;
this._d = d;
}
Transformation.prototype = {
// @method transform(point: Point, scale?: Number): Point
// Returns a transformed point, optionally multiplied by the given scale.
// 只接受 Point,不接受数组.
transform(point, scale) { // (Point, Number) -> Point
return this._transform(point.clone(), scale);
},
// destructive transform (faster)
_transform(point, scale) {
scale = scale || 1;
point.x = scale * (this._a * point.x + this._b);
point.y = scale * (this._c * point.y + this._d);
return point;
},
// @method untransform(point: Point, scale?: Number): Point
// Returns the reverse transformation of the given point, optionally divided
// by the given scale. Only accepts actual `L.Point` instances, not arrays.
untransform(point, scale) {
scale = scale || 1;
return new Point(
(point.x / scale - this._b) / this._a,
(point.y / scale - this._d) / this._c);
}
};
// factory L.transformation(a: Number, b: Number, c: Number, d: Number)
// @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
// Instantiates a Transformation object with the given coefficients.
// @alternative
// @factory L.transformation(coefficients: Array): Transformation
// Expects an coefficients array of the form
// `[a: Number, b: Number, c: Number, d: Number]`.
export function toTransformation(a, b, c, d) {
return new Transformation(a, b, c, d);
}
线LineUtil.js
用来简化和裁剪处理线要素相关的方法,用于加快leaflet的渲染性能。
import {Point, toPoint} from './Point';
import {toLatLng} from '../geo/LatLng';
/*
* @namespace LineUtil
*
* 处理 polyline 中 points 的一系列工具函数, used by Leaflet internally to make polylines lightning-fast.
*/
// 使用 vertex reduction and Douglas-Peucker simplification简化线.
// Improves rendering performance dramatically by lessening the number of points to draw.
// @function simplify(points: Point[], tolerance: Number): Point[]
// 在保留线形状的同时显著地减少线段的点,返回一个新的线段,
// 使用道格拉斯普克算法[Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
// 用来大幅提升各个缩放级别下线段的渲染和显示性能,同时也能解决“视觉噪点”
// tolerance 通过设定一个距离用来控制简化效果(值越小意味着质量越高,但速度越慢,点数越多).
// 这个工具也发布了一个独立的工具库 [Simplify.js](https://mourner.github.io/simplify-js/).
export function simplify(points, tolerance) {
if (!tolerance || !points.length) {
return points.slice();
}
// 预先计算平方,在下面做比较的时候就不用求平方根了,增加计算效率
const sqTolerance = tolerance * tolerance;
// 第一步 1: 按距离减少点
points = _reducePoints(points, sqTolerance);
// 第二步 2: Douglas-Peucker 道格拉斯普克算法简化
points = _simplifyDP(points, sqTolerance);
return points;
}
// @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
//求点到线段的最近距离
export function pointToSegmentDistance(p, p1, p2) {
return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
}
// @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
// 求点到线段的最近点
export function closestPointOnSegment(p, p1, p2) {
return _sqClosestPointOnSegment(p, p1, p2);
}
// Ramer-Douglas-Peucker 简化算法, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
function _simplifyDP(points, sqTolerance) {
const len = points.length,
ArrayConstructor = typeof Uint8Array !== `${undefined}` ? Uint8Array : Array,
markers = new ArrayConstructor(len);
markers[0] = markers[len - 1] = 1;
_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);
let i;
const newPoints = [];
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
}
function _simplifyDPStep(points, markers, sqTolerance, first, last) {
let maxSqDist = 0,
index, i, sqDist;
for (i = first + 1; i <= last - 1; i++) {
sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
_simplifyDPStep(points, markers, sqTolerance, first, index);
_simplifyDPStep(points, markers, sqTolerance, index, last);
}
}
// 将彼此距离太近的点简化成一个
function _reducePoints(points, sqTolerance) {
// 保存结果数据,第一个点不会被简化掉
const reducedPoints = [points[0]];
let prev = 0;
for (let i = 1; i < points.length; i++) {
// 遍历所有的点,
if (_sqDist(points[i], points[prev]) > sqTolerance) {
// 如果两个点之间的距离大于限差,就保存到结果数组中(小于限差的都被剔除了)
reducedPoints.push(points[i]);
// 把当前点置为上一个点的索引
prev = i;
}
}
if (prev < points.length - 1) {
// 最后一个如果没有被选入,prev就会小于points.length
// 但是最后一个点不被简化,所有手动加进来
reducedPoints.push(points[points.length - 1]);
}
return reducedPoints;
}
let _lastCode;
// @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
// 使用Cohen-Sutherland算法对线段进行按照矩形裁剪
// [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
// 直接修改线段的点,用于leaflet在图上快速渲染线要素
export function clipSegment(a, b, bounds, useLastCode, round) {
let codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
codeB = _getBitCode(b, bounds),
codeOut, p, newCode;
// save 2nd code to avoid calculating it on the next segment
_lastCode = codeB;
while (true) {
// if a,b is inside the clip window (trivial accept)
if (!(codeA | codeB)) {
return [a, b];
}
// if a,b is outside the clip window (trivial reject)
if (codeA & codeB) {
return false;
}
// other cases
codeOut = codeA || codeB;
p = _getEdgeIntersection(a, b, codeOut, bounds, round);
newCode = _getBitCode(p, bounds);
if (codeOut === codeA) {
a = p;
codeA = newCode;
} else {
b = p;
codeB = newCode;
}
}
}
// 获取相交
export function _getEdgeIntersection(a, b, code, bounds, round) {
const dx = b.x - a.x,
dy = b.y - a.y,
min = bounds.min,
max = bounds.max;
let x, y;
if (code & 8) { // top
x = a.x + dx * (max.y - a.y) / dy;
y = max.y;
} else if (code & 4) { // bottom
x = a.x + dx * (min.y - a.y) / dy;
y = min.y;
} else if (code & 2) { // right
x = max.x;
y = a.y + dy * (max.x - a.x) / dx;
} else if (code & 1) { // left
x = min.x;
y = a.y + dy * (min.x - a.x) / dx;
}
return new Point(x, y, round);
}
export function _getBitCode(p, bounds) {
let code = 0;
if (p.x < bounds.min.x) { // left
code |= 1;
} else if (p.x > bounds.max.x) { // right
code |= 2;
}
if (p.y < bounds.min.y) { // bottom
code |= 4;
} else if (p.y > bounds.max.y) { // top
code |= 8;
}
return code;
}
// 平方距离 (用平方距离作比较可以避免很多不必要的 Math.sqrt 运算)
function _sqDist(p1, p2) {
const dx = p2.x - p1.x,
dy = p2.y - p1.y;
return dx * dx + dy * dy;
}
// 求 p 点到线段 p1-p2 的最近距离或者最近点??
export function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
// https://blog.csdn.net/qq_28087491/article/details/119239974
let x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y,
t;
// p1-p2 的距离平方
const dot = dx * dx + dy * dy;
// 如果dot等于0,表示p1等于p2,表示线段是一个点
if (dot > 0) {
// p到p1的距离 占 p1-p2距离的比值
t = ((p.x - x) * dx + (p.y - y) * dy) / dot;
if (t > 1) {
// p在p1-p2上或p1-p2延长线上
x = p2.x;
y = p2.y;
} else if (t > 0) {
// p在p1-p2之间,未落在线上
x += dx * t;
y += dy * t;
}
}
// 垂线交点
dx = p.x - x;
dy = p.y - y;
return sqDist ? dx * dx + dy * dy : new Point(x, y);
}
// @function isFlat(latlngs: LatLng[]): Boolean
// Returns true if `latlngs` is a flat array, false is nested.
export function isFlat(latlngs) {
return !Array.isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
}
/* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
* Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
*/
export function polylineCenter(latlngs, crs) {
let i, halfDist, segDist, dist, p1, p2, ratio, center;
if (!latlngs || latlngs.length === 0) {
throw new Error('latlngs not passed');
}
if (!isFlat(latlngs)) {
console.warn('latlngs are not flat! Only the first ring will be used');
latlngs = latlngs[0];
}
const points = [];
for (const j in latlngs) {
points.push(crs.project(toLatLng(latlngs[j])));
}
const len = points.length;
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) {
center = points[0];
} else {
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;
center = [
p2.x - ratio * (p2.x - p1.x),
p2.y - ratio * (p2.y - p1.y)
];
break;
}
}
}
return crs.unproject(toPoint(center));
}
面 PolyUtil
import * as LineUtil from './LineUtil';
import {toLatLng} from '../geo/LatLng';
import {toPoint} from './Point';
/*
* @namespace PolyUtil
* polygon geometries 的工具函数.
*/
/* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
* 使用指定的bounds裁剪面 (使用的是[Sutherland-Hodgman 算法](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
* 用于绘制屏幕范围内的要素,加快性能。
* 面和线需要不同的裁剪算法,所以这里线和面的方法是分开的。
*/
export function clipPolygon(points, bounds, round) {
let clippedPoints,
i, j, k,
a, b,
len, edge, p;
const edges = [1, 4, 2, 8];
for (i = 0, len = points.length; i < len; i++) {
points[i]._code = LineUtil._getBitCode(points[i], bounds);
}
// for each edge (left, bottom, right, top)
for (k = 0; k < 4; k++) {
edge = edges[k];
clippedPoints = [];
for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
a = points[i];
b = points[j];
// if a is inside the clip window
if (!(a._code & edge)) {
// if b is outside the clip window (a->b goes out of screen)
if (b._code & edge) {
p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p);
}
clippedPoints.push(a);
// else if b is inside the clip window (a->b enters the screen)
} else if (!(b._code & edge)) {
p = LineUtil._getEdgeIntersection(b, a, edge, bounds, round);
p._code = LineUtil._getBitCode(p, bounds);
clippedPoints.push(p);
}
}
points = clippedPoints;
}
return points;
}
/* @function polygonCenter(latlngs: LatLng[] crs: CRS): LatLng
* 返回面的质心 ([centroid](http://en.wikipedia.org/wiki/Centroid))
*/
export function polygonCenter(latlngs, crs) {
let i, j, p1, p2, f, area, x, y, center;
if (!latlngs || latlngs.length === 0) {
throw new Error('latlngs not passed');
}
if (!LineUtil.isFlat(latlngs)) {
console.warn('latlngs are not flat! Only the first ring will be used');
latlngs = latlngs[0];
}
const points = [];
for (const k in latlngs) {
points.push(crs.project(toLatLng(latlngs[k])));
}
const len = points.length;
area = x = y = 0;
// polygon centroid algorithm;
for (i = 0, j = len - 1; i < len; j = i++) {
p1 = points[i];
p2 = points[j];
f = p1.y * p2.x - p2.y * p1.x;
x += (p1.x + p2.x) * f;
y += (p1.y + p2.y) * f;
area += f * 3;
}
if (area === 0) {
// Polygon is so small that all points are on same pixel.
center = points[0];
} else {
center = [x / area, y / area];
}
return crs.unproject(toPoint(center));
}