Skip to content

Commit

Permalink
chore(i3s examples): add i3s colorization by attributes (#2887)
Browse files Browse the repository at this point in the history
  • Loading branch information
maxkuznetsov-actionengine authored Feb 28, 2024
1 parent 6c52dee commit 3930317
Show file tree
Hide file tree
Showing 16 changed files with 526 additions and 1 deletion.
12 changes: 12 additions & 0 deletions examples/website/i3s-colorization-by-attributes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
This is a standalone web app using `@loaders.gl/i3s`.

### Usage

Copy the content of this folder to your project.

```bash
# install dependencies
yarn
# bundle and serve the app with webpack
yarn start
```
35 changes: 35 additions & 0 deletions examples/website/i3s-colorization-by-attributes/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, height=device-height, initial-scale=1" />
<title>I3S Coloziration by Attributes Example</title>
<style>
html {
height: 100%;
}

body {
font-family: 'Uber Move', Helvetica, Arial, sans-serif;
font-size: 13px;
-webkit-font-smoothing: antialiased;
margin: 0;
padding: 0;
overflow: hidden;
height: 100%;
}

#app {
height: 100%;
}
</style>
</head>

<body>
<div id="app"></div>
</body>
<script type="module">
import {renderToDOM} from './src/app.tsx';
renderToDOM(document.getElementById('app'));
</script>
</html>
32 changes: 32 additions & 0 deletions examples/website/i3s-colorization-by-attributes/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"private": true,
"name": "i3s-picking-loaders-example",
"description": "I3S picking",
"version": "0.0.1",
"license": "MIT",
"type": "module",
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"serve": "vite preview"
},
"dependencies": {
"@deck.gl/core": "^8.9.28",
"@deck.gl-community/layers": "^0.0.0",
"@loaders.gl/core": "^4.0.0",
"@loaders.gl/i3s": "^4.0.0",
"@luma.gl/core": "^8.5.21",
"mapbox-gl": "npm:empty-npm-package@^1.0.0",
"maplibre-gl": "^3.6.2",
"prop-types": "^15.7.2",
"react": "^18.2.0",
"react-color": "^2.19.3",
"react-dom": "^18.2.0",
"react-map-gl": "^7.1.7",
"styled-components": "^4.2.0"
},
"devDependencies": {
"typescript": "^5.3.0",
"vite": "^5.0.0"
}
}
139 changes: 139 additions & 0 deletions examples/website/i3s-colorization-by-attributes/src/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import React, {useEffect, useState} from 'react';
import {createRoot} from 'react-dom/client';

import {Map} from 'react-map-gl';
import maplibregl from 'maplibre-gl';

import DeckGL from '@deck.gl/react';
import {ViewState, FlyToInterpolator} from '@deck.gl/core';

import {DataDrivenTile3DLayer, colorizeTile} from '@deck.gl-community/layers';

import {COORDINATE_SYSTEM, I3SLoader} from '@loaders.gl/i3s';
import {Tileset3D} from '@loaders.gl/tiles';
import {ControlPanel} from './components/control-panel';
import {AttributeData, ColorsByAttribute} from './types';
import {ColorizationPanel} from './components/colorization-panel';
import {
COLORIZE_MODES,
COLORS_BY_ATTRIBUTE,
EXAMPLES,
INITIAL_VIEW_STATE,
MAP_CONTROLLER,
TRANSITION_DURAITON
} from './constants';
import {getNumericAttributeInfo} from './utils/fetch-attributes-data';

export default function App() {
const tileSets: string[] = Object.keys(EXAMPLES);
const [tilesetSelected, setTilesetSelected] = useState<string>(tileSets[0]);
const [viewState, setViewState] = useState<ViewState>(INITIAL_VIEW_STATE);
const [attributesObject, setAttributesObject] = useState<Record<string, AttributeData>>({});
const [colorizeParams, setColorizeParams] = useState<{mode: string; attributeName: string}>({
mode: COLORIZE_MODES[0],
attributeName: ''
});
const [colorsByAttribute, setColorsByAttribute] = useState<ColorsByAttribute | null>(null);

function onSelectTilesetHandler(item: string) {
setTilesetSelected(item);
}

useEffect(() => {
if (colorizeParams.mode !== 'none' && colorizeParams.attributeName !== '') {
setColorsByAttribute({
attributeName: colorizeParams.attributeName,
minValue: attributesObject[colorizeParams.attributeName]?.min || 0,
maxValue: attributesObject[colorizeParams.attributeName]?.max || 0,
minColor: COLORS_BY_ATTRIBUTE.min.rgba,
maxColor: COLORS_BY_ATTRIBUTE.max.rgba,
mode: colorizeParams.mode
});
} else {
setColorsByAttribute(null);
}
}, [colorizeParams]);

function getAttributes(tileset: Tileset3D) {
const promises: Promise<AttributeData | null>[] = [];
for (const attribute of tileset.tileset?.statisticsInfo) {
promises.push(
getNumericAttributeInfo(tileset.tileset?.basePath, attribute.href, attribute.name)
);
}
Promise.allSettled(promises).then((results) => {
const attributes: Record<string, AttributeData> = {};
for (const result of results) {
if (result.status === 'fulfilled' && result.value && result.value.name) {
attributes[result.value.name] = {min: result.value.min, max: result.value.max};
}
}
if (Object.keys(attributes).length > 0) {
setAttributesObject(attributes);
setColorizeParams({...colorizeParams, attributeName: Object.keys(attributes)[0]});
}
});
}

function onTilesetLoadHandler(tileset: Tileset3D) {
const {zoom, cartographicCenter} = tileset;
const [longitude, latitude] = cartographicCenter || [];
const newViewState = {
...INITIAL_VIEW_STATE,
zoom: zoom + 2,
longitude,
latitude,
transitionDuration: TRANSITION_DURAITON,
transitionInterpolator: new FlyToInterpolator()
};
setViewState(newViewState);
getAttributes(tileset);
}

function onColorizeModeSelectHandler(mode: string) {
setColorizeParams({...colorizeParams, mode});
}

function onAttributeSelectHandler(attributeName: string) {
setColorizeParams({...colorizeParams, attributeName});
}

function renderLayers() {
const loadOptions = {i3s: {coordinateSystem: COORDINATE_SYSTEM.LNGLAT_OFFSETS}};
const layers = new DataDrivenTile3DLayer({
data: EXAMPLES[tilesetSelected].url,
loader: I3SLoader,
onTilesetLoad: onTilesetLoadHandler,
loadOptions,
colorsByAttribute,
customizeColors: colorizeTile
});

return layers;
}

return (
<div style={{position: 'relative', height: '100%'}}>
<DeckGL initialViewState={viewState} layers={renderLayers()} controller={MAP_CONTROLLER}>
<Map
reuseMaps
mapLib={maplibregl}
mapStyle={'https://basemaps.cartocdn.com/gl/dark-matter-nolabels-gl-style/style.json'}
preventStyleDiffing
preserveDrawingBuffer
/>
</DeckGL>
<ControlPanel tileSets={tileSets} onSelectTileset={onSelectTilesetHandler} />
<ColorizationPanel
colorizeModes={COLORIZE_MODES}
colorizeAttributes={attributesObject}
onSelectColorizeMode={onColorizeModeSelectHandler}
onSelectAttribute={onAttributeSelectHandler}
/>
</div>
);
}

export function renderToDOM(container) {
createRoot(container).render(<App />);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import React from 'react';
import styled from 'styled-components';
import {COLOR, FLEX, FONT} from './styles';
import {DropDown} from './drop-down';

const Container = styled.div`
${FLEX}
${COLOR}
top: 70px;
gap 10px;
padding: 16px;
margin: 10px;
line-height: 28px;
border-radius: 8px;
`;

const RowWrapper = styled.div`
display: flex;
direction: row;
justify-content: space-between;
align-items: center;
width: 100%;
`;

const Label = styled.div`
${FONT}
margin-right: 10px;
`;

type ColorizationPanelProps = {
colorizeModes: string[];
colorizeAttributes: Record<string, {min: number; max: number}>;
onSelectColorizeMode: (item: string) => void;
onSelectAttribute: (item: string) => void;
};

export function ColorizationPanel({
colorizeModes,
colorizeAttributes,
onSelectColorizeMode,
onSelectAttribute
}: ColorizationPanelProps) {
return (
<Container>
<RowWrapper>
<Label>Colorize Mode</Label>
<DropDown items={colorizeModes} onSelect={onSelectColorizeMode} />
</RowWrapper>
<RowWrapper>
<Label>Colorize Attribute</Label>
<DropDown items={Object.keys(colorizeAttributes)} onSelect={onSelectAttribute} />
</RowWrapper>
</Container>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React from 'react';
import styled from 'styled-components';
import {COLOR, FLEX} from './styles';
import {DropDown} from './drop-down';

const Container = styled.div`
${FLEX}
${COLOR}
gap: 10px;
padding: 16px;
margin: 10px;
line-height: 28px;
border-radius: 8px;
`;

type ControlPanelProps = {
tileSets: string[];
onSelectTileset: (item: string) => void;
};

export function ControlPanel({tileSets, onSelectTileset}: ControlPanelProps) {
return (
<Container>
<DropDown items={tileSets} onSelect={onSelectTileset} />
</Container>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React from 'react';
import styled from 'styled-components';
import {COLOR, FONT} from './styles';

const DropDownStyle = `
position: static;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding: 4px 16px;
cursor: pointer;
border-radius: 4px;
box-sizing: border-box;
option {
color: white;
background: #0E111A;
display: flex;
white-space: pre;
}
&:hover {
background: #4F52CC;
color: black;
}
`;

const StyledDropDown = styled.select`
${COLOR}
${FONT}
${DropDownStyle}
width: 170px;
`;

type DropDownProps = {
items: (string | number)[];
onSelect: (item: string) => void;
};

export const DropDown = ({items, onSelect}: DropDownProps) => {
return (
<StyledDropDown onChange={(evt) => onSelect(evt.target.value)}>
{items.map((item) => (
<option key={item}>{item}</option>
))}
</StyledDropDown>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
export const FONT = `
font-family: sans-serif;
font-size: 16px;
font-style: normal;
font-weight: 500;
line-height: 19px;
letter-spacing: 0em;
text-align: left;
`;

export const FLEX = `
display: flex;
flex-direction: column;
align-items: flex-start;
position: absolute;
`;

export const COLOR = `
background: #0E111A;
color: white;
`;
Loading

0 comments on commit 3930317

Please sign in to comment.