From 2a49fbb29657ed105c48fedbd0d2337a8830083c Mon Sep 17 00:00:00 2001 From: Jan Thijs <92784122+janthijs@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:48:57 +0200 Subject: [PATCH] fix: update types of spiderify --- .../MarkerClusterSpiderfy.tsx | 7 +- .../MarkerClusterSpiderfy/getMapData.test.ts | 13 +-- src/pages/MarkerClusterSpiderfy/getMapData.ts | 82 +++++++++++-------- .../MarkerClusterSpiderfy/processFeatures.ts | 1 - src/utils/toBoundsLiteral.ts | 10 +-- src/utils/toGeoJSON.ts | 4 + 6 files changed, 68 insertions(+), 49 deletions(-) diff --git a/src/pages/MarkerClusterSpiderfy/MarkerClusterSpiderfy.tsx b/src/pages/MarkerClusterSpiderfy/MarkerClusterSpiderfy.tsx index c12ae58..f9993de 100644 --- a/src/pages/MarkerClusterSpiderfy/MarkerClusterSpiderfy.tsx +++ b/src/pages/MarkerClusterSpiderfy/MarkerClusterSpiderfy.tsx @@ -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; @@ -125,7 +126,7 @@ const MarkerClusterSpider: FunctionComponent = ({ return processFeatures( mapInstance, - getMapData(mapInstance) as MapSuperClusterFeature[], + getMapData(mapInstance, rawData as DataRecord[]), clusterOptions ); }, [mapInstance, zoom, center]); diff --git a/src/pages/MarkerClusterSpiderfy/getMapData.test.ts b/src/pages/MarkerClusterSpiderfy/getMapData.test.ts index d4c8627..84a6f93 100644 --- a/src/pages/MarkerClusterSpiderfy/getMapData.test.ts +++ b/src/pages/MarkerClusterSpiderfy/getMapData.test.ts @@ -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 = [ { @@ -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', () => { diff --git a/src/pages/MarkerClusterSpiderfy/getMapData.ts b/src/pages/MarkerClusterSpiderfy/getMapData.ts index 92aaaef..eb88156 100644 --- a/src/pages/MarkerClusterSpiderfy/getMapData.ts +++ b/src/pages/MarkerClusterSpiderfy/getMapData.ts @@ -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 @@ -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[], - bbox: [number, number, number, number] +export const filterPointFeaturesWithinBoundingBox =

( + features: PointFeature

[], + bbox: BBox ) => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const featuresFiltered: PointFeature[] = []; + const featuresFiltered: PointFeature

[] = []; features.forEach(feature => { if ( - isCoordWithingBoundingBox( + isCoordWithinBoundingBox( bbox, - feature.geometry.coordinates as LatLngTuple, + feature.geometry.coordinates as [number, number], 0, 1 ) @@ -50,14 +45,17 @@ export const filterPointFeaturesWithinBoundingBox = ( }; // Helpful for cluster events to detect which cluster to follow -const addExpansionZoom = ( +const addExpansionZoom = ( superClusterIndex: Supercluster, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - feature: Feature + feature: Feature ) => { 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", @@ -67,23 +65,30 @@ const addExpansionZoom = ( } }; -const getMapData = (map: L.Map): PointFeature[] => { - const bounds = toBoundsLiteral(map.getBounds()).flat() as [ - number, - number, - number, - number, +const getMapData = ( + 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[] = []; + 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, @@ -93,15 +98,24 @@ const getMapData = (map: L.Map): PointFeature[] => { }).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

( + feature: Supercluster.ClusterFeature

| PointFeature

+): feature is Supercluster.ClusterFeature

{ + return feature.properties?.cluster !== undefined; +} + export default getMapData; diff --git a/src/pages/MarkerClusterSpiderfy/processFeatures.ts b/src/pages/MarkerClusterSpiderfy/processFeatures.ts index 6e15220..69b6c78 100644 --- a/src/pages/MarkerClusterSpiderfy/processFeatures.ts +++ b/src/pages/MarkerClusterSpiderfy/processFeatures.ts @@ -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 diff --git a/src/utils/toBoundsLiteral.ts b/src/utils/toBoundsLiteral.ts index 081bfc9..6dbdbe5 100644 --- a/src/utils/toBoundsLiteral.ts +++ b/src/utils/toBoundsLiteral.ts @@ -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], ]; }; diff --git a/src/utils/toGeoJSON.ts b/src/utils/toGeoJSON.ts index 6ae0c95..62cf926 100644 --- a/src/utils/toGeoJSON.ts +++ b/src/utils/toGeoJSON.ts @@ -6,6 +6,10 @@ export function toGeoJSON( type DataGeometry = DataType['geometry']; type DataProperties = Omit; + if (!data.length) { + throw new Error('Data was empty'); + } + const features = data.map(data => { const { geometry, ...other } = data;