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

chore: refactor geospatial example to use hooks #2793

Merged
merged 1 commit into from
Nov 19, 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
279 changes: 133 additions & 146 deletions examples/website/geospatial/app.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,35 @@
// loaders.gl, MIT license
// Copyright (c) vis.gl contributors

import React, {PureComponent} from 'react';
import React, {useState, useEffect} 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 {DeckGL} from '@deck.gl/react/typed';
import {MapController} from '@deck.gl/core/typed';
import {GeoJsonLayer} from '@deck.gl/layers/typed';

import ControlPanel from './components/control-panel';
import FileUploader from './components/file-uploader';
import {ControlPanel} from './components/control-panel';
import {FileUploader} from './components/file-uploader';

import type {Example} from './examples';
import {INITIAL_LOADER_NAME, INITIAL_EXAMPLE_NAME, INITIAL_MAP_STYLE, EXAMPLES} from './examples';

import {Table, GeoJSON} from '@loaders.gl/schema';
import {Loader, load, /* registerLoaders */} from '@loaders.gl/core';
import {Loader, load /* registerLoaders */} from '@loaders.gl/core';
import {ParquetLoader, installBufferPolyfill} from '@loaders.gl/parquet';
import {FlatGeobufLoader} from '@loaders.gl/flatgeobuf';
// import {GeoPackageLoader} from '@loaders.gl/geopackage';
import {ArrowLoader} from '@loaders.gl/arrow';

installBufferPolyfill();

const LOADERS: Loader[] = [
ParquetLoader,
FlatGeobufLoader,
ArrowLoader,
ParquetLoader,
FlatGeobufLoader
// GeoPackageLoader
];
const LOADER_OPTIONS = {
Expand All @@ -36,14 +42,11 @@ const LOADER_OPTIONS = {
shape: 'geojson-table',
preserveBinary: true
},
'geopackage': {
shape: 'geojson-table',
geopackage: {
shape: 'geojson-table'
// table: 'FEATURESriversds'
}
}

import type {Example} from './examples';
import {INITIAL_LOADER_NAME, INITIAL_EXAMPLE_NAME, INITIAL_MAP_STYLE, EXAMPLES} from './examples';
};

export const INITIAL_VIEW_STATE = {
latitude: 49.254,
Expand All @@ -55,28 +58,40 @@ export const INITIAL_VIEW_STATE = {
};

type AppProps = {
format?: string
format?: string;
};

type AppState = {
examples: Record<string, Record<string, Example>>,
examples: Record<string, Record<string, Example>>;
// CURRENT VIEW POINT / CAMERA POSITION
viewState: Record<string, number>,
viewState: Record<string, number>;

// EXAMPLE STATE
selectedExample: string,
selectedLoader: string,
loadedTable: Table | null
}
selectedExample: string;
selectedLoader: string;
loadedTable: Table | null;
};

/**
*
*
*/
export default class App extends PureComponent<AppProps, AppState> {
constructor(props) {
super(props);

let examples: Record<string, Record<string, Example>> = EXAMPLES;
export default function App(props: AppProps) {

const [state, setState] = useState<AppState>({
// EXAMPLE STATE
examples: EXAMPLES,
selectedExample: INITIAL_EXAMPLE_NAME,
selectedLoader: INITIAL_LOADER_NAME,

// CURRENT VIEW POINT / CAMERA POSITION
viewState: INITIAL_VIEW_STATE,
loadedTable: null,
error: null
});

// Initialize the examples (each demo might focus on a different "props.format")
useEffect(() => {
let examples: Record<string, Record<string, Example>> = {...EXAMPLES};
if (props.format) {
// Move the preferred format examples to the "top"
examples = {[props.format]: EXAMPLES[props.format], ...EXAMPLES};
Expand All @@ -91,127 +106,45 @@ export default class App extends PureComponent<AppProps, AppState> {
break;
}
}
setState({...state, examples, selectedExample, selectedLoader});
}, [props.format]);

this.state = {
examples,
// CURRENT VIEW POINT / CAMERA POSITIO
viewState: INITIAL_VIEW_STATE,

// EXAMPLE STATE
selectedExample,
selectedLoader,
loadedTable: null
};

this._onExampleChange = this._onExampleChange.bind(this);
this._onFileRemoved = this._onFileRemoved.bind(this);
this._onFileSelected = this._onFileSelected.bind(this);
// this._onFileUploaded = this._onFileUploaded.bind(this);
this._onViewStateChange = this._onViewStateChange.bind(this);
}

_onViewStateChange({viewState}) {
this.setState({viewState});
}

async _onExampleChange(args: {selectedLoader: string, selectedExample: string, example: Example}) {
const {selectedLoader, selectedExample, example} = args

const url = example.data;
console.log('Loading', url);
const data = await load(url, LOADERS, LOADER_OPTIONS) as Table;
console.log('Loaded data', data);

const {viewState} = example;
this.setState({selectedLoader, selectedExample, viewState, loadedTable: data});
}

_onFileRemoved() {
this.setState({loadedTable: null});
}

async _onFileSelected(uploadedFile: File) {
console.log('Loading', uploadedFile.name);
const data = await load(uploadedFile, LOADERS, LOADER_OPTIONS) as Table;
console.log('Loaded data', data);
this.setState({
selectedExample: uploadedFile.name,
loadedTable: data
});
}

// _onFileUploaded(data, uploadedFile) {
// this.setState({
// selectedExample: uploadedFile.name,
// uploadedFile: data
// });
// }

_renderControlPanel() {
const {examples, selectedExample, viewState, selectedLoader} = this.state;

return (
return (
<div style={{position: 'relative', height: '100%'}}>

<ControlPanel
examples={examples}
selectedExample={selectedExample}
selectedLoader={selectedLoader}
onExampleChange={this._onExampleChange}
examples={state.examples}
selectedExample={state.selectedExample}
selectedLoader={state.selectedLoader}
onExampleChange={(props) =>onExampleChange({...props, state, setState})}
>
{state.error ? <div style={{color: 'red'}}>{state.error}</div> : ''}
<div style={{textAlign: 'center'}}>
long/lat: {viewState.longitude.toFixed(5)},{viewState.latitude.toFixed(5)}, zoom:
{viewState.zoom.toFixed(2)}
center long/lat: {state.viewState.longitude.toFixed(3)},
{state.viewState.latitude.toFixed(3)},
zoom: {state.viewState.zoom.toFixed(2)}
</div>
{<FileUploader onFileSelected={this._onFileSelected} onFileRemoved={this._onFileRemoved} />}
<FileUploader
onFileRemoved={() => setState({...state, loadedTable: null})}
onFileSelected={async (uploadedFile: File) => {
// TODO - error handling
const data = (await load(uploadedFile, LOADERS, LOADER_OPTIONS)) as Table;
setState({
...state,
selectedExample: uploadedFile.name,
loadedTable: data
});
}}
/>
</ControlPanel>
);
}

_renderLayer() {
const {examples, selectedExample, selectedLoader, loadedTable} = this.state;

const geojson = loadedTable as GeoJSON;
console.warn('Rendering layer with', geojson);
return [
new GeoJsonLayer({
id: `geojson-${selectedExample}(${selectedLoader})`,
data: geojson,

pickable: true,
autoHighlight: true,
highlightColor: [0, 255, 0],

// Visuals
opacity: 1.0,
stroked: false,
filled: true,
extruded: true,
wireframe: true,
// getElevation: (f) => Math.sqrt(f?.properties?.valuePerSqm) * 10,
// lines
getLineColor: [0, 0, 255],
getLineWidth: 3,
lineWidthUnits: 'pixels',
// point fills
getFillColor: [255, 0, 0],
getPointRadius: 100,
pointRadiusScale: 500,
// pointRadiusUnits: 'pixels',
})
];
}

render() {
const {viewState} = this.state;

return (
<div style={{position: 'relative', height: '100%'}}>
{this._renderControlPanel()}
<DeckGL
layers={this._renderLayer()}
viewState={viewState}
onViewStateChange={this._onViewStateChange}
controller={{type: MapController, maxPitch: 85}}
getTooltip={({object}) => object && {
<DeckGL
layers={renderLayer(state)}
viewState={state.viewState}
onViewStateChange={({viewState}) => setState({...state, viewState})}
controller={{type: MapController, maxPitch: 85}}
getTooltip={({object}) =>
object && {
html: `\
<h2>${object.properties?.name}</h2>
<div>${object.geometry?.coordinates?.[0]}</div>
Expand All @@ -220,16 +153,70 @@ export default class App extends PureComponent<AppProps, AppState> {
backgroundColor: '#ddd',
fontSize: '0.8em'
}
}}
>
<Map reuseMaps mapLib={maplibregl} mapStyle={INITIAL_MAP_STYLE} preventStyleDiffing />
}
}
>
<Map reuseMaps mapLib={maplibregl} mapStyle={INITIAL_MAP_STYLE} preventStyleDiffing />
</DeckGL>
</div>
);
}

</DeckGL>
</div>
);
async function onExampleChange(args: {
selectedLoader: string;
selectedExample: string;
example: Example;
state: AppState;
setState: Function;
}) {
const {selectedLoader, selectedExample, example, state, setState} = args;

const url = example.data;
console.log('Loading', url);
try {
const data = (await load(url, LOADERS, LOADER_OPTIONS)) as Table;
console.log('Loaded data', data);
const viewState = {...state.viewState, ...example.viewState};
setState({...state, selectedLoader, selectedExample, viewState, loadedTable: data});
} catch (error) {
console.log('Failed to load data', url, error);
setState({...state, error: `Could not load ${selectedExample}: ${error.message}`});
}
}

function renderLayer({selectedExample, selectedLoader, loadedTable}) {

const geojson = loadedTable as GeoJSON;
console.warn('Rendering layer with', geojson);
return [
new GeoJsonLayer({
id: `geojson-${selectedExample}(${selectedLoader})`,
data: geojson,

pickable: true,
autoHighlight: true,
highlightColor: [0, 255, 0],

// Visuals
opacity: 1.0,
stroked: false,
filled: true,
extruded: true,
wireframe: true,
// getElevation: (f) => Math.sqrt(f?.properties?.valuePerSqm) * 10,
// lines
getLineColor: [0, 0, 255],
getLineWidth: 3,
lineWidthUnits: 'pixels',
// point fills
getFillColor: [255, 0, 0],
getPointRadius: 100,
pointRadiusScale: 500
// pointRadiusUnits: 'pixels',
})
];
}

export function renderToDOM(container) {
createRoot(container).render(<App />);
}
2 changes: 1 addition & 1 deletion examples/website/geospatial/components/control-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ type PropTypes = React.PropsWithChildren<{
}) => void;
}>;

export default class ControlPanel extends PureComponent<PropTypes> {
export class ControlPanel extends PureComponent<PropTypes> {
static defaultProps: PropTypes = {
examples: {},
droppedFile: null,
Expand Down
4 changes: 2 additions & 2 deletions examples/website/geospatial/components/file-uploader.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, {PureComponent} from 'react';
import styled from 'styled-components';
import ParsedFile from './parsed-file';
import {ParsedFile} from './parsed-file';

const Container = styled.div`
display: flex;
Expand Down Expand Up @@ -32,7 +32,7 @@ type FileUploaderState = {
uploadedFile: File | null;
}

export default class FileUploader extends PureComponent<FileUploaderProps, FileUploaderState> {
export class FileUploader extends PureComponent<FileUploaderProps, FileUploaderState> {
constructor(props) {
super(props);

Expand Down
2 changes: 1 addition & 1 deletion examples/website/geospatial/components/parsed-file.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ type FileLoaderState = {
error: string | null;
};

export default class FileLoader extends PureComponent<FileLoaderPropTypes, FileLoaderState> {
export class ParsedFile extends PureComponent<FileLoaderPropTypes, FileLoaderState> {
static defaultProps = {
file: null
};
Expand Down
Loading
Loading