Skip to content

Commit ae9f8b9

Browse files
committed
refactor: adopt module approach #38
1 parent 7fec900 commit ae9f8b9

File tree

5 files changed

+124
-113
lines changed

5 files changed

+124
-113
lines changed

src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
export { TeritorioCluster } from './teritorio-cluster'
22
export {
3-
buildCss,
43
unfoldedClusterRenderCircle,
54
unfoldedClusterRenderGrid,
65
unfoldedClusterRenderHexaGrid,
76
unfoldedClusterRenderSmart,
87
} from './utils/helpers'
8+
export { buildCss } from './utils/index'

src/teritorio-cluster.ts

Lines changed: 21 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -10,55 +10,13 @@ import type {
1010
Marker,
1111
Point,
1212
} from 'maplibre-gl'
13+
import type { FeatureInClusterMatch, FeatureMatch, TeritorioClusterOptions } from './types'
1314
import bbox from '@turf/bbox'
1415
import { featureCollection } from '@turf/helpers'
1516
import maplibre from 'maplibre-gl'
1617
import { deepMerge } from './utils/deep-merge'
1718
import { clusterRenderDefault, markerRenderDefault, pinMarkerRenderDefault, unfoldedClusterRenderSmart } from './utils/helpers'
18-
19-
type UnfoldedCluster = (
20-
(
21-
parent: HTMLDivElement,
22-
items: MapGeoJSONFeature[],
23-
markerSize: number,
24-
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
25-
clickHandler: (event: Event, feature: MapGeoJSONFeature) => void
26-
) => void
27-
)
28-
type ClusterRender = (
29-
(
30-
element: HTMLDivElement,
31-
props: MapGeoJSONFeature['properties']
32-
) => void
33-
)
34-
type MarkerRender = (
35-
(
36-
element: HTMLDivElement,
37-
markerSize: number,
38-
feature?: GeoJSONFeature
39-
) => void
40-
)
41-
type PinMarkerRender = (
42-
(
43-
coords: LngLatLike,
44-
offset: Point
45-
) => Marker
46-
)
47-
interface FeatureInClusterMatch { clusterId: string, feature: GeoJSONFeature }
48-
type FeatureMatch = FeatureInClusterMatch | GeoJSONFeature
49-
50-
interface TeritorioClusterOptions {
51-
clusterMaxZoom: number
52-
clusterMinZoom: number
53-
clusterRender: ClusterRender
54-
fitBoundsOptions: FitBoundsOptions
55-
initialFeature: MapGeoJSONFeature | undefined
56-
markerRender: MarkerRender
57-
markerSize: number
58-
unfoldedClusterRender: UnfoldedCluster
59-
unfoldedClusterMaxLeaves: number
60-
pinMarkerRender: PinMarkerRender
61-
}
19+
import { calculatePinMarkerOffset, getFeatureId, isClusterFeature } from './utils/index'
6220

6321
export class TeritorioCluster extends EventTarget implements CustomLayerInterface {
6422
public id: string
@@ -148,7 +106,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
148106
}
149107

150108
public setSelectedFeature = (feature: GeoJSONFeature): void => {
151-
const id = this.getFeatureId(feature)
109+
const id = getFeatureId(feature)
152110
const match = this.findFeature(id)
153111

154112
if (!match) {
@@ -200,8 +158,13 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
200158
}
201159

202160
private renderCustom = async (currentExec: number): Promise<void> => {
203-
if (!this.shouldRender(currentExec))
161+
if (
162+
!this.map
163+
|| !this.map.isSourceLoaded(this.sourceId)
164+
|| !this.source
165+
|| this.abortExec !== currentExec) {
204166
return
167+
}
205168

206169
const features = this.map!.querySourceFeatures(this.sourceId)
207170
this.clearRenderState()
@@ -217,10 +180,10 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
217180
if (this.abortExec !== currentExec)
218181
return
219182

220-
const id = this.getFeatureId(feature)
183+
const id = getFeatureId(feature)
221184
this.featuresMap.set(id, feature)
222185

223-
if (this.isClusterFeature(feature)) {
186+
if (isClusterFeature(feature)) {
224187
const success = await this.loadClusterLeaves(id, feature)
225188

226189
if (this.abortExec !== currentExec)
@@ -233,7 +196,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
233196

234197
this.featuresMap.forEach((feature) => {
235198
const coords = feature.geometry.type === 'Point' ? new maplibre.LngLat(feature.geometry.coordinates[0], feature.geometry.coordinates[1]) : undefined
236-
const id = this.getFeatureId(feature)
199+
const id = getFeatureId(feature)
237200

238201
if (!coords) {
239202
console.error(`Feature ${id} is not Geometry.Point, thus not supported yet.`)
@@ -281,8 +244,8 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
281244

282245
// If initialFeature is part of this new cluster
283246
// We position the Pin marker on it
284-
if (this.opts.initialFeature && this.isFeatureInCluster(id, this.getFeatureId(this.opts.initialFeature))) {
285-
this.selectedFeatureId = this.getFeatureId(this.opts.initialFeature)
247+
if (this.opts.initialFeature && this.isFeatureInCluster(id, getFeatureId(this.opts.initialFeature))) {
248+
this.selectedFeatureId = getFeatureId(this.opts.initialFeature)
286249
this.renderPinMarkerInCluster(element, coords)
287250
this.opts.initialFeature = undefined
288251
}
@@ -303,7 +266,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
303266

304267
// If initialFeature is this new marker
305268
// We position the Pin marker on it
306-
if (this.opts.initialFeature && (this.getFeatureId(this.opts.initialFeature) === id)) {
269+
if (this.opts.initialFeature && (getFeatureId(this.opts.initialFeature) === id)) {
307270
this.selectedFeatureId = id
308271
this.renderPinMarker(coords)
309272
this.opts.initialFeature = undefined
@@ -321,7 +284,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
321284
}
322285

323286
if (this.opts.initialFeature && !this.selectedFeatureId && !this.pinMarker) {
324-
const id = this.getFeatureId(this.opts.initialFeature)
287+
const id = getFeatureId(this.opts.initialFeature)
325288
const coords = this.opts.initialFeature.geometry.type === 'Point'
326289
? new maplibre.LngLat(this.opts.initialFeature.geometry.coordinates[0], this.opts.initialFeature.geometry.coordinates[1])
327290
: undefined
@@ -389,20 +352,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
389352
return false
390353
}
391354

392-
return this.clusterLeaves.get(clusterId)!.findIndex(feature => this.getFeatureId(feature) === featureId) > -1
393-
}
394-
395-
private getClusterCenter = (cluster: HTMLElement): { clusterXCenter: number, clusterYCenter: number } => {
396-
const { left, right, top, bottom } = cluster.getBoundingClientRect()
397-
398-
return { clusterXCenter: (left + right) / 2, clusterYCenter: (top + bottom) / 2 }
399-
}
400-
401-
private calculatePinMarkerOffset = (cluster: HTMLElement, marker: HTMLElement): Point => {
402-
const { clusterXCenter, clusterYCenter } = this.getClusterCenter(cluster)
403-
const { x, y, height, width } = marker.getBoundingClientRect()
404-
405-
return new maplibre.Point(x - clusterXCenter + (width / 2), y - clusterYCenter + (height / 2))
355+
return this.clusterLeaves.get(clusterId)!.findIndex(feature => getFeatureId(feature) === featureId) > -1
406356
}
407357

408358
private renderPinMarkerInCluster = (cluster: HTMLElement, coords: LngLatLike): void => {
@@ -418,13 +368,13 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
418368
if (!selectedFeatureHTML)
419369
throw new Error('Selected feature HTML marker was not found !')
420370

421-
this.renderPinMarker(coords, this.calculatePinMarkerOffset(cluster, selectedFeatureHTML))
371+
this.renderPinMarker(coords, calculatePinMarkerOffset(cluster, selectedFeatureHTML))
422372
}
423373
}
424374

425375
private renderMarker = (feature: GeoJSONFeature): HTMLDivElement => {
426376
const element = document.createElement('div')
427-
element.id = this.getFeatureId(feature)
377+
element.id = getFeatureId(feature)
428378

429379
this.opts.markerRender(element, this.opts.markerSize, feature)
430380

@@ -439,7 +389,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
439389
const iterator = this.clusterLeaves.entries()
440390

441391
for (const [key, value] of iterator) {
442-
const match = value.find(feature => this.getFeatureId(feature) === id)
392+
const match = value.find(feature => getFeatureId(feature) === id)
443393

444394
if (match) {
445395
return { clusterId: key, feature: match }
@@ -450,7 +400,7 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
450400
private featureClickHandler = (event: Event, feature: GeoJSONFeature): void => {
451401
event.stopPropagation()
452402

453-
if (!(event.currentTarget instanceof HTMLElement) || this.selectedFeatureId === this.getFeatureId(feature))
403+
if (!(event.currentTarget instanceof HTMLElement) || this.selectedFeatureId === getFeatureId(feature))
454404
return
455405

456406
this.setSelectedFeature(feature)
@@ -492,44 +442,11 @@ export class TeritorioCluster extends EventTarget implements CustomLayerInterfac
492442
}
493443
}
494444

495-
private shouldRender = (currentExec: number): boolean => {
496-
return !!this.map
497-
&& this.map.isSourceLoaded(this.sourceId)
498-
&& !!this.source
499-
&& this.abortExec === currentExec
500-
}
501-
502445
private clearRenderState = (): void => {
503446
this.clusterLeaves.clear()
504447
this.featuresMap.clear()
505448
}
506449

507-
private isClusterFeature = (feature: GeoJSONFeature): boolean => {
508-
return Boolean(feature.properties?.cluster)
509-
}
510-
511-
private getFeatureId = (feature: GeoJSONFeature): string => {
512-
if (feature.properties.cluster) {
513-
if (!feature.id)
514-
throw new Error('Cluster feature is missing "id".')
515-
return feature.id.toString()
516-
}
517-
518-
// Vido support: shouldn't be part of this plugin
519-
let metadata = feature.properties.metadata
520-
521-
if (typeof metadata === 'string') {
522-
try {
523-
metadata = JSON.parse(metadata)
524-
}
525-
catch {
526-
metadata = undefined
527-
}
528-
}
529-
530-
return (metadata?.id ?? feature.properties?.id).toString()
531-
}
532-
533450
private setSource = (): void => {
534451
if (!this.map)
535452
return

src/types.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import type {
2+
FitBoundsOptions,
3+
GeoJSONFeature,
4+
LngLatLike,
5+
MapGeoJSONFeature,
6+
Marker,
7+
Point,
8+
} from 'maplibre-gl'
9+
10+
export type UnfoldedCluster = (
11+
parent: HTMLDivElement,
12+
items: MapGeoJSONFeature[],
13+
markerSize: number,
14+
renderMarker: (feature: MapGeoJSONFeature) => HTMLDivElement,
15+
clickHandler: (event: Event, feature: MapGeoJSONFeature) => void
16+
) => void
17+
18+
export type ClusterRender = (
19+
element: HTMLDivElement,
20+
props: MapGeoJSONFeature['properties']
21+
) => void
22+
23+
export type MarkerRender = (
24+
element: HTMLDivElement,
25+
markerSize: number,
26+
feature?: GeoJSONFeature
27+
) => void
28+
29+
export type PinMarkerRender = (
30+
coords: LngLatLike,
31+
offset: Point
32+
) => Marker
33+
34+
export interface FeatureInClusterMatch {
35+
clusterId: string
36+
feature: GeoJSONFeature
37+
}
38+
39+
export type FeatureMatch = FeatureInClusterMatch | GeoJSONFeature
40+
41+
export interface TeritorioClusterOptions {
42+
clusterMaxZoom: number
43+
clusterMinZoom: number
44+
clusterRender: ClusterRender
45+
fitBoundsOptions: FitBoundsOptions
46+
initialFeature: MapGeoJSONFeature | undefined
47+
markerRender: MarkerRender
48+
markerSize: number
49+
unfoldedClusterRender: UnfoldedCluster
50+
unfoldedClusterMaxLeaves: number
51+
pinMarkerRender: PinMarkerRender
52+
}

src/utils/helpers.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,6 @@
11
import type { LngLatLike, MapGeoJSONFeature, Marker, Point } from 'maplibre-gl'
22
import maplibre from 'maplibre-gl'
3-
4-
// Helper to apply styles on DOM element
5-
export function buildCss(htmlEl: HTMLElement, styles: { [key: string]: string }): void {
6-
const rules = htmlEl.style
7-
8-
for (const property in styles)
9-
rules.setProperty(property, styles[property])
10-
}
3+
import { buildCss } from './index'
114

125
// Circle shape
136
export function unfoldedClusterRenderCircle(

src/utils/index.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import type { GeoJSONFeature } from 'maplibre-gl'
2+
import { Point } from 'maplibre-gl'
3+
4+
export function isClusterFeature(feature: GeoJSONFeature): boolean {
5+
return Boolean(feature.properties?.cluster)
6+
}
7+
8+
export function getFeatureId(feature: GeoJSONFeature): string {
9+
if (feature.properties.cluster) {
10+
if (!feature.id)
11+
throw new Error('Cluster feature is missing "id".')
12+
return feature.id.toString()
13+
}
14+
15+
// Vido support: shouldn't be part of this plugin
16+
let metadata = feature.properties.metadata
17+
if (typeof metadata === 'string') {
18+
try {
19+
metadata = JSON.parse(metadata)
20+
}
21+
catch {
22+
metadata = undefined
23+
}
24+
}
25+
26+
return (metadata?.id ?? feature.properties?.id).toString()
27+
}
28+
29+
export function calculatePinMarkerOffset(cluster: HTMLElement, marker: HTMLElement): Point {
30+
const { clusterXCenter, clusterYCenter } = getClusterCenter(cluster)
31+
const { x, y, height, width } = marker.getBoundingClientRect()
32+
return new Point(x - clusterXCenter + width / 2, y - clusterYCenter + height / 2)
33+
}
34+
35+
// Helper to apply styles on DOM element
36+
export function buildCss(htmlEl: HTMLElement, styles: { [key: string]: string }): void {
37+
const rules = htmlEl.style
38+
39+
for (const property in styles)
40+
rules.setProperty(property, styles[property])
41+
}
42+
43+
function getClusterCenter(cluster: HTMLElement): {
44+
clusterXCenter: number
45+
clusterYCenter: number
46+
} {
47+
const { left, right, top, bottom } = cluster.getBoundingClientRect()
48+
return { clusterXCenter: (left + right) / 2, clusterYCenter: (top + bottom) / 2 }
49+
}

0 commit comments

Comments
 (0)