UNPKG

terriajs

Version:

Geospatial data visualization platform.

232 lines 9.38 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import Point from "@mapbox/point-geometry"; import bbox from "@turf/bbox"; import booleanIntersects from "@turf/boolean-intersects"; import circle from "@turf/circle"; import { featureCollection } from "@turf/helpers"; import geojsonvt from "geojson-vt"; import { cloneDeep } from "lodash-es"; import { makeObservable, observable, runInAction } from "mobx"; import { GeomType } from "protomaps-leaflet"; import Cartographic from "terriajs-cesium/Source/Core/Cartographic"; import CesiumMath from "terriajs-cesium/Source/Core/Math"; import ImageryLayerFeatureInfo from "terriajs-cesium/Source/Scene/ImageryLayerFeatureInfo"; import { toFeatureCollection } from "../../../Core/GeoJson"; import { LAYER_NAME_PROP, PROTOMAPS_DEFAULT_TILE_SIZE, PROTOMAPS_TILE_BUFFER } from "../../ImageryProvider/ProtomapsImageryProvider"; /** Extent (of coordinates) of tiles generated by geojson-vt */ export const GEOJSON_VT_EXTENT = 4096; /** Layer name to use with geojson-vt * This must be used in PaintRules/LabelRules (eg `dataLayer: "layer"`) */ export const GEOJSON_SOURCE_LAYER_NAME = "layer"; /** Protomaps Geojson source * This source uses geojson-vt to tile geojson data * It is designed to be used with ProtomapsImageryProvider */ export class ProtomapsGeojsonSource { /** Data object from Options */ data; /** Resolved geojsonObject (if applicable) */ geojsonObject; /** Geojson-vt tileIndex (if applicable) */ tileIndex; constructor(url) { makeObservable(this); this.data = url; if (typeof url !== "string") { this.geojsonObject = url; } } /** Fetch geoJSON data (if required) and tile with geojson-vt */ async fetchData() { let result; if (typeof this.data === "string") { result = toFeatureCollection(await (await fetch(this.data)).json()) ?? featureCollection([]); } else { result = this.data; } runInAction(() => (this.geojsonObject = result)); return geojsonvt(result, { buffer: (PROTOMAPS_TILE_BUFFER / PROTOMAPS_DEFAULT_TILE_SIZE) * GEOJSON_VT_EXTENT, extent: GEOJSON_VT_EXTENT, maxZoom: 24 }); } async get(c, tileSize) { if (!this.tileIndex) { this.tileIndex = this.fetchData(); } // request a particular tile const tile = (await this.tileIndex).getTile(c.z, c.x, c.y); const result = new Map(); if (tile && tile.features && tile.features.length > 0) { result.set(GEOJSON_SOURCE_LAYER_NAME, geojsonVtTileToProtomapsFeatures(tile.features, tileSize)); } return result; } async pickFeatures(_x, _y, level, longitude, latitude) { if (!this.geojsonObject) return []; const featureInfos = []; // Get rough meters per pixel (at equator) for given zoom level const zoomMeters = 156543 / Math.pow(2, level); // Create circle with 10 pixel radius to pick features const buffer = circle([CesiumMath.toDegrees(longitude), CesiumMath.toDegrees(latitude)], 10 * zoomMeters, { steps: 10, units: "meters" }); // Create wrappedBuffer with only positive coordinates - this is needed for features which overlap antemeridian const wrappedBuffer = cloneDeep(buffer); wrappedBuffer.geometry.coordinates.forEach((ring) => ring.forEach((point) => { point[0] = point[0] < 0 ? point[0] + 360 : point[0]; })); const bufferBbox = bbox(buffer); // Get array of all features const geojsonFeatures = this.geojsonObject.features; const pickedFeatures = []; for (let index = 0; index < geojsonFeatures.length; index++) { const feature = geojsonFeatures[index]; if (!feature.bbox) { feature.bbox = bbox(feature); } // Filter by bounding box and then intersection with buffer (to minimize calls to booleanIntersects) if (Math.max(feature.bbox[0], // Wrap buffer bbox if necessary feature.bbox[0] > 180 ? bufferBbox[0] + 360 : bufferBbox[0]) <= Math.min(feature.bbox[2], // Wrap buffer bbox if necessary feature.bbox[2] > 180 ? bufferBbox[2] + 360 : bufferBbox[2]) && Math.max(feature.bbox[1], bufferBbox[1]) <= Math.min(feature.bbox[3], bufferBbox[3])) { // If we have longitudes greater than 180 - used wrappedBuffer if (feature.bbox[0] > 180 || feature.bbox[2] > 180) { if (booleanIntersects(feature, wrappedBuffer)) pickedFeatures.push(feature); } else if (booleanIntersects(feature, buffer)) pickedFeatures.push(feature); } } // Convert pickedFeatures to ImageryLayerFeatureInfos pickedFeatures.forEach((f) => { const featureInfo = new ImageryLayerFeatureInfo(); featureInfo.data = f; featureInfo.properties = Object.assign({ [LAYER_NAME_PROP]: GEOJSON_SOURCE_LAYER_NAME }, f.properties ?? {}); if (f.geometry.type === "Point" && typeof f.geometry.coordinates[0] === "number" && typeof f.geometry.coordinates[1] === "number") { featureInfo.position = Cartographic.fromDegrees(f.geometry.coordinates[0], f.geometry.coordinates[1]); } featureInfo.configureDescriptionFromProperties(f.properties); featureInfo.configureNameFromProperties(f.properties); featureInfos.push(featureInfo); }); return featureInfos; } } __decorate([ observable.ref ], ProtomapsGeojsonSource.prototype, "geojsonObject", void 0); export const geomTypeMap = (type) => { switch (type) { case "Point": case "MultiPoint": return GeomType.Point; case "LineString": case "MultiLineString": return GeomType.Line; case "Polygon": case "MultiPolygon": return GeomType.Polygon; default: return null; } }; export function geojsonVtTileToProtomapsFeatures(features, tileSize) { const scale = tileSize / GEOJSON_VT_EXTENT; return features .map((f) => { let transformedGeom; let numVertices; // Calculate bbox const bbox = { minX: Infinity, minY: Infinity, maxX: -Infinity, maxY: -Infinity }; // Multi-polygon if (Array.isArray(f.geometry[0][0])) { // Note: the type is incorrect here const geom = f.geometry; transformedGeom = geom.map((g1) => g1.map((g2) => { const x = g2[0] * scale; const y = g2[1] * scale; if (bbox.minX > x) { bbox.minX = x; } if (bbox.maxX < x) { bbox.maxX = x; } if (bbox.minY > y) { bbox.minY = y; } if (bbox.maxY < y) { bbox.maxY = y; } return new Point(x, y); })); numVertices = transformedGeom.reduce((count, current) => count + current.length, 0); } // Other feature types else { const geom = f.geometry; transformedGeom = [ geom.map((g1) => { const x = g1[0] * scale; const y = g1[1] * scale; if (bbox.minX > x) { bbox.minX = x; } if (bbox.maxX < x) { bbox.maxX = x; } if (bbox.minY > y) { bbox.minY = y; } if (bbox.maxY < y) { bbox.maxY = y; } return new Point(x, y); }) ]; numVertices = transformedGeom.length; } if (f.type === 0) return null; const geomType = { [1]: GeomType.Point, [2]: GeomType.Line, [3]: GeomType.Polygon }[f.type]; const feature = { props: { ...(f.tags ?? {}) }, bbox, geomType, geom: transformedGeom, numVertices }; return feature; }) .filter((f) => f !== null); } //# sourceMappingURL=ProtomapsGeojsonSource.js.map