https://github.com/simonepri/is-sea/tree/master
https://github.com/simonepri/is-sea
https://github.com/simonepri/geo-maps/blob/master/info/earth-seas.md
async function isSea(lat, lng) {
if (landLookup === null) {
// 把json文件转为json格式加载进来
const map = await fetchMapAsync();
landLookup = new GeoJsonLookup(map);
}
return landLookup.hasContainers({type: 'Point', coordinates: [lng, lat]});
}
class GeoJsonGeometriesLookup {
/**
- Create an instance of the GeoJSON lookup class.
- @public
- @param {Object} geoJson The GeoJSON for which create the lookup data.
- @param {Object} [options] Optional options.
- @param {boolean} options.ignorePoints If true the extractor will ignore
- geometries of type Point.
- @param {boolean} options.ignoreLines If true the extractor will ignore
- geometries of type LineString.
- @param {boolean} options.ignorePolygon If true the extractor will ignore
- geometries of type Polygon.
*/
constructor(geoJson, options) {
options = typeof options === 'object' ? options : {};
// 构建成员变量:点,线,面集合,只有面List有数据
const extracted = new GeoJsonGeometries(geoJson, options);
this.D = new Array(3);
this.D[0] = {list: extracted.points.features, bboxs: null, lookup: null};
this.D[1] = {list: extracted.lines.features, bboxs: null, lookup: null};
// list就是_loadPolygon()方法push的集合数据
this.D[2] = {list: extracted.polygons.features, bboxs: null, lookup: null};
for (let d = 0; d < 3; d++) {
// dim就是D集合中的对象:list,bboxs,lookup
const dim = this.D[d];
// 循环到d=2
if (dim.list.length > 0) {
dim.bboxs = new Array(dim.list.length);
// 构造一个对象:
//{
// data: {children: Array(0), height: 1, leaf: true, maxX: -Infinity,maxY: -Infinity,minX: Infinity,minY: Infinity}
// _maxEntries: 9
// _minEntries: 4
//}
dim.lookup = rbush();
// 数组集合:{type: FEATURE, geometry: {type: POLYGON, coordinates}, properties}
const geoms = dim.list;
// 21个zise的空数组
const bboxs = dim.bboxs;
// 上面构建的对象
const lookup = dim.lookup;
// 把数组的每个二维元素集合的点的边界框放置到bboxs[]
for (let i = 0, len = geoms.length; i < len; i++) {
const bbox = tbbox(geoms[i]);
bboxs[i] = {minX: bbox[0], minY: bbox[1], maxX: bbox[2], maxY: bbox[3], id: i};
}
// 把这些元素组成框,构成r-tree结构的树,到时候查询就会快很多
lookup.load(bboxs);
}
}
}
//接受一组要素,计算所有输入要素的bbox,然后返回一个边界框
function bbox(geojson) {
// 新建一个数组
var BBox = [Infinity, Infinity, -Infinity, -Infinity];
meta.coordEach(geojson, function (coord) {
//callback回调函数,取里面二维数组的每一点来与Bbox比较。BBox最后取到二维数组的minX,minY,maxX,maxY
// 取小
if (BBox[0] > coord[0]) BBox[0] = coord[0];
if (BBox[1] > coord[1]) BBox[1] = coord[1];
// 取大
if (BBox[2] < coord[0]) BBox[2] = coord[0];
if (BBox[3] < coord[1]) BBox[3] = coord[1];
});
return BBox;
}
}
function rbush(maxEntries, format) {
if (!(this instanceof rbush)) return new rbush(maxEntries, format);
// max entries in a node is 9 by default; min node fill is 40% for best performance
this._maxEntries = Math.max(4, maxEntries || 9); // 结果是9
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); // 结果是4
if (format) {
this._initFormat(format);
}
this.clear();
}
load: function (data) {
if (!(data && data.length)) return this;
if (data.length < this._minEntries) {
for (var i = 0, len = data.length; i < len; i++) {
this.insert(data[i]);
}
return this;
}
// recursively build the tree with the given data from stratch using OMT algorithm
// omt算法:r-tree
var node = this._build(data.slice(), 0, data.length - 1, 0);
if (!this.data.children.length) {
// save as is if tree is empty
this.data = node;
} else if (this.data.height === node.height) {
// split root if trees have the same height
this._splitRoot(this.data, node);
} else {
if (this.data.height < node.height) {
// swap trees if inserted one is bigger
var tmpNode = this.data;
this.data = node;
node = tmpNode;
}
// insert the small tree into the large tree at appropriate level
this._insert(node, this.data.height - node.height - 1, true);
}
return this;
},
function multiSelect(arr, left, right, n, compare) {
var stack = [left, right],
mid;
while (stack.length) {
right = stack.pop();
left = stack.pop();
if (right - left <= n) continue;
mid = left + Math.ceil((right - left) / n / 2) * n;
quickselect(arr, mid, left, right, compare);
stack.push(left, mid, mid, right);
}
}
_build: function (items, left, right, height) {
var N = right - left + 1,
M = this._maxEntries,
node;
if (N <= M) {
// reached leaf level; return leaf
node = createNode(items.slice(left, right + 1));
calcBBox(node, this.toBBox);
return node;
}
if (!height) {
// target height of the bulk-loaded tree
height = Math.ceil(Math.log(N) / Math.log(M));
// target number of root entries to maximize storage utilization
M = Math.ceil(N / Math.pow(M, height - 1));
}
node = createNode([]);
node.leaf = false;
node.height = height;
// split the items into M mostly square tiles
var N2 = Math.ceil(N / M),
N1 = N2 * Math.ceil(Math.sqrt(M)),
i, j, right2, right3;
multiSelect(items, left, right, N1, this.compareMinX);
for (i = left; i <= right; i += N1) {
right2 = Math.min(i + N1 - 1, right);
multiSelect(items, i, right2, N2, this.compareMinY);
for (j = i; j <= right2; j += N2) {
right3 = Math.min(j + N2 - 1, right2);
// pack each entry recursively
node.children.push(this._build(items, j, right3, height - 1));
}
}
calcBBox(node, this.toBBox);
return node;
},
clear: function () {
this.data = createNode([]);
return this;
},
function createNode(children) {
return {
children: children,
height: 1,
leaf: true,
minX: Infinity,
minY: Infinity,
maxX: -Infinity,
maxY: -Infinity
};
}
class GeoJsonGeometries {
/**
- Create an instance of the geometries extractor.
- @public
- @param {Object} geoJson The GeoJSON from which extract the geometries.
- @param {Object} [options] Optional options.
- @param {boolean} options.ignorePoints If true the extractor will ignore
- geometries of type Point.
- @param {boolean} options.ignoreLines If true the extractor will ignore
- geometries of type LineString.
- @param {boolean} options.ignorePolygon If true the extractor will ignore
- geometries of type Polygon.
*/
constructor(geoJson, options) {
options = typeof options === 'object' ? options : {};
// 构建点,线,面集合 这里是3个空数组
this.pointsList = options.ignorePoints === true ? undefined : [];
this.linesList = options.ignoreLines === true ? undefined : [];
this.polygonsList = options.ignorePolygons === true ? undefined : [];
// 递归的构建不同类型的多边形集合,每个集合都是一个对象。polygonsList这个数组被赋值满
this._loadGeneric(geoJson);
}
_loadGeneric(geoJson, properties) {
if (this.pointsList !== undefined) {
switch (geoJson.type) {
case POINT: {
return this._loadPoint(geoJson.coordinates, properties);
}
case MULTI_POINT: {
return geoJson.coordinates.forEach(coordinates => this._loadPoint(coordinates, properties));
}
default: break;
}
}
if (this.linesList !== undefined) {
switch (geoJson.type) {
case LINE_STRING: {
return this._loadLine(geoJson.coordinates, properties);
}
case MULTI_LINE_STRING: {
return geoJson.coordinates.forEach(coordinates => this._loadLine(coordinates, properties));
}
default: break;
}
}
if (this.polygonsList !== undefined) {
switch (geoJson.type) {
case POLYGON: {
return this._loadPolygon(geoJson.coordinates, properties);
}
// 第二次调用命中这个case,21个元素,第一个元素有1008个,每个元素构建为对象,加载数据到多边形List
case MULTI_POLYGON: {
return geoJson.coordinates.forEach(coordinates => this._loadPolygon(coordinates, properties));
}
default: break;
}
}
switch (geoJson.type) {
case FEATURE: {
return this._loadGeneric(geoJson.geometry, geoJson.properties);
}
case FEATURE_COLLECTION: {
return geoJson.features.forEach(feature => this._loadGeneric(feature.geometry, feature.properties));
}
// 第一次调用命中这个case,这个数组就一个元素
case GEOMETRY_COLLECTION: {
// 递归调用这个方法,
return geoJson.geometries.forEach(geometry => this._loadGeneric(geometry, properties));
}
default: break;
}
}
// 加载数据到多边形List
_loadPolygon(coordinates, properties) {
// 构建面集合中的对象
this.polygonsList.push({type: FEATURE, geometry: {type: POLYGON, coordinates}, properties});
}
hasContainers(geometry, options) {
options = typeof options === 'object' ? options : {};
options.limit = 1;
return this.forEachCotainer(geometry, options) === 1;
}
forEachCotainer(geometry, options, func) {
options = typeof options === 'object' ? options : {};
func = typeof func === 'function' ? func : () => {};
let count = 0;
const size = getGeometryDimension(geometry);
const ignores = [options.ignorePoints, options.ignoreLines, options.ignorePolygons];
for (let d = size; d < 3; d++) {
if (ignores[d] === true) {
continue;
}
const dim = this.D[d];
if (dim.lookup === null) {
continue;
}
// 构建一个矩形,现在输入的点。所以矩形的最大和最小的xy值是一样的
const bbox = tbbox(geometry);
// 查找这个点是否在r-tree中,返回r-tree中最终命中的叶子节点元素
const bboxs = dim.lookup.search({minX: bbox[0], minY: bbox[1], maxX: bbox[2], maxY: bbox[3]});
for (let i = 0, len = bboxs.length; i < len; i++) {
const geom = dim.list[bboxs[i].id];
// geom是否包含geometry,执行的是下面的booleanContains()方法
if (!tcontains(geom, geometry)) {
continue;
}
func(geom, count);
count++;
if (options.limit > 0 && options.limit === count) {
return count;
}
}
}
return count;
}
search: function (bbox) {
var node = this.data,
result = [],
toBBox = this.toBBox;
// 输入的矩形与r-tree是否相交
if (!intersects(bbox, node)) return result;
// 相交往下走
var nodesToSearch = [],
i, len, child, childBBox;
while (node) {
for (i = 0, len = node.children.length; i < len; i++) {
child = node.children[i];
// 如果是叶子节点,那么把叶子节点中的元素集合转化为矩形
// 如果是非叶子节点,则吧子节点当做childBBox
childBBox = node.leaf ? toBBox(child) : child;
// 判断子节点和输入矩形是否相交
if (intersects(bbox, childBBox)) {
// 根节点是叶子节点,则把当前子节点放入结果中
if (node.leaf) result.push(child);
// 输入的矩形是否包含子节点矩形区域
else if (contains(bbox, childBBox)) this._all(child, result);
else nodesToSearch.push(child);
}
}
node = nodesToSearch.pop();
}
return result;
},
// 两个矩形是否相交
function intersects(a, b) {
return b.minX <= a.maxX &&
b.minY <= a.maxY &&
b.maxX >= a.minX &&
b.maxY >= a.minY;
}
// a是否包含b
function contains(a, b) {
return a.minX <= b.minX &&
a.minY <= b.minY &&
b.maxX <= a.maxX &&
b.maxY <= a.maxY;
}
function getGeometryDimension(geometry) {
switch (geometry.type) {
case POINT: return 0;
case LINE_STRING: return 1;
case POLYGON: return 2;
default: throw new TypeError('Unsupported GeoJSON type. Use one of: Point, LineString, Polygon');
}
function booleanContains(feature1, feature2) {
var type1 = invariant.getType(feature1);
var type2 = invariant.getType(feature2);
var geom1 = invariant.getGeom(feature1);
var geom2 = invariant.getGeom(feature2);
var coords1 = invariant.getCoords(feature1);
var coords2 = invariant.getCoords(feature2);
switch (type1) {
case 'Point':
switch (type2) {
case 'Point':
return compareCoords(coords1, coords2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
case 'MultiPoint':
switch (type2) {
case 'Point':
return isPointInMultiPoint(geom1, geom2);
case 'MultiPoint':
return isMultiPointInMultiPoint(geom1, geom2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
case 'LineString':
switch (type2) {
case 'Point':
return isPointOnLine(geom2, geom1, {ignoreEndVertices: true});
case 'LineString':
return isLineOnLine(geom1, geom2);
case 'MultiPoint':
return isMultiPointOnLine(geom1, geom2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
case 'Polygon':
switch (type2) {
case 'Point':
// 点是不是在矩形里面
return booleanPointInPolygon(geom2, geom1, {ignoreBoundary: true});
case 'LineString':
return isLineInPoly(geom1, geom2);
case 'Polygon':
return isPolyInPoly(geom1, geom2);
case 'MultiPoint':
return isMultiPointInPoly(geom1, geom2);
default:
throw new Error('feature2 ' + type2 + ' geometry not supported');
}
default:
throw new Error('feature1 ' + type1 + ' geometry not supported');
}
}
function booleanPointInPolygon(point, polygon, options) {
// Optional parameters
options = options || {};
if (typeof options !== 'object') throw new Error('options is invalid');
var ignoreBoundary = options.ignoreBoundary;
// validation
if (!point) throw new Error('point is required');
if (!polygon) throw new Error('polygon is required');
var pt = invariant.getCoord(point);
var polys = invariant.getCoords(polygon);
var type = (polygon.geometry) ? polygon.geometry.type : polygon.type;
var bbox = polygon.bbox;
// Quick elimination if point is not inside bbox
if (bbox && inBBox(pt, bbox) === false) return false;
// normalize to multipolygon
if (type === 'Polygon') polys = [polys];
for (var i = 0, insidePoly = false; i < polys.length && !insidePoly; i++) {
// check if it is in the outer ring first
if (inRing(pt, polys[i][0], ignoreBoundary)) {
var inHole = false;
var k = 1;
// check for the point in any of the holes
while (k < polys[i].length && !inHole) {
if (inRing(pt, polys[i][k], !ignoreBoundary)) {
inHole = true;
}
k++;
}
if (!inHole) insidePoly = true;
}
}
return insidePoly;
}
function inRing(pt, ring, ignoreBoundary) {
var isInside = false;
if (ring[0][0] === ring[ring.length - 1][0] && ring[0][1] === ring[ring.length - 1][1]) ring = ring.slice(0, ring.length - 1);
for (var i = 0, j = ring.length - 1; i < ring.length; j = i++) {
var xi = ring[i][0], yi = ring[i][1];
var xj = ring[j][0], yj = ring[j][1];
var onBoundary = (pt[1] * (xi - xj) + yi * (xj - pt[0]) + yj * (pt[0] - xi) === 0) &&
((xi - pt[0]) * (xj - pt[0]) <= 0) && ((yi - pt[1]) * (yj - pt[1]) <= 0);
if (onBoundary) return !ignoreBoundary;
var intersect = ((yi > pt[1]) !== (yj > pt[1])) &&
(pt[0] < (xj - xi) * (pt[1] - yi) / (yj - yi) + xi);
if (intersect) isInside = !isInside;
}
return isInside;
}
}
}