UNPKG

@deck.gl/geo-layers

Version:

deck.gl layers supporting geospatial use cases and GIS formats

177 lines (146 loc) 4.82 kB
import { CullingVolume, Plane, AxisAlignedBoundingBox, makeOrientedBoundingBoxFromPoints } from '@math.gl/culling'; import { lngLatToWorld } from '@math.gl/web-mercator'; import { osmTile2lngLat } from './utils'; const TILE_SIZE = 512; const MAX_MAPS = 3; const REF_POINTS_5 = [[0.5, 0.5], [0, 0], [0, 1], [1, 0], [1, 1]]; const REF_POINTS_9 = REF_POINTS_5.concat([[0, 0.5], [0.5, 0], [1, 0.5], [0.5, 1]]); const REF_POINTS_11 = REF_POINTS_9.concat([[0.25, 0.5], [0.75, 0.5]]); class OSMNode { constructor(x, y, z) { this.x = x; this.y = y; this.z = z; } get children() { if (!this._children) { const x = this.x * 2; const y = this.y * 2; const z = this.z + 1; this._children = [new OSMNode(x, y, z), new OSMNode(x, y + 1, z), new OSMNode(x + 1, y, z), new OSMNode(x + 1, y + 1, z)]; } return this._children; } update(params) { const { viewport, cullingVolume, elevationBounds, minZ, maxZ, bounds, offset, project } = params; const boundingVolume = this.getBoundingVolume(elevationBounds, offset, project); if (bounds && !this.insideBounds(bounds)) { return false; } const isInside = cullingVolume.computeVisibility(boundingVolume); if (isInside < 0) { return false; } if (!this.childVisible) { let { z } = this; if (z < maxZ && z >= minZ) { const distance = boundingVolume.distanceTo(viewport.cameraPosition) * viewport.scale / viewport.height; z += Math.floor(Math.log2(distance)); } if (z >= maxZ) { this.selected = true; return true; } } this.selected = false; this.childVisible = true; for (const child of this.children) { child.update(params); } return true; } getSelected(result = []) { if (this.selected) { result.push(this); } if (this._children) { for (const node of this._children) { node.getSelected(result); } } return result; } insideBounds([minX, minY, maxX, maxY]) { const scale = Math.pow(2, this.z); const extent = TILE_SIZE / scale; return this.x * extent < maxX && this.y * extent < maxY && (this.x + 1) * extent > minX && (this.y + 1) * extent > minY; } getBoundingVolume(zRange, worldOffset, project) { if (project) { const refPoints = this.z < 1 ? REF_POINTS_11 : this.z < 2 ? REF_POINTS_9 : REF_POINTS_5; const refPointPositions = []; for (const p of refPoints) { const lngLat = osmTile2lngLat(this.x + p[0], this.y + p[1], this.z); lngLat[2] = zRange[0]; refPointPositions.push(project(lngLat)); if (zRange[0] !== zRange[1]) { lngLat[2] = zRange[1]; refPointPositions.push(project(lngLat)); } } return makeOrientedBoundingBoxFromPoints(refPointPositions); } const scale = Math.pow(2, this.z); const extent = TILE_SIZE / scale; const originX = this.x * extent + worldOffset * TILE_SIZE; const originY = TILE_SIZE - (this.y + 1) * extent; return new AxisAlignedBoundingBox([originX, originY, zRange[0]], [originX + extent, originY + extent, zRange[1]]); } } export function getOSMTileIndices(viewport, maxZ, zRange, bounds) { const project = viewport.resolution ? viewport.projectPosition : null; const planes = Object.values(viewport.getFrustumPlanes()).map(({ normal, distance }) => new Plane(normal.clone().negate(), distance)); const cullingVolume = new CullingVolume(planes); const unitsPerMeter = viewport.distanceScales.unitsPerMeter[2]; const elevationMin = zRange && zRange[0] * unitsPerMeter || 0; const elevationMax = zRange && zRange[1] * unitsPerMeter || 0; const minZ = viewport.pitch <= 60 ? maxZ : 0; if (bounds) { const [minLng, minLat, maxLng, maxLat] = bounds; const topLeft = lngLatToWorld([minLng, maxLat]); const bottomRight = lngLatToWorld([maxLng, minLat]); bounds = [topLeft[0], TILE_SIZE - topLeft[1], bottomRight[0], TILE_SIZE - bottomRight[1]]; } const root = new OSMNode(0, 0, 0); const traversalParams = { viewport, project, cullingVolume, elevationBounds: [elevationMin, elevationMax], minZ, maxZ, bounds, offset: 0 }; root.update(traversalParams); if (viewport.subViewports && viewport.subViewports.length > 1) { traversalParams.offset = -1; while (root.update(traversalParams)) { if (--traversalParams.offset < -MAX_MAPS) { break; } } traversalParams.offset = 1; while (root.update(traversalParams)) { if (++traversalParams.offset > MAX_MAPS) { break; } } } return root.getSelected(); } //# sourceMappingURL=tile-2d-traversal.js.map