|
14 | 14 | import DateRangeSelector from '$lib/components/DateRangeSelector.svelte'; |
15 | 15 | // @ts-ignore |
16 | 16 | import type { Feature, Geometry } from 'geojson'; |
| 17 | + import { pinSVGs } from '$lib/components/pins/svg'; |
17 | 18 |
|
18 | | - // Subscribe to pins store |
19 | 19 | const pins = $derived($pinsStore); |
20 | 20 | let map = $state<MaplibreMap | undefined>(undefined); |
21 | | - let sidebarCollapsed = $state(false); |
| 21 | + let sidebarCollapsed = $state<boolean>(false); |
22 | 22 | let geoJsonData = $state(eventsToGeoJSON([])); |
23 | 23 |
|
24 | | - // Function to update pins based on current map bounds |
| 24 | + function loadPinImages() { |
| 25 | + if (!map) return; |
| 26 | +
|
| 27 | + Object.entries(pinSVGs).forEach(([name, svg]) => { |
| 28 | + const img = new Image(); |
| 29 | + img.onload = () => { |
| 30 | + if (map && !map.hasImage(`pin-${name}`)) { |
| 31 | + map.addImage(`pin-${name}`, img); |
| 32 | + console.log(`Image pin-${name} chargée`); |
| 33 | + } |
| 34 | + }; |
| 35 | +
|
| 36 | + const blob = new Blob([svg as string], { type: 'image/svg+xml' }); |
| 37 | + const url = URL.createObjectURL(blob); |
| 38 | + img.src = url; |
| 39 | + }); |
| 40 | + } |
| 41 | +
|
25 | 42 | async function updatePins() { |
26 | 43 | if (!map) return; |
27 | 44 | const events = await pinsStore.loadPins(map.getBounds()); |
28 | 45 | geoJsonData = eventsToGeoJSON(events); |
29 | 46 | } |
30 | 47 |
|
31 | | - // Effect to add map event listeners when map is available |
32 | 48 | $effect(() => { |
33 | 49 | if (!map) return; |
34 | 50 |
|
35 | 51 | map.on('moveend', updatePins); |
| 52 | + map.on('load', loadPinImages); |
36 | 53 |
|
37 | 54 | updatePins(); |
| 55 | + loadPinImages(); |
38 | 56 |
|
39 | 57 | // Add geolocate control to the map |
40 | 58 | map.addControl( |
|
51 | 69 |
|
52 | 70 | return () => { |
53 | 71 | map?.off('moveend', updatePins); |
| 72 | + map?.off('load', loadPinImages); |
54 | 73 | }; |
55 | 74 | }); |
56 | 75 |
|
57 | | - // Update GeoJSON data when pins change |
| 76 | + let prevPinsLength = 0; |
| 77 | + let prevPinsString = ''; |
| 78 | +
|
58 | 79 | $effect(() => { |
59 | | - geoJsonData = eventsToGeoJSON(pins); |
| 80 | + const currentPinsString = JSON.stringify(pins.map((p: any) => p.id)); |
| 81 | +
|
| 82 | + if (pins.length !== prevPinsLength || currentPinsString !== prevPinsString) { |
| 83 | + prevPinsLength = pins.length; |
| 84 | + prevPinsString = currentPinsString; |
| 85 | +
|
| 86 | + geoJsonData = eventsToGeoJSON(pins); |
| 87 | + } |
60 | 88 | }); |
61 | 89 | </script> |
62 | 90 |
|
|
82 | 110 | id="events" |
83 | 111 | data={geoJsonData} |
84 | 112 | cluster={{ |
85 | | - radius: 40, |
86 | | - maxZoom: 14 |
| 113 | + radius: 100, |
| 114 | + maxZoom: 13 |
87 | 115 | }} |
88 | 116 | > |
89 | 117 | <CircleLayer |
90 | 118 | id="cluster_circles" |
91 | 119 | applyToClusters |
92 | 120 | cursor="pointer" |
93 | 121 | paint={{ |
94 | | - 'circle-color': '#2196f3', |
| 122 | + 'circle-color': [ |
| 123 | + 'interpolate', |
| 124 | + ['linear'], |
| 125 | + ['get', 'point_count'], |
| 126 | + 1, 'rgba(255, 240, 50, 0.95)', |
| 127 | + 5, 'rgba(255, 150, 0, 0.95)', |
| 128 | + 15, 'rgba(255, 0, 50, 0.95)', |
| 129 | + 30, 'rgba(200, 0, 100, 0.95)', |
| 130 | + 50, 'rgba(100, 0, 150, 0.95)' |
| 131 | + ], |
95 | 132 | 'circle-radius': [ |
96 | | - 'step', |
| 133 | + 'interpolate', |
| 134 | + ['linear'], |
97 | 135 | ['get', 'point_count'], |
98 | | - 20, // Size for small clusters |
99 | | - 20, // Threshold |
100 | | - 30, // Size for medium clusters |
101 | | - 50, // Threshold |
102 | | - 40 // Size for large clusters |
103 | | - ] as any, |
| 136 | + 1, 35, |
| 137 | + 5, 45, |
| 138 | + 15, 55, |
| 139 | + 30, 65, |
| 140 | + 50, 75 |
| 141 | + ], |
| 142 | + 'circle-blur': 1.5, |
| 143 | + 'circle-opacity': 0.8, |
104 | 144 | }} |
105 | 145 | > |
106 | 146 | </CircleLayer> |
|
116 | 156 | 'text-font': ['Open Sans Bold'] |
117 | 157 | }} |
118 | 158 | paint={{ |
119 | | - 'text-color': '#ffffff' |
| 159 | + 'text-color': '#222222' |
120 | 160 | }} |
121 | 161 | /> |
122 | 162 |
|
123 | | - <CircleLayer |
124 | | - id="events_circle" |
| 163 | + <SymbolLayer |
| 164 | + id="event_points" |
125 | 165 | applyToClusters={false} |
126 | 166 | hoverCursor="pointer" |
127 | | - paint={{ |
128 | | - 'circle-color': [ |
| 167 | + layout={{ |
| 168 | + 'icon-image': [ |
129 | 169 | 'match', |
130 | 170 | ['get', 'kind'], |
131 | | - 'movie', 'rgb(94, 37, 207)', |
132 | | - '#2196f3' // default color |
133 | | - ] as any, |
134 | | - 'circle-radius': 8, |
| 171 | + 'movie', 'pin-movie', |
| 172 | + 'concert', 'pin-concert', |
| 173 | + 'festival', 'pin-festival', |
| 174 | + 'theater', 'pin-theater', |
| 175 | + 'party', 'pin-party', |
| 176 | + 'pin-default' |
| 177 | + ], |
| 178 | + 'icon-size': 1.0, |
| 179 | + 'icon-allow-overlap': true, |
| 180 | + 'icon-anchor': 'bottom' |
135 | 181 | }} |
136 | 182 | > |
137 | 183 | <Popup openOn="click"> |
138 | 184 | {#snippet children({ data }: { data: Feature<Geometry, EventWithCoordinates> | undefined })} |
139 | 185 | <EventPopup feature={data ?? undefined} /> |
140 | 186 | {/snippet} |
141 | 187 | </Popup> |
142 | | - </CircleLayer> |
| 188 | + </SymbolLayer> |
143 | 189 | </GeoJSON> |
144 | 190 | </MapLibre> |
145 | 191 |
|
|
165 | 211 | right: 10px; |
166 | 212 | } |
167 | 213 |
|
168 | | - /* Style des popups */ |
169 | 214 | :global(.maplibregl-popup-content) { |
170 | 215 | background: transparent !important; |
171 | 216 | padding: 0 !important; |
|
192 | 237 | border-left-color: rgb(240, 240, 245) !important; |
193 | 238 | border-right-color: transparent !important; |
194 | 239 | } |
| 240 | +
|
| 241 | + :global(.maplibregl-marker) { |
| 242 | + background: none !important; |
| 243 | + } |
195 | 244 | </style> |
0 commit comments