-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Translate deckgl-layers/3d-building-layer to .ts
Signed-off-by: Daria Terekhova <daria.terekhova@actionengine.com>
- Loading branch information
1 parent
7ada98a
commit 6b5b051
Showing
3 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
// Copyright (c) 2022 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
import {CompositeLayer} from '@deck.gl/core'; | ||
import {TileLayer as DeckGLTileLayer} from '@deck.gl/geo-layers'; | ||
import {getTileData} from './3d-building-utils'; | ||
import {ThreeDBuildingLayerProps, Coordinates, TileDataItem} from './types'; | ||
import {SolidPolygonLayer} from '@deck.gl/layers'; | ||
|
||
export default class ThreeDBuildingLayer extends CompositeLayer<ThreeDBuildingLayerProps> { | ||
// this layer add its subLayers to the redux store, and push sample data | ||
|
||
renderSubLayers(props: ThreeDBuildingLayerProps) { | ||
return new SolidPolygonLayer({ | ||
...props, | ||
parameter: { | ||
blendFunc: ['SRC_ALPHA', 'ONE_MINUS_SRC_ALPHA', 'ONE', 'ONE_MINUS_SRC_ALPHA'], | ||
blendEquation: ['FUNC_ADD', 'FUNC_ADD'] | ||
}, | ||
extruded: true, | ||
opacity: 1, | ||
filled: true, | ||
getElevation: (feature: TileDataItem) => feature.properties.height || 0, | ||
getPolygon: (feature: TileDataItem) => feature.coordinates, | ||
getFillColor: this.props.threeDBuildingColor | ||
}); | ||
} | ||
|
||
renderLayers() { | ||
return [ | ||
new DeckGLTileLayer({ | ||
getTileData: (args: Coordinates) => | ||
getTileData(this.props.mapboxApiUrl, this.props.mapboxApiAccessToken, args), | ||
minZoom: 13, | ||
renderSubLayers: this.renderSubLayers.bind(this), | ||
updateTriggers: this.props.updateTriggers | ||
}) | ||
]; | ||
} | ||
} |
211 changes: 211 additions & 0 deletions
211
src/deckgl-layers/3d-building-layer/3d-building-utils.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
// Copyright (c) 2022 Uber Technologies, Inc. | ||
// | ||
// Permission is hereby granted, free of charge, to any person obtaining a copy | ||
// of this software and associated documentation files (the "Software"), to deal | ||
// in the Software without restriction, including without limitation the rights | ||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
// copies of the Software, and to permit persons to whom the Software is | ||
// furnished to do so, subject to the following conditions: | ||
// | ||
// The above copyright notice and this permission notice shall be included in | ||
// all copies or substantial portions of the Software. | ||
// | ||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
// THE SOFTWARE. | ||
|
||
import Protobuf from 'pbf'; | ||
import {VectorTile} from '@mapbox/vector-tile'; | ||
import {worldToLngLat} from 'viewport-mercator-project'; | ||
import { | ||
Coordinates, | ||
FlatFigure, | ||
TileDataItem, | ||
VectorTileFeature, | ||
VectorTileFeatureProperties | ||
} from './types'; | ||
|
||
/* global fetch */ | ||
const TILE_SIZE = 512; | ||
const MAPBOX_HOST = 'https://a.tiles.mapbox.com'; | ||
const MAP_SOURCE = '/v4/mapbox.mapbox-streets-v7'; | ||
|
||
export function getTileData( | ||
host: string, | ||
token: string, | ||
{x, y, z}: Coordinates | ||
): Promise<TileDataItem[]> { | ||
const mapSource = `${host || | ||
MAPBOX_HOST}${MAP_SOURCE}/${z}/${x}/${y}.vector.pbf?access_token=${token}`; | ||
|
||
return fetch(mapSource) | ||
.then(response => response.arrayBuffer()) | ||
.then(buffer => decodeTile(x, y, z, buffer)); | ||
} | ||
|
||
export function decodeTile( | ||
x: number, | ||
y: number, | ||
z: number, | ||
arrayBuffer: ArrayBuffer | ||
): TileDataItem[] { | ||
const tile = new VectorTile(new Protobuf(arrayBuffer)); | ||
|
||
const result: TileDataItem[] = []; | ||
const xProj = x * TILE_SIZE; | ||
const yProj = y * TILE_SIZE; | ||
const scale = Math.pow(2, z); | ||
|
||
const projectFunc = project.bind(null, xProj, yProj, scale); | ||
|
||
/* eslint-disable guard-for-in */ | ||
const layerName = 'building'; | ||
const vectorTileLayer = tile.layers[layerName]; | ||
if (!vectorTileLayer) { | ||
return []; | ||
} | ||
for (let i = 0; i < vectorTileLayer.length; i++) { | ||
const vectorTileFeature = vectorTileLayer.feature(i); | ||
const features = vectorTileFeatureToProp(vectorTileFeature, projectFunc); | ||
features.forEach(f => { | ||
f.properties.layer = layerName; | ||
if (f.properties.height) { | ||
result.push(f); | ||
} | ||
}); | ||
} | ||
return result; | ||
} | ||
|
||
function project(x: number, y: number, scale: number, line: FlatFigure, extent: number): void { | ||
const sizeToPixel = extent / TILE_SIZE; | ||
|
||
for (let ii = 0; ii < line.length; ii++) { | ||
const p = line[ii]; | ||
// LNGLAT | ||
line[ii] = worldToLngLat([x + p[0] / sizeToPixel, y + p[1] / sizeToPixel], scale); | ||
} | ||
} | ||
|
||
/* adapted from @mapbox/vector-tile/lib/vectortilefeature.js for better perf */ | ||
/* eslint-disable */ | ||
export function vectorTileFeatureToProp( | ||
vectorTileFeature: VectorTileFeature, | ||
project: (r: FlatFigure, n: number) => void | ||
): {coordinates: FlatFigure[]; properties: VectorTileFeatureProperties}[] { | ||
let coords: FlatFigure[][] | FlatFigure[] = getCoordinates(vectorTileFeature); | ||
const extent = vectorTileFeature.extent; | ||
let i: number; | ||
let j: number; | ||
|
||
coords = classifyRings(coords); | ||
for (i = 0; i < coords.length; i++) { | ||
for (j = 0; j < coords[i].length; j++) { | ||
project(coords[i][j], extent); | ||
} | ||
} | ||
|
||
return coords.map(coordinates => ({ | ||
coordinates, | ||
properties: vectorTileFeature.properties | ||
})); | ||
} | ||
|
||
function getCoordinates(vectorTileFeature: VectorTileFeature): FlatFigure[] { | ||
const pbf = vectorTileFeature._pbf; | ||
pbf.pos = vectorTileFeature._geometry; | ||
|
||
const end = pbf.readVarint() + pbf.pos; | ||
let cmd = 1; | ||
let length = 0; | ||
let x = 0; | ||
let y = 0; | ||
|
||
const lines: FlatFigure[] = []; | ||
let line: FlatFigure | undefined; | ||
|
||
while (pbf.pos < end) { | ||
if (length <= 0) { | ||
const cmdLen = pbf.readVarint(); | ||
cmd = cmdLen & 0x7; | ||
length = cmdLen >> 3; | ||
} | ||
|
||
length--; | ||
|
||
if (cmd === 1 || cmd === 2) { | ||
x += pbf.readSVarint(); | ||
y += pbf.readSVarint(); | ||
|
||
if (cmd === 1) { | ||
// moveTo | ||
if (line) lines.push(line); | ||
line = []; | ||
} | ||
|
||
if (line) line.push([x, y]); | ||
} else if (cmd === 7) { | ||
// Workaround for https://github.com/mapbox/mapnik-vector-tile/issues/90 | ||
if (line) { | ||
line.push(line[0].slice()); // closePolygon | ||
} | ||
} else { | ||
throw new Error(`unknown command ${cmd}`); | ||
} | ||
} | ||
|
||
if (line) lines.push(line); | ||
|
||
return lines; | ||
} | ||
|
||
// classifies an array of rings into polygons with outer rings and holes | ||
|
||
function classifyRings(rings: FlatFigure[]): FlatFigure[][] { | ||
const len = rings.length; | ||
|
||
if (len <= 1) return [rings]; | ||
|
||
const polygons: FlatFigure[][] = []; | ||
let polygon: FlatFigure[] | undefined; | ||
let ccw: boolean | undefined; | ||
|
||
for (let i = 0; i < len; i++) { | ||
const area = signedArea(rings[i]); | ||
if (area === 0) { | ||
continue; | ||
} | ||
|
||
if (ccw === undefined) { | ||
ccw = area < 0; | ||
} | ||
|
||
if (ccw === area < 0) { | ||
if (polygon) { | ||
polygons.push(polygon); | ||
} | ||
polygon = [rings[i]]; | ||
} else if (polygon) { | ||
polygon.push(rings[i]); | ||
} | ||
} | ||
if (polygon) { | ||
polygons.push(polygon); | ||
} | ||
|
||
return polygons; | ||
} | ||
|
||
function signedArea(ring: FlatFigure): number { | ||
let sum = 0; | ||
for (let i = 0, len = ring.length, j = len - 1, p1: number[], p2: number[]; i < len; j = i++) { | ||
p1 = ring[i]; | ||
p2 = ring[j]; | ||
sum += (p2[0] - p1[0]) * (p1[1] + p2[1]); | ||
} | ||
return sum; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import {RGBColor} from '../../reducers'; | ||
|
||
export type ThreeDBuildingLayerProps = { | ||
id: string; | ||
mapboxApiAccessToken: string; | ||
mapboxApiUrl: string; | ||
threeDBuildingColor: RGBColor; | ||
updateTriggers: { | ||
getFillColor: RGBColor; | ||
}; | ||
}; | ||
export type Coordinates = {x: number; y: number; z: number}; | ||
// TODO rename | ||
export type FlatFigure = number[][]; | ||
export type TileDataItem = {coordinates: FlatFigure[]; properties: VectorTileFeatureProperties}; | ||
export type VectorTileFeatureProperties = {layer: string; height?: number}; | ||
export type VectorTileFeature = { | ||
extent: number; | ||
properties: VectorTileFeatureProperties; | ||
_pbf: { | ||
buf: ArrayBuffer; | ||
pos: number; | ||
type: number; | ||
length: number; | ||
readVarint: (b?: boolean) => number; | ||
readSVarint: () => number; | ||
}; | ||
_geometry: number; | ||
}; |