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

Clientside data optimization #269

Draft
wants to merge 14 commits into
base: main
Choose a base branch
from
Draft
Next Next commit
wip RTREE
  • Loading branch information
nofurtherinformation committed Jan 31, 2025
commit aed270e18a0bc3131b3f507bf205c3a7921df9c4
31 changes: 27 additions & 4 deletions app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@turf/helpers": "^7.1.0",
"@turf/point-on-feature": "^7.1.0",
"@turf/points-within-polygon": "^7.1.0",
"@types/rbush": "^4.0.0",
"@visx/axis": "^3.12.0",
"@visx/brush": "^3.12.0",
"@visx/gradient": "^3.12.0",
Expand All @@ -45,6 +46,7 @@
"maplibre-gl": "^4.4.1",
"next": "^14.2.7",
"pmtiles": "^3.0.7",
"rbush": "^4.0.1",
"re-resizable": "^6.10.1",
"react": "^18",
"react-dom": "^18",
Expand Down
4 changes: 4 additions & 0 deletions app/src/app/components/Topbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {useTemporalStore} from '../store/temporalStore';
import {document} from '../utils/api/mutations';
import {DistrictrMap} from '../utils/api/apiHandlers';
import {defaultPanels} from '@components/sidebar/DataPanelUtils';
import { toggleUseRTree } from '../utils/helpers';

export const Topbar: React.FC = () => {
const handleReset = useMapStore(state => state.handleReset);
Expand Down Expand Up @@ -100,6 +101,9 @@ export const Topbar: React.FC = () => {
</DropdownMenu.Sub>
</DropdownMenu.Content>
</DropdownMenu.Root>
<Button variant="solid" color="cyan" onClick={toggleUseRTree}>
Toggle R-Tree
</Button>
<Flex direction="row" align="center" gapX="2">
<Button variant="outline" className="mr-2" disabled>
Share
Expand Down
11 changes: 11 additions & 0 deletions app/src/app/constants/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {MapStore, useMapStore} from '../store/mapStore';
import {colorScheme} from './colors';
import {throttle} from 'lodash';
import GeometryWorker from '../utils/GeometryWorker';
import { featureCache } from '../utils/featureCache';


export const BLOCK_SOURCE_ID = 'blocks';
Expand Down Expand Up @@ -280,8 +281,18 @@ const addBlockLayers = (map: Map | null, mapDocument: DocumentObject) => {
console.log('map or mapDocument not ready', mapDocument);
return;
}

const blockSource = getBlocksSource(mapDocument.tiles_s3_path);
removeBlockLayers(map);
fetch(`${process.env.NEXT_PUBLIC_S3_BUCKET_URL}/tilesets/co_rtree_test.json`)
.then((response) => response.json())
.then((data) => {
featureCache.addFeatures(
data,
BLOCK_SOURCE_ID,
mapDocument.parent_layer
);
});
map?.addSource(BLOCK_SOURCE_ID, blockSource);
map?.addLayer(
getBlocksLayerSpecification(mapDocument.parent_layer, BLOCK_LAYER_ID),
Expand Down
90 changes: 90 additions & 0 deletions app/src/app/utils/featureCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
"use client"
import RBush from 'rbush';

type Data = {
path: string;
[key: string]: unknown;
}

type RBushBbox = {
minX: number;
minY: number;
maxX: number;
maxY: number;
}

class FeatureCache {
private tree = new RBush();

features: Record<string, {
properties: Data;
id: string;
source: string;
sourceLayer: string;
}> = {};

addFeatures(
features: Record<string,{
props: Data;
bounds: [number, number, number, number][];
}>,
source: string,
sourceLayer: string
) {
// const t0 = performance.now();
const formattedData = Object.values(features).map(({props, bounds}) => {
this.features[props.path] = {
properties: props,
id: props.path,
source,
sourceLayer,
};
return bounds.map((bbox) => {
return {
minX: bbox[0],
minY: bbox[1],
maxX: bbox[2],
maxY: bbox[3],
path: props.path,
}
})
}).flat()
this.tree.load(formattedData)
// const t1 = performance.now();
// console.log(`FeatureCache.addFeatures took ${t1 - t0} milliseconds.`)
}

searchRTree(bbox: RBushBbox) {
// const t0 = performance.now();
const results = this.tree.search(bbox);
// const t1 = performance.now();
// console.log(`FeatureCache.getFeaturesInBBox took ${t1 - t0} milliseconds.`)
// @ts-ignore
return results
}

searchFeaturesinBbox(bbox: RBushBbox) {
const t0 = performance.now();
const entries = this.searchRTree(bbox)
const results = []
const foundIds = new Set();
for (const entry of entries) {
// @ts-ignore
const id = entry.path;
if (!foundIds.has(id)) {
foundIds.add(id);
results.push(this.features[id]);
}
}
const t1 = performance.now();
// console.log(`FeatureCache.getFeaturesInBBox took ${t1 - t0} milliseconds.`)
return results
}

clear(){
this.tree.clear();
this.features = {};
}
}

export const featureCache = new FeatureCache();
53 changes: 43 additions & 10 deletions app/src/app/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import {MapStore, useMapStore} from '../store/mapStore';
import {NullableZone} from '../constants/types';
import {idCache} from '../store/idCache';
import { featureCache } from './featureCache';

/**
* PaintEventHandler
Expand Down Expand Up @@ -72,6 +73,18 @@ export const boxAroundPoint = (
];
};

let USE_RTREE = true
let searchTimesPer1kFeatures: number[] = []

setInterval(() => {
console.log('Time in MS to query 1000 features:', searchTimesPer1kFeatures.reduce((a,b)=>a+b,0)/searchTimesPer1kFeatures.length)
}, 1000);

export const toggleUseRTree = () => {
USE_RTREE = !USE_RTREE
searchTimesPer1kFeatures = []
alert(USE_RTREE ? "Using RTree" : "Not using RTree")
}
/**
* getFeaturesInBbox
* Get the features in a bounding box on the map.
Expand All @@ -87,18 +100,38 @@ export const getFeaturesInBbox = (
_layers: string[] = [BLOCK_HOVER_LAYER_ID],
filterLocked: boolean = true
): MapGeoJSONFeature[] | undefined => {
const bbox = boxAroundPoint(e, brushSize);
const {captiveIds} = useMapStore.getState();

const layers = _layers?.length
if (USE_RTREE) {
if (!map) return [];
const bbox = boxAroundPoint(e, brushSize);
// bbox to latlon
const [topLeft, bottomRight] = bbox
const [topLeftLatLon, bottomRightLatLon] = [map?.unproject(topLeft),map?.unproject(bottomRight)]
const bboxLatLon = {
minX: topLeftLatLon.lng,
maxY: topLeftLatLon.lat,
maxX: bottomRightLatLon.lng,
minY: bottomRightLatLon.lat
}
const t0 = performance.now();
const features = featureCache.searchFeaturesinBbox(bboxLatLon)
const t1 = performance.now();
searchTimesPer1kFeatures.push((t1 - t0)/features.length*1000)
return filterFeatures(features as any, filterLocked);
} else {
const bbox = boxAroundPoint(e, brushSize);
const {captiveIds} = useMapStore.getState();

const layers = _layers?.length
? _layers
: captiveIds.size
? [BLOCK_HOVER_LAYER_ID, BLOCK_HOVER_LAYER_ID_CHILD]
: [BLOCK_HOVER_LAYER_ID];

let features = map?.queryRenderedFeatures(bbox, {layers}) || [];

return filterFeatures(features, filterLocked);
? [BLOCK_HOVER_LAYER_ID, BLOCK_HOVER_LAYER_ID_CHILD]
: [BLOCK_HOVER_LAYER_ID];
const t0 = performance.now();
let features = map?.queryRenderedFeatures(bbox, {layers}) || [];
const t1 = performance.now();
searchTimesPer1kFeatures.push((t1 - t0)/features.length*1000)
return filterFeatures(features, filterLocked);
}
};

/**
Expand Down
Loading