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文件轉(zhuǎn)為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 : {};
// 構(gòu)建成員變量:點农猬,線赡艰,面集合,只有面List有數(shù)據(jù)
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的集合數(shù)據(jù)
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];
// 循環(huán)到d=2
if (dim.list.length > 0) {
dim.bboxs = new Array(dim.list.length);
// 構(gòu)造一個對象:
//{
// data: {children: Array(0), height: 1, leaf: true, maxX: -Infinity,maxY: -Infinity,minX: Infinity,minY: Infinity}
// _maxEntries: 9
// _minEntries: 4
//}
dim.lookup = rbush();
// 數(shù)組集合:{type: FEATURE, geometry: {type: POLYGON, coordinates}, properties}
const geoms = dim.list;
// 21個zise的空數(shù)組
const bboxs = dim.bboxs;
// 上面構(gòu)建的對象
const lookup = dim.lookup;
// 把數(shù)組的每個二維元素集合的點的邊界框放置到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};
}
// 把這些元素組成框斤葱,構(gòu)成r-tree結(jié)構(gòu)的樹慷垮,到時候查詢就會快很多
lookup.load(bboxs);
}
}
}
//接受一組要素揖闸,計算所有輸入要素的bbox,然后返回一個邊界框
function bbox(geojson) {
// 新建一個數(shù)組
var BBox = [Infinity, Infinity, -Infinity, -Infinity];
meta.coordEach(geojson, function (coord) {
//callback回調(diào)函數(shù)料身,取里面二維數(shù)組的每一點來與Bbox比較汤纸。BBox最后取到二維數(shù)組的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); // 結(jié)果是9
this._minEntries = Math.max(2, Math.ceil(this._maxEntries * 0.4)); // 結(jié)果是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 : {};
// 構(gòu)建點愉棱,線算墨,面集合 這里是3個空數(shù)組
this.pointsList = options.ignorePoints === true ? undefined : [];
this.linesList = options.ignoreLines === true ? undefined : [];
this.polygonsList = options.ignorePolygons === true ? undefined : [];
// 遞歸的構(gòu)建不同類型的多邊形集合爹谭,每個集合都是一個對象说贝。polygonsList這個數(shù)組被賦值滿
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);
}
// 第二次調(diào)用命中這個case,21個元素乡恕,第一個元素有1008個运杭,每個元素構(gòu)建為對象,加載數(shù)據(jù)到多邊形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));
}
// 第一次調(diào)用命中這個case,這個數(shù)組就一個元素
case GEOMETRY_COLLECTION: {
// 遞歸調(diào)用這個方法,
return geoJson.geometries.forEach(geometry => this._loadGeneric(geometry, properties));
}
default: break;
}
}
// 加載數(shù)據(jù)到多邊形List
_loadPolygon(coordinates, properties) {
// 構(gòu)建面集合中的對象
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;
}
// 構(gòu)建一個矩形血筑,現(xiàn)在輸入的點梆砸。所以矩形的最大和最小的xy值是一樣的
const bbox = tbbox(geometry);
// 查找這個點是否在r-tree中,返回r-tree中最終命中的葉子節(jié)點元素
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,執(zhí)行的是下面的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];
// 如果是葉子節(jié)點,那么把葉子節(jié)點中的元素集合轉(zhuǎn)化為矩形
// 如果是非葉子節(jié)點,則吧子節(jié)點當做childBBox
childBBox = node.leaf ? toBBox(child) : child;
// 判斷子節(jié)點和輸入矩形是否相交
if (intersects(bbox, childBBox)) {
// 根節(jié)點是葉子節(jié)點杨耙,則把當前子節(jié)點放入結(jié)果中
if (node.leaf) result.push(child);
// 輸入的矩形是否包含子節(jié)點矩形區(qū)域
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;
}
}
}