Skip to content

Commit

Permalink
Translate deckgl-layers/3d-building-layer to .ts
Browse files Browse the repository at this point in the history
Signed-off-by: Daria Terekhova <daria.terekhova@actionengine.com>
  • Loading branch information
dariaterekhova-actionengine committed Apr 19, 2022
1 parent 7ada98a commit 6b5b051
Show file tree
Hide file tree
Showing 3 changed files with 297 additions and 0 deletions.
57 changes: 57 additions & 0 deletions src/deckgl-layers/3d-building-layer/3d-building-layer.ts
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 src/deckgl-layers/3d-building-layer/3d-building-utils.ts
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;
}
29 changes: 29 additions & 0 deletions src/deckgl-layers/3d-building-layer/types.ts
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;
};

0 comments on commit 6b5b051

Please sign in to comment.