@deck.gl/geo-layers
Version:
deck.gl layers supporting geospatial use cases and GIS formats
318 lines (277 loc) • 7.85 kB
JavaScript
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