packages/layers/geojson/src/workers/PolygonGeomWorker.ts (195 lines of code) (raw):

/** * Copyright (C) 2021 Alibaba Group Holding Limited * All rights reserved. */ import earcut from 'earcut' // @TODO 体积过大,需要全部换成按需引入 // import * as turf from '@turf/turf' import { flattenEach } from '@turf/meta' import { getGeom, getCoords } from '@turf/invariant' import bbox from '@turf/bbox' import inside from 'point-in-polygon' // import classifyPoint from 'robust-point-in-polygon' // jsts import ConformingDelaunayTriangulationBuilder from 'jsts-esm/esm/org/locationtech/jts/triangulate/ConformingDelaunayTriangulationBuilder' import GeometryFactory from 'jsts-esm/esm/org/locationtech/jts/geom/GeometryFactory' import Coordinate from 'jsts-esm/esm/org/locationtech/jts/geom/Coordinate' import { patchPreReturnedMessage } from '@polaris.gl/utils-worker-manager' const _self: Worker = self as any /** * Message receiver */ _self.addEventListener('message', (e) => { const info = e.data let result: any switch (info.task) { case 'triangulate': { const feature = info.feature const index = feature.index result = triangulateGeoJSON(feature) result.data.index = index break } case 'tessellation': { const feature = info.feature const index = feature.index result = CDTGeojsonWithSubdivision(feature, Math.pow(2, info.tessellation)) result.data.index = index break } default: console.error(`Polaris::PolygonSurfaceLayer - Invalid worker task: ${info.task}`) postMessage(patchPreReturnedMessage(e, { error: `Invalid worker task: ${info.task}` })) return } patchPreReturnedMessage(e, result.data) postMessage(result.data, result.transferables) }) _self.addEventListener('error', (e) => { throw new Error(e.message) }) function triangulateGeoJSON(geojson: any): any { let points: Array<number> = [] let triangles: Array<number> = [] flattenEach(geojson, (feature: any) => { if (!getGeom(feature)) return const coords = getCoords(feature) const data = earcut.flatten(coords) const _triangles = earcut(data.vertices, data.holes, data.dimensions) const offset = points.length / data.dimensions if (offset === 0) { triangles = _triangles points = data.vertices } else { for (let i = 0; i < _triangles.length; i++) { triangles.push(_triangles[i] + offset) } for (let i = 0; i < data.vertices.length; i++) { points.push(data.vertices[i]) } } }) return { data: { points, triangles, }, transferables: undefined, } } // 带细分的CDT三角化 function CDTGeojsonWithSubdivision(data, subdivision): any { // console.log('CDTGeojsonWithSubdivision start') let points: number[] = [] let triangles: number[] = [] let steiners: number[][] = [] // let lnglats = [] // 这个会拆掉所有的multiGeom,拆成不同的feature // const _t = performance.now() // console.time('细分') flattenEach(data, (feature) => { const geom = getGeom(feature) if (!geom) return // feature = turf.cleanCoords(feature) if (!feature.properties) return const coords = getCoords(feature) // 与地球的细分对齐,进行差点 const dLng = 360 / subdivision const dLat = 360 / subdivision // https://tools.ietf.org/html/rfc7946#section-5 const _bbox = bbox(feature) // 左边界(最小经度) const left = Math.floor(_bbox[0] / dLng) // 下边界 const btm = Math.floor(_bbox[1] / dLat) // 右边界(最大经度) const right = Math.ceil(_bbox[2] / dLng) // 上边界 const top = Math.ceil(_bbox[3] / dLat) // https://en.wikipedia.org/wiki/Steiner_point const steinerPoints: number[][] = [] for (let i = left; i <= right; i++) { for (let j = btm; j <= top; j++) { const lnglat = [i * dLng, j * dLat] if (pointInsidePolygon(lnglat, coords)) { steinerPoints.push(lnglat) } } } // debugger // console.log('细分固定点', steinerPoints.length); // console.log(steinerPoints); // console.time('CDTtriangulate'); const { triangles: _triangles, points: _points } = CDTtriangulate(coords, steinerPoints) // console.timeEnd('CDTtriangulate'); const offset = points.length / 2 if (offset === 0) { triangles = _triangles points = _points steiners = steinerPoints } else { for (let i = 0; i < _triangles.length; i++) { triangles.push(_triangles[i] + offset) } for (let i = 0; i < _points.length; i++) { points.push(_points[i]) } for (let i = 0; i < steinerPoints.length; i++) { steiners.push(steinerPoints[i]) } } }) // console.log('CDTGeojsonWithSubdivision end') return { data: { points, triangles, steiners, }, transferables: undefined, } } /** * @param {Array<[Num:x, Num:y]>} boundary 外边界 * @param {Array<[Num:x, Num:y]>} steinerPoints 内差点 * @return {{triangles<[a,b,c, a,b,c, ...], points<[[x,y], [x,y]]>>}} */ // https://blog.csdn.net/cdl2008sky/article/details/7268577 const geomFact = new GeometryFactory() function CDTtriangulate(boundaries, steinerPoints) { const boundary = boundaries[0] // console.log(boundary.length + steinerPoints.length); const builder = new ConformingDelaunayTriangulationBuilder() // 阈值 builder.setTolerance(0) // 顶点们 (不包含限制边) // builder.setSites(Geometry geom) // @NOTE 不能用 MultiPoint 会报错 steinerPoints = [boundary[0], boundary[1], ...steinerPoints] // steinerPoints.push(boundary[0], boundary[1]) // const sites = geomFact.createMultiPoint(steinerPoints.map((p, index) => { const sites = geomFact.createLineString(steinerPoints.map((p) => new Coordinate(...p))) // const sites = geomFact.createGeometryCollection([_sites]) builder.setSites(sites) // 限制边 // builder.setConstraints(Geometry constraintLines) const shell = geomFact.createLinearRing(boundary.map((p) => new Coordinate(...p))) const holes: any = [] for (let i = 1; i < boundaries.length; i++) { const holeBoundary = boundaries[i] const hole = geomFact.createLinearRing(holeBoundary.map((p) => new Coordinate(...p))) holes.push(hole) } const constraintLines = geomFact.createPolygon(shell, holes) builder.setConstraints(constraintLines) // 获取三角形 const _triangles = builder.getTriangles(geomFact) // debugger const triangles: number[] = [] const points: number[] = [] // @WARNNING @NOTE java接口太麻烦了,接下来开始破坏封装 let index = 0 _triangles._geometries.forEach((polygon) => { const ps = polygon._shell._points._coordinates const center = [(ps[0].x + ps[1].x + ps[2].x) / 3, (ps[0].y + ps[1].y + ps[2].y) / 3] if (!pointInsidePolygon(center, boundaries)) { return } points.push(ps[0].x, ps[0].y) points.push(ps[1].x, ps[1].y) points.push(ps[2].x, ps[2].y) triangles.push(index * 3 + 0) triangles.push(index * 3 + 1) triangles.push(index * 3 + 2) index++ }) return { triangles, points, } } const R2D = 180 / Math.PI const D2R = Math.PI / 180 function pointInsidePolygon(point, boundaries) { if (!boundaries[0][0][0]) { boundaries = [boundaries] } // [ // [ring [], [], []], // [hole [], []], // ] const boundary = boundaries[0] // const A = inside(point, polygon) // const B = classifyPoint(polygon, point) // console.log(A, B); if (!inside(point, boundary)) { return false } for (let i = 1; i < boundaries.length; i++) { const holeBoundary = boundaries[i] if (inside(point, holeBoundary)) { return false } } return true // @NOTE 这个方法相对于 point-in-polygon 的优势是可以排除 落在边界上的点 // 这两个都不支持hole // https://www.npmjs.com/package/robust-point-in-polygon // return classifyPoint(polygon, point) === -1 }