Skip to content

Commit

Permalink
fix: update types of spiderify
Browse files Browse the repository at this point in the history
  • Loading branch information
janthijs committed Jun 21, 2024
1 parent b406e31 commit 2a49fbb
Show file tree
Hide file tree
Showing 6 changed files with 68 additions and 49 deletions.
7 changes: 4 additions & 3 deletions src/pages/MarkerClusterSpiderfy/MarkerClusterSpiderfy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ import createClusterIcon from './utils/createClusterIcon';
import getMapData from './getMapData';
import processFeatures from './processFeatures';
import { CLUSTER_OPTIONS, CLUSTER_STYLES, lineStyles } from './mapStyles';
import type { ClusterOptions, MapSuperClusterFeature } from './types';
import { GeoJsonObject } from 'geojson';
import type { ClusterOptions, DataRecord } from './types';
import type { GeoJsonObject } from 'geojson';
import rawData from './data.json';

interface MarkerClusterSpiderProps {
clusterOptions?: ClusterOptions;
Expand Down Expand Up @@ -125,7 +126,7 @@ const MarkerClusterSpider: FunctionComponent<MarkerClusterSpiderProps> = ({

return processFeatures(
mapInstance,
getMapData(mapInstance) as MapSuperClusterFeature[],
getMapData(mapInstance, rawData as DataRecord[]),
clusterOptions
);
}, [mapInstance, zoom, center]);
Expand Down
13 changes: 8 additions & 5 deletions src/pages/MarkerClusterSpiderfy/getMapData.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { describe, expect, it } from 'vitest';
import getMapData, {
filterPointFeaturesWithinBoundingBox,
isCoordWithingBoundingBox,
isCoordWithinBoundingBox,
} from './getMapData';
import L from 'leaflet';
import rawData from './data.json';
import { DataRecord } from './types';

const fakeClusterData = [
{
Expand Down Expand Up @@ -128,14 +130,15 @@ describe('getMapData', () => {
new L.Map(document.createElement('div'), {
center: [52.370216, 4.895168],
zoom: 7,
})
}),
rawData as DataRecord[]
);
expect(mapData).toEqual(fakeClusterData);
});

it('isCoordWithingBoundingBox checks coordinates truly inside bounding box', () => {
expect(isCoordWithingBoundingBox([10, 10, 20, 20], [15, 15])).toBe(true);
expect(isCoordWithingBoundingBox([10, 10, 20, 20], [5, 33])).toBe(false);
it('isCoordWithinBoundingBox checks coordinates truly inside bounding box', () => {
expect(isCoordWithinBoundingBox([10, 10, 20, 20], [15, 15])).toBe(true);
expect(isCoordWithinBoundingBox([10, 10, 20, 20], [5, 33])).toBe(false);
});

it('filterPointFeaturesWithinBoundingBox checks features are truly inside bounding box', () => {
Expand Down
82 changes: 48 additions & 34 deletions src/pages/MarkerClusterSpiderfy/getMapData.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
import type { LatLngTuple } from 'leaflet';
import Supercluster, { AnyProps, PointFeature } from 'supercluster';
import { Feature, Geometry } from 'geojson';
import Supercluster, { PointFeature } from 'supercluster';
import { BBox, Feature, GeoJsonProperties, Geometry, Point } from 'geojson';
import { toGeoJSON } from '@/utils/toGeoJSON';
import toBoundsLiteral from '@/utils/toBoundsLiteral';
import { DataRecord } from './types';
import rawData from './data.json';

// Simple util to check coords within a bounding box
export const isCoordWithingBoundingBox = (
bbox: [number, number, number, number],
export const isCoordWithinBoundingBox = (
bbox: BBox,
coord: LatLngTuple,
xIndex = 1,
yIndex = 0
Expand All @@ -25,19 +22,17 @@ export const isCoordWithingBoundingBox = (
};

// Restrict data to that within specified bounding box
export const filterPointFeaturesWithinBoundingBox = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
features: PointFeature<any>[],
bbox: [number, number, number, number]
export const filterPointFeaturesWithinBoundingBox = <P>(
features: PointFeature<P>[],
bbox: BBox
) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const featuresFiltered: PointFeature<any>[] = [];
const featuresFiltered: PointFeature<P>[] = [];

features.forEach(feature => {
if (
isCoordWithingBoundingBox(
isCoordWithinBoundingBox(
bbox,
feature.geometry.coordinates as LatLngTuple,
feature.geometry.coordinates as [number, number],
0,
1
)
Expand All @@ -50,14 +45,17 @@ export const filterPointFeaturesWithinBoundingBox = (
};

// Helpful for cluster events to detect which cluster to follow
const addExpansionZoom = (
const addExpansionZoom = <D extends Supercluster.ClusterProperties>(
superClusterIndex: Supercluster,
// eslint-disable-next-line @typescript-eslint/no-explicit-any
feature: Feature<Geometry, any>
feature: Feature<Geometry, D>
) => {
try {
feature.properties.expansion_zoom =
superClusterIndex.getClusterExpansionZoom(feature.properties.cluster_id);
feature.properties = {
...feature.properties,
expansion_zoom: superClusterIndex.getClusterExpansionZoom(
feature.properties.cluster_id
),
};
} catch (error) {
console.error(
"Can't add expansion zoom to cluster",
Expand All @@ -67,23 +65,30 @@ const addExpansionZoom = (
}
};

const getMapData = (map: L.Map): PointFeature<AnyProps>[] => {
const bounds = toBoundsLiteral(map.getBounds()).flat() as [
number,
number,
number,
number,
const getMapData = <D extends { geometry: Point }>(
map: L.Map,
rawData: D[]
) => {
const bounds = map.getBounds();
const bbox: BBox = [
bounds.getWest(),
bounds.getSouth(),
bounds.getEast(),
bounds.getNorth(),
];

// Save some resources by handling only data present in the current map view
const featuresWithinBbox = filterPointFeaturesWithinBoundingBox(
toGeoJSON(rawData as DataRecord[]).features,
bounds
toGeoJSON(rawData).features,
bbox
);

let clusters: PointFeature<AnyProps>[] = [];
type Dprops = (typeof featuresWithinBbox)[0];

const superClusterIndex = new Supercluster({
const superClusterIndex = new Supercluster<
Dprops['properties'],
Dprops['properties']
>({
// Enable this for console.logs with the timing to build each cluster
log: false,
radius: 40,
Expand All @@ -93,15 +98,24 @@ const getMapData = (map: L.Map): PointFeature<AnyProps>[] => {
}).load(featuresWithinBbox);

if (superClusterIndex) {
clusters = superClusterIndex.getClusters(bounds, map.getZoom());
const clusters = superClusterIndex.getClusters(bbox, map.getZoom());

for (const feature of clusters) {
// Used on cluster events to detect which cluster to zoom into
addExpansionZoom(superClusterIndex, feature);
if (isCluster(feature)) {
// Used on cluster events to detect which cluster to zoom into
addExpansionZoom(superClusterIndex, feature);
}
}
return clusters;
}

return clusters;
return [];
};

function isCluster<P extends GeoJsonProperties>(
feature: Supercluster.ClusterFeature<P> | PointFeature<P>
): feature is Supercluster.ClusterFeature<P> {
return feature.properties?.cluster !== undefined;
}

export default getMapData;
1 change: 0 additions & 1 deletion src/pages/MarkerClusterSpiderfy/processFeatures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,6 @@ const processFeatures = (
for (const [, features] of Object.entries(markerItems)) {
if (features.length === 1) {
// Only one marker exists at this location so no modifications necessary
// markersFinal.push(features[0]);
clusterItems.push(features[0]);
} else {
// Multiple markers exist at this location, therefore, prepare the zoomed in cluster markers
Expand Down
10 changes: 4 additions & 6 deletions src/utils/toBoundsLiteral.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { LatLngBounds } from 'leaflet';
import type { LatLngBounds, LatLngBoundsLiteral } from 'leaflet';

const toBoundsLiteral = (
bounds: LatLngBounds
): [[number, number], [number, number]] => {
const toBoundsLiteral = (bounds: LatLngBounds): LatLngBoundsLiteral => {
const southWest = bounds.getSouthWest();
const northEast = bounds.getNorthEast();

return [
[southWest.lng as number, southWest.lat as number],
[northEast.lng as number, northEast.lat as number],
[southWest.lng, southWest.lat],
[northEast.lng, northEast.lat],
];
};

Expand Down
4 changes: 4 additions & 0 deletions src/utils/toGeoJSON.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ export function toGeoJSON<DataType extends { geometry: Geometry }>(
type DataGeometry = DataType['geometry'];
type DataProperties = Omit<DataType, 'geometry'>;

if (!data.length) {
throw new Error('Data was empty');
}

const features = data.map(data => {
const { geometry, ...other } = data;

Expand Down

0 comments on commit 2a49fbb

Please sign in to comment.