Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Update Tile3DLayer to support colorizing by attributes on the fly #298

Merged
merged 6 commits into from
Nov 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,12 @@
"@fortawesome/free-solid-svg-icons": "^5.15.4",
"@fortawesome/react-fontawesome": "^0.1.17",
"@hyperjump/json-schema": "^0.23.2",
"@loaders.gl/3d-tiles": "^4.0.0-beta.7",
"@loaders.gl/core": "^4.0.0-beta.7",
"@loaders.gl/i3s": "^4.0.0-beta.7",
"@loaders.gl/tiles": "^4.0.0-beta.7",
"@loaders.gl/3d-tiles": "^4.0.0-beta.8",
"@loaders.gl/core": "^4.0.0-beta.8",
"@loaders.gl/i3s": "^4.0.0-beta.8",
"@loaders.gl/tiles": "^4.0.0-beta.8",
"@luma.gl/core": "^8.5.14",
"@math.gl/core": "^3.6.0",
"@math.gl/proj4": "^3.6.3",
"@probe.gl/stats": "^4.0.4",
"@reduxjs/toolkit": "^1.9.5",
Expand Down Expand Up @@ -86,7 +87,7 @@
"webpack-dev-server": "^4.7.4"
},
"resolutions": {
"@loaders.gl/tiles": "^4.0.0-beta.7"
"@loaders.gl/tiles": "^4.0.0-beta.8"
},
"volta": {
"node": "18.18.2",
Expand Down
81 changes: 45 additions & 36 deletions src/components/deck-gl-wrapper/deck-gl-wrapper.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import { load } from "@loaders.gl/core";
import { LineLayer, ScatterplotLayer } from "@deck.gl/layers";
import { ImageLoader } from "@loaders.gl/images";
import { Tileset3D } from "@loaders.gl/tiles";
import { BoundingVolumeLayer } from "../../layers";
import { BoundingVolumeLayer, CustomTile3DLayer } from "../../layers";
import { COORDINATE_SYSTEM, I3SLoader } from "@loaders.gl/i3s";
import ColorMap from "../../utils/debug/colors-map";
import {
Expand All @@ -61,7 +61,7 @@ import { setupStore } from "../../redux/store";
import { setColorsByAttrubute } from "../../redux/slices/colors-by-attribute-slice";
import { setDragMode } from "../../redux/slices/drag-mode-slice";
import { setDebugOptions } from "../../redux/slices/debug-options-slice";
import { addBaseMap } from "../../redux/slices/base-maps-slice"
import { addBaseMap } from "../../redux/slices/base-maps-slice";

const simpleCallbackMock = jest.fn().mockImplementation(() => {
/* Do Nothing */
Expand Down Expand Up @@ -263,17 +263,17 @@ describe("Deck.gl I3S map component", () => {
describe("Render Tile3DLayer", () => {
it("Should render Tile3DLayer", () => {
callRender(renderWithProvider);
expect(Tile3DLayer).toHaveBeenCalled();
expect(CustomTile3DLayer).toHaveBeenCalled();
const {
id,
data,
loader,
loadOptions,
autoHighlight,
highlightedObjectIndex,
} = Tile3DLayer.mock.lastCall[0];
} = (CustomTile3DLayer as any).mock.lastCall[0];
expect(id).toBe(
"tile-layer-undefined-draco-true-compressed-textures-true--colors-by-attribute-undefined--colors-by-attribute-mode-undefined--0"
"tile-layer-undefined-draco-true-compressed-textures-true--0"
);
expect(data).toBe(tilesetUrl);
expect(loader).toBe(I3SLoader);
Expand All @@ -282,7 +282,6 @@ describe("Deck.gl I3S map component", () => {
coordinateSystem: COORDINATE_SYSTEM.LNGLAT_OFFSETS,
useCompressedTextures: true,
useDracoGeometry: true,
colorsByAttribute: null,
},
});
expect(autoHighlight).toBe(false);
Expand Down Expand Up @@ -328,16 +327,21 @@ describe("Deck.gl I3S map component", () => {

it("Should update layer", () => {
callRender(renderWithProvider, { loadNumber: 1 });
const { id } = Tile3DLayer.mock.lastCall[0];
const { id } = (CustomTile3DLayer as any).mock.lastCall[0];
expect(id).toBe(
"tile-layer-undefined-draco-true-compressed-textures-true--colors-by-attribute-undefined--colors-by-attribute-mode-undefined--1"
"tile-layer-undefined-draco-true-compressed-textures-true--1"
);
});

it("Should render pickable with auto highlighting", () => {
const store = setupStore();
callRender(renderWithProvider, { pickable: true, autoHighlight: true }, store);
const { pickable, autoHighlight } = Tile3DLayer.mock.lastCall[0];
callRender(
renderWithProvider,
{ pickable: true, autoHighlight: true },
store
);
const { pickable, autoHighlight } = (CustomTile3DLayer as any).mock
.lastCall[0];
expect(pickable).toBe(true);
expect(autoHighlight).toBe(true);
});
Expand All @@ -346,7 +350,8 @@ describe("Deck.gl I3S map component", () => {
callRender(renderWithProvider, {
selectedTilesetBasePath: "http://another.tileset.local",
});
const { highlightedObjectIndex } = Tile3DLayer.mock.lastCall[0];
const { highlightedObjectIndex } = (CustomTile3DLayer as any).mock
.lastCall[0];
expect(highlightedObjectIndex).toBe(-1);
});

Expand All @@ -358,7 +363,7 @@ describe("Deck.gl I3S map component", () => {
_subLayerProps: {
mesh: { wireframe },
},
} = Tile3DLayer.mock.lastCall[0];
} = (CustomTile3DLayer as any).mock.lastCall[0];
expect(wireframe).toBe(false);

store.dispatch(setDebugOptions({ wireframe: true }));
Expand All @@ -367,7 +372,7 @@ describe("Deck.gl I3S map component", () => {
_subLayerProps: {
mesh: { wireframe: wireframe2 },
},
} = Tile3DLayer.mock.lastCall[0];
} = (CustomTile3DLayer as any).mock.lastCall[0];
expect(wireframe2).toBe(true);
});

Expand All @@ -380,23 +385,23 @@ describe("Deck.gl I3S map component", () => {
},
],
});
const { loadOptions } = Tile3DLayer.mock.lastCall[0];
const { loadOptions } = (CustomTile3DLayer as any).mock.lastCall[0];
expect(loadOptions).toEqual({
i3s: {
coordinateSystem: COORDINATE_SYSTEM.LNGLAT_OFFSETS,
useCompressedTextures: true,
useDracoGeometry: true,
colorsByAttribute: null,
token: "<abcdefg123456>",
},
});
});

it("Should call Tile3DLayer tileset callbacks", () => {
const { rerender } = callRender(renderWithProvider);
expect(Tile3DLayer).toHaveBeenCalled();
const { onTileLoad, onTilesetLoad, onTileUnload } =
Tile3DLayer.mock.lastCall[0];
expect(CustomTile3DLayer).toHaveBeenCalled();
const { onTileLoad, onTilesetLoad, onTileUnload } = (
CustomTile3DLayer as any
).mock.lastCall[0];
const tile3d = getTile3d();
act(() => onTileLoad(tile3d));
expect(simpleCallbackMock).toHaveBeenCalledTimes(1);
Expand All @@ -411,12 +416,14 @@ describe("Deck.gl I3S map component", () => {
expect(simpleCallbackMock).toHaveBeenCalledTimes(2);

callRender(rerender, { onTileLoad: undefined });
const { onTileLoad: onTileLoad2 } = Tile3DLayer.mock.lastCall[0];
const { onTileLoad: onTileLoad2 } = (CustomTile3DLayer as any).mock
.lastCall[0];
act(() => onTileLoad2(tile3d));
expect(simpleCallbackMock).toHaveBeenCalledTimes(2);

callRender(rerender, { onTileUnload: undefined });
const { onTileUnload: onTileUnload2 } = Tile3DLayer.mock.lastCall[0];
const { onTileUnload: onTileUnload2 } = (CustomTile3DLayer as any).mock
.lastCall[0];
expect(() => act(() => onTileUnload2(tile3d))).not.toThrow();
expect(simpleCallbackMock).toHaveBeenCalledTimes(2);
});
Expand All @@ -431,8 +438,8 @@ describe("Deck.gl I3S map component", () => {
coloredTilesMap: { "selected-tile-id": [33, 55, 66] },
store,
});
expect(Tile3DLayer).toHaveBeenCalled();
const { _getMeshColor } = Tile3DLayer.mock.lastCall[0];
expect(CustomTile3DLayer).toHaveBeenCalled();
const { _getMeshColor } = (CustomTile3DLayer as any).mock.lastCall[0];
_getMeshColor();
expect(getColorMock).toHaveBeenCalledWith(undefined, {
coloredBy: "Original",
Expand All @@ -443,8 +450,8 @@ describe("Deck.gl I3S map component", () => {

it("Should remove featureIds", () => {
callRender(renderWithProvider, { featurePicking: false });
expect(Tile3DLayer).toHaveBeenCalled();
const { onTileLoad } = Tile3DLayer.mock.lastCall[0];
expect(CustomTile3DLayer).toHaveBeenCalled();
const { onTileLoad } = (CustomTile3DLayer as any).mock.lastCall[0];
const tile3d = getTile3d();
tile3d.content.featureIds = new Uint32Array([10, 20, 30]);
act(() => onTileLoad(tile3d));
Expand All @@ -455,16 +462,17 @@ describe("Deck.gl I3S map component", () => {
const store = setupStore();
store.dispatch(setDebugOptions({ showUVDebugTexture: false }));
const { rerender } = callRender(renderWithProvider, undefined, store);
expect(Tile3DLayer).toHaveBeenCalled();
const { onTileLoad } = Tile3DLayer.mock.lastCall[0];
expect(CustomTile3DLayer).toHaveBeenCalled();
const { onTileLoad } = (CustomTile3DLayer as any).mock.lastCall[0];
const tile3d = getTile3d();
act(() => onTileLoad(tile3d));
expect(selectOriginalTextureForTile).toHaveBeenCalledWith(tile3d);
expect(selectDebugTextureForTile).not.toHaveBeenCalled();

store.dispatch(setDebugOptions({ showUVDebugTexture: true }));
callRender(rerender, undefined, store);
const { onTileLoad: onTileLoadSecond } = Tile3DLayer.mock.lastCall[0];
const { onTileLoad: onTileLoadSecond } = (CustomTile3DLayer as any).mock
.lastCall[0];
act(() => onTileLoadSecond(tile3d));
expect(selectDebugTextureForTile).toHaveBeenCalledWith(tile3d, null);
expect(selectOriginalTextureForTile).toHaveBeenCalledTimes(1);
Expand All @@ -473,8 +481,8 @@ describe("Deck.gl I3S map component", () => {
it("Should not be pickable", () => {
const store = setupStore();
callRender(renderWithProvider, { pickable: false }, store);
expect(Tile3DLayer).toHaveBeenCalled();
const { pickable } = Tile3DLayer.mock.lastCall[0];
expect(CustomTile3DLayer).toHaveBeenCalled();
const { pickable } = (CustomTile3DLayer as any).mock.lastCall[0];
expect(pickable).toBe(false);
});

Expand All @@ -491,12 +499,13 @@ describe("Deck.gl I3S map component", () => {
})
);
callRender(renderWithProvider, undefined, store);
expect(Tile3DLayer).toHaveBeenCalled();
const { id, loadOptions } = Tile3DLayer.mock.lastCall[0];
expect(CustomTile3DLayer).toHaveBeenCalled();
const { id, colorsByAttribute } = (CustomTile3DLayer as any).mock
.lastCall[0];
expect(id).toBe(
"tile-layer-undefined-draco-true-compressed-textures-true--colors-by-attribute-HEIGHTROOF--colors-by-attribute-mode-replace--0"
"tile-layer-undefined-draco-true-compressed-textures-true--0"
);
expect(loadOptions.i3s.colorsByAttribute).toEqual({
expect(colorsByAttribute).toEqual({
attributeName: "HEIGHTROOF",
maxColor: [44, 44, 175, 255],
maxValue: 1400,
Expand All @@ -509,8 +518,7 @@ describe("Deck.gl I3S map component", () => {

describe("Render TerrainLayer", () => {
const store = setupStore();
store.dispatch(
addBaseMap({ id: "Terrain", mapUrl: "", name: "Terrain" }));
store.dispatch(addBaseMap({ id: "Terrain", mapUrl: "", name: "Terrain" }));
it("Should render terrain", () => {
callRender(renderWithProvider, undefined, store);
expect(TerrainLayer).toHaveBeenCalled();
Expand All @@ -519,7 +527,8 @@ describe("Deck.gl I3S map component", () => {
it("Should call onTerrainTileLoad", () => {
const store = setupStore();
store.dispatch(
addBaseMap({ id: "Terrain", mapUrl: "", name: "Terrain" }));
addBaseMap({ id: "Terrain", mapUrl: "", name: "Terrain" })
);
const { rerender } = callRender(renderWithProvider, undefined, store);
const { onTileLoad } = TerrainLayer.mock.lastCall[0];
const terrainTile = {
Expand Down
11 changes: 7 additions & 4 deletions src/components/deck-gl-wrapper/deck-gl-wrapper.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
TilesetType,
MinimapPosition,
} from "../../types";
import { BoundingVolumeLayer } from "../../layers";
import { BoundingVolumeLayer, CustomTile3DLayer } from "../../layers";
import ColorMap from "../../utils/debug/colors-map";
import {
selectDebugTextureForTile,
Expand Down Expand Up @@ -62,6 +62,7 @@ import {
selectBaseMaps,
selectSelectedBaseMapId,
} from "../../redux/slices/base-maps-slice";
import { colorizeTile } from "../../utils/colorize-tile";

const TRANSITION_DURAITON = 4000;
const INITIAL_VIEW_STATE = {
Expand Down Expand Up @@ -618,7 +619,6 @@ export const DeckGlWrapper = ({
coordinateSystem: COORDINATE_SYSTEM.LNGLAT_OFFSETS,
useDracoGeometry,
useCompressedTextures,
colorsByAttribute: colorsByAttribute,
},
};
let url = layer.url;
Expand All @@ -628,10 +628,13 @@ export const DeckGlWrapper = ({
urlObject.searchParams.append("token", layer.token);
url = urlObject.href;
}
return new Tile3DLayer({
id: `tile-layer-${layer.id}-draco-${useDracoGeometry}-compressed-textures-${useCompressedTextures}--colors-by-attribute-${colorsByAttribute?.attributeName}--colors-by-attribute-mode-${colorsByAttribute?.mode}--${loadNumber}`,
return new CustomTile3DLayer({
id: `tile-layer-${layer.id}-draco-${useDracoGeometry}-compressed-textures-${useCompressedTextures}--${loadNumber}` as string,
belom88 marked this conversation as resolved.
Show resolved Hide resolved
data: url,
// @ts-expect-error loader
loader: I3SLoader,
colorsByAttribute,
customizeColors: colorizeTile,
onTilesetLoad: onTilesetLoadHandler,
onTileLoad: onTileLoadHandler,
onTileUnload,
Expand Down
10 changes: 6 additions & 4 deletions src/components/layers-panel/layer-settings-panel.spec.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { act, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { renderWithTheme } from "../../utils/testing-utils/render-with-theme";
import { renderWithThemeProviders } from "../../utils/testing-utils/render-with-theme";
import { LayerSettingsPanel } from "./layer-settings-panel";

// Mocked components
import { CloseButton } from "../close-button/close-button";
import { BuildingExplorer } from "./building-explorer";
import { setupStore } from "../../redux/store";

jest.mock("../close-button/close-button");
jest.mock("./building-explorer");
Expand All @@ -25,7 +26,7 @@ const onBackClick = jest.fn();
const onCloseClick = jest.fn();
const onBuildingExplorerOpened = jest.fn();

const callRender = (renderFunc, props = {}) => {
const callRender = (renderFunc, props = {}, store = setupStore()) => {
return renderFunc(
<LayerSettingsPanel
sublayers={[]}
Expand All @@ -34,13 +35,14 @@ const callRender = (renderFunc, props = {}) => {
onCloseClick={onCloseClick}
onBuildingExplorerOpened={onBuildingExplorerOpened}
{...props}
/>
/>,
store
);
};

describe("Layers Settings Panel", () => {
it("Should render LayerSettingsPanel", () => {
const { container } = callRender(renderWithTheme);
const { container } = callRender(renderWithThemeProviders);
expect(container).toBeInTheDocument();

expect(screen.getByText("Layer settings"));
Expand Down
15 changes: 13 additions & 2 deletions src/components/layers-panel/layer-settings-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ReactEventHandler } from "react";
import { ReactEventHandler, useEffect } from "react";
import styled, { useTheme } from "styled-components";

import ArrowLeftIcon from "../../../public/icons/arrow-left.svg";
import { CloseButton } from "../close-button/close-button";
import { BuildingExplorer } from "./building-explorer";
import { PanelHorizontalLine } from "../common";
import { ActiveSublayer } from "../../utils/active-sublayer";
import { useAppDispatch } from "../../redux/hooks";
import { setColorsByAttrubute } from "../../redux/slices/colors-by-attribute-slice";

const Container = styled.div`
display: flex;
Expand Down Expand Up @@ -47,7 +49,7 @@ export const LayerSettingsPanel = ({
onUpdateSublayerVisibility,
onBackClick,
onCloseClick,
onBuildingExplorerOpened
onBuildingExplorerOpened,
}: {
sublayers: ActiveSublayer[];
onUpdateSublayerVisibility: (Sublayer) => void;
Expand All @@ -56,6 +58,15 @@ export const LayerSettingsPanel = ({
onBuildingExplorerOpened: (opended: boolean) => void;
}) => {
const theme = useTheme();
const dispatch = useAppDispatch();

useEffect(() => {
dispatch(setColorsByAttrubute(null));
belom88 marked this conversation as resolved.
Show resolved Hide resolved
return () => {
dispatch(setColorsByAttrubute(null));
};
}, []);

return (
<Container>
<Header>
Expand Down
Loading