UNPKG

@deck.gl/geo-layers

Version:

deck.gl layers supporting geospatial use cases and GIS formats

318 lines (277 loc) 7.85 kB
import { h3ToGeoBoundary, h3GetResolution, h3ToGeo, geoToH3, h3IsPentagon, h3Distance, edgeLength, UNITS } from 'h3-js'; import { lerp } from '@math.gl/core'; import { CompositeLayer, createIterable } from '@deck.gl/core'; import { ColumnLayer, PolygonLayer } from '@deck.gl/layers'; const UPDATE_THRESHOLD_KM = 10; export function normalizeLongitudes(vertices, refLng) { refLng = refLng === undefined ? vertices[0][0] : refLng; for (const pt of vertices) { const deltaLng = pt[0] - refLng; if (deltaLng > 180) { pt[0] -= 360; } else if (deltaLng < -180) { pt[0] += 360; } } } export function scalePolygon(hexId, vertices, factor) { const [lat, lng] = h3ToGeo(hexId); const actualCount = vertices.length; normalizeLongitudes(vertices, lng); const vertexCount = vertices[0] === vertices[actualCount - 1] ? actualCount - 1 : actualCount; for (let i = 0; i < vertexCount; i++) { vertices[i][0] = lerp(lng, vertices[i][0], factor); vertices[i][1] = lerp(lat, vertices[i][1], factor); } } function getHexagonCentroid(getHexagon, object, objectInfo) { const hexagonId = getHexagon(object, objectInfo); const [lat, lng] = h3ToGeo(hexagonId); return [lng, lat]; } function h3ToPolygon(hexId, coverage = 1, flatten) { const vertices = h3ToGeoBoundary(hexId, true); if (coverage !== 1) { scalePolygon(hexId, vertices, coverage); } else { normalizeLongitudes(vertices); } if (flatten) { const positions = new Float64Array(vertices.length * 2); let i = 0; for (const pt of vertices) { positions[i++] = pt[0]; positions[i++] = pt[1]; } return positions; } return vertices; } function mergeTriggers(getHexagon, coverage) { let trigger; if (getHexagon === undefined || getHexagon === null) { trigger = coverage; } else if (typeof getHexagon === 'object') { trigger = { ...getHexagon, coverage }; } else { trigger = { getHexagon, coverage }; } return trigger; } const defaultProps = { ...PolygonLayer.defaultProps, highPrecision: 'auto', coverage: { type: 'number', min: 0, max: 1, value: 1 }, centerHexagon: null, getHexagon: { type: 'accessor', value: x => x.hexagon }, extruded: true }; delete defaultProps.getLineDashArray; export default class H3HexagonLayer extends CompositeLayer { shouldUpdateState({ changeFlags }) { return this._shouldUseHighPrecision() ? changeFlags.propsOrDataChanged : changeFlags.somethingChanged; } updateState({ props, oldProps, changeFlags }) { if (props.highPrecision !== true && (changeFlags.dataChanged || changeFlags.updateTriggers && changeFlags.updateTriggers.getHexagon)) { const dataProps = this._calculateH3DataProps(props); this.setState(dataProps); } this._updateVertices(this.context.viewport); } _calculateH3DataProps(props) { let resolution = -1; let hasPentagon = false; let hasMultipleRes = false; const { iterable, objectInfo } = createIterable(props.data); for (const object of iterable) { objectInfo.index++; const hexId = props.getHexagon(object, objectInfo); const hexResolution = h3GetResolution(hexId); if (resolution < 0) { resolution = hexResolution; if (!props.highPrecision) break; } else if (resolution !== hexResolution) { hasMultipleRes = true; break; } if (h3IsPentagon(hexId)) { hasPentagon = true; break; } } return { resolution, edgeLengthKM: resolution >= 0 ? edgeLength(resolution, UNITS.km) : 0, hasMultipleRes, hasPentagon }; } _shouldUseHighPrecision() { if (this.props.highPrecision === 'auto') { const { resolution, hasPentagon, hasMultipleRes } = this.state; const { viewport } = this.context; return viewport.resolution || hasMultipleRes || hasPentagon || resolution >= 0 && resolution <= 5; } return this.props.highPrecision; } _updateVertices(viewport) { if (this._shouldUseHighPrecision()) { return; } const { resolution, edgeLengthKM, centerHex } = this.state; if (resolution < 0) { return; } const hex = this.props.centerHexagon || geoToH3(viewport.latitude, viewport.longitude, resolution); if (centerHex === hex) { return; } if (centerHex) { const distance = h3Distance(centerHex, hex); if (distance >= 0 && distance * edgeLengthKM < UPDATE_THRESHOLD_KM) { return; } } const { unitsPerMeter } = viewport.distanceScales; let vertices = h3ToPolygon(hex); const [centerLat, centerLng] = h3ToGeo(hex); const [centerX, centerY] = viewport.projectFlat([centerLng, centerLat]); vertices = vertices.map(p => { const worldPosition = viewport.projectFlat(p); return [(worldPosition[0] - centerX) / unitsPerMeter[0], (worldPosition[1] - centerY) / unitsPerMeter[1]]; }); this.setState({ centerHex: hex, vertices }); } renderLayers() { return this._shouldUseHighPrecision() ? this._renderPolygonLayer() : this._renderColumnLayer(); } _getForwardProps() { const { elevationScale, material, coverage, extruded, wireframe, stroked, filled, lineWidthUnits, lineWidthScale, lineWidthMinPixels, lineWidthMaxPixels, getFillColor, getElevation, getLineColor, getLineWidth, transitions, updateTriggers } = this.props; return { elevationScale, extruded, coverage, wireframe, stroked, filled, lineWidthUnits, lineWidthScale, lineWidthMinPixels, lineWidthMaxPixels, material, getElevation, getFillColor, getLineColor, getLineWidth, transitions, updateTriggers: { getFillColor: updateTriggers.getFillColor, getElevation: updateTriggers.getElevation, getLineColor: updateTriggers.getLineColor, getLineWidth: updateTriggers.getLineWidth } }; } _renderPolygonLayer() { const { data, getHexagon, updateTriggers, coverage } = this.props; const SubLayerClass = this.getSubLayerClass('hexagon-cell-hifi', PolygonLayer); const forwardProps = this._getForwardProps(); forwardProps.updateTriggers.getPolygon = mergeTriggers(updateTriggers.getHexagon, coverage); return new SubLayerClass(forwardProps, this.getSubLayerProps({ id: 'hexagon-cell-hifi', updateTriggers: forwardProps.updateTriggers }), { data, _normalize: false, _windingOrder: 'CCW', positionFormat: 'XY', getPolygon: (object, objectInfo) => { const hexagonId = getHexagon(object, objectInfo); return h3ToPolygon(hexagonId, coverage, true); } }); } _renderColumnLayer() { const { data, getHexagon, updateTriggers } = this.props; const SubLayerClass = this.getSubLayerClass('hexagon-cell', ColumnLayer); const forwardProps = this._getForwardProps(); forwardProps.updateTriggers.getPosition = updateTriggers.getHexagon; return new SubLayerClass(forwardProps, this.getSubLayerProps({ id: 'hexagon-cell', flatShading: true, updateTriggers: forwardProps.updateTriggers }), { data, diskResolution: 6, radius: 1, vertices: this.state.vertices, getPosition: getHexagonCentroid.bind(null, getHexagon) }); } } H3HexagonLayer.defaultProps = defaultProps; H3HexagonLayer.layerName = 'H3HexagonLayer'; //# sourceMappingURL=h3-hexagon-layer.js.map