pretix-map 0.1.2__py3-none-any.whl → 0.1.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,105 +1,118 @@
1
- // Wait for the DOM to be fully loaded before running map code
2
1
  document.addEventListener('DOMContentLoaded', function () {
3
- console.log("Sales Map JS Loaded (FINAL TEST - INCLUDING TOGGLE)");
2
+ console.log("Sales Map JS Loaded (Cluster Toggle & Heatmap Opts)");
3
+
4
+ // --- L.Icon.Default setup ---
5
+ try {
6
+ delete L.Icon.Default.prototype._getIconUrl;
7
+ L.Icon.Default.mergeOptions({
8
+ iconRetinaUrl: '/static/leaflet/images/marker-icon-2x.png',
9
+ iconUrl: '/static/leaflet/images/marker-icon.png',
10
+ shadowUrl: '/static/leaflet/images/marker-shadow.png',
11
+ iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34],
12
+ tooltipAnchor: [16, -28], shadowSize: [41, 41]
13
+ });
14
+ console.log("Set Leaflet default icon image paths explicitly.");
15
+ } catch (e) {
16
+ console.error("Error setting icon path:", e);
17
+ }
18
+ // --- End L.Icon.Default setup ---
19
+
4
20
 
5
21
  // --- Configuration ---
6
22
  const mapContainerId = 'sales-map-container';
7
23
  const statusOverlayId = 'map-status-overlay';
8
- const toggleButtonId = 'view-toggle-btn';
24
+ const viewToggleButtonId = 'view-toggle-btn';
25
+ const clusterToggleButtonId = 'cluster-toggle-btn';
26
+ const heatmapOptionsPanelId = 'heatmap-options-panel';
9
27
  const initialZoom = 5;
10
- const defaultMapView = 'pins'; // Can be 'pins' or 'heatmap'
11
- const heatmapOptions = {
12
- radius: 25,
13
- blur: 15,
14
- maxZoom: 18,
15
- // max: 1.0, // Let leaflet-heat calculate max based on data density
16
- minOpacity: 0.2
28
+ const defaultMapView = 'pins';
29
+
30
+ // --- Globals & State ---
31
+ let map = null;
32
+ let coordinateData = [];
33
+ let pinLayer = null;
34
+ let heatmapLayer = null;
35
+ let currentView = defaultMapView;
36
+ let dataUrl = null;
37
+ let isClusteringEnabled = true;
38
+ let heatmapOptions = {
39
+ radius: 25, blur: 15, maxZoom: 18, minOpacity: 0.2
17
40
  };
18
41
 
19
- // --- Globals ---
20
- let map = null; // Leaflet map instance
21
- let coordinateData = []; // To store fetched [{lat: Y, lon: X, tooltip: Z}, ...] objects
22
- let pinLayer = null; // Layer for markers/clusters
23
- let heatmapLayer = null; // Layer for heatmap
24
- let currentView = defaultMapView; // Track current view state
25
- let dataUrl = null; // To store the API endpoint URL
26
-
42
+ // --- DOM Elements ---
27
43
  const mapElement = document.getElementById(mapContainerId);
28
44
  const statusOverlay = document.getElementById(statusOverlayId);
29
- const toggleButton = document.getElementById(toggleButtonId);
30
-
31
- // --- Helper to update status overlay ---
45
+ const viewToggleButton = document.getElementById(viewToggleButtonId);
46
+ const clusterToggleButton = document.getElementById(clusterToggleButtonId);
47
+ const heatmapOptionsPanel = document.getElementById(heatmapOptionsPanelId);
48
+ const heatmapRadiusInput = document.getElementById('heatmap-radius');
49
+ const heatmapBlurInput = document.getElementById('heatmap-blur');
50
+ const heatmapMaxZoomInput = document.getElementById('heatmap-maxZoom');
51
+ const radiusValueSpan = document.getElementById('radius-value');
52
+ const blurValueSpan = document.getElementById('blur-value');
53
+ const maxZoomValueSpan = document.getElementById('maxzoom-value');
54
+
55
+ // --- Status Update Helpers ---
32
56
  function updateStatus(message, isError = false) {
33
57
  if (statusOverlay) {
34
58
  const p = statusOverlay.querySelector('p');
35
59
  if (p) {
36
60
  p.textContent = message;
37
- p.className = isError ? 'text-danger' : ''; // Apply error class if needed
61
+ p.className = isError ? 'text-danger' : '';
38
62
  }
39
- statusOverlay.style.display = 'flex'; // Make sure it's visible
63
+ statusOverlay.style.display = 'flex';
40
64
  } else {
41
65
  console.warn("Status overlay element not found.");
42
66
  }
43
67
  }
44
68
 
45
- // --- Helper to hide status overlay ---
46
69
  function hideStatus() {
47
70
  if (statusOverlay) {
48
- statusOverlay.style.display = 'none'; // Hide the overlay
71
+ statusOverlay.style.display = 'none';
49
72
  }
50
73
  }
51
74
 
75
+ // --- End Status Helpers ---
76
+
77
+
52
78
  // --- Initialization ---
53
79
  function initializeMap() {
54
- console.log("Initializing Leaflet map...");
55
-
56
80
  if (!mapElement) {
57
- console.error(`Map container element #${mapContainerId} not found.`);
58
- return; // Stop initialization if container is missing
81
+ console.error(`Map container #${mapContainerId} not found.`);
82
+ return;
59
83
  }
60
84
  if (!statusOverlay) {
61
- console.warn("Status overlay not found");
62
- }
63
- if (!toggleButton) {
64
- // Log a warning but don't necessarily stop if button is missing
65
- console.warn(`Toggle button #${toggleButtonId} not found.`);
85
+ console.warn("Status overlay element not found.");
66
86
  }
87
+ if (!viewToggleButton) console.warn("View toggle button not found.");
88
+ if (!clusterToggleButton) console.warn("Cluster toggle button not found.");
89
+ if (!heatmapOptionsPanel) console.warn("Heatmap options panel not found.");
90
+ if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput) console.warn("Heatmap input elements missing.");
67
91
 
68
- // --- Get the data URL from the container's data attribute ---
69
92
  dataUrl = mapElement.dataset.dataUrl;
70
93
  if (!dataUrl) {
71
- console.error("Data URL not found in container's data-data-url attribute! Cannot fetch data.");
72
94
  updateStatus("Configuration Error: Missing data source URL.", true);
73
- return; // Stop initialization if data URL is missing
95
+ return;
74
96
  }
75
97
  console.log(`Data URL found: ${dataUrl}`);
76
- // --- End data URL retrieval ---
77
-
78
- // Set Leaflet default image path (if needed, depends on static file setup)
79
- L.Icon.Default.imagePath = '/static/leaflet/images/'; // Ensure this path is correct
80
- console.log("Set Leaflet default imagePath to:", L.Icon.Default.imagePath);
81
-
82
- console.log("Initializing Leaflet map...");
83
98
  updateStatus("Initializing map...");
99
+
84
100
  try {
85
- map = L.map(mapContainerId).setView([48.85, 2.35], initialZoom); // Default center
101
+ map = L.map(mapContainerId).setView([48.85, 2.35], initialZoom);
86
102
  console.log("L.map() called successfully.");
87
103
 
88
- console.log("Adding Tile Layer...");
89
104
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
90
- attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
105
+ attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
106
+ maxZoom: 18,
107
+ }).on('load', function () {
108
+ console.log('Base tiles loaded.');
91
109
  }).addTo(map);
92
110
  console.log("Tile layer added successfully.");
93
111
 
94
- // Setup toggle button listener if it exists
95
- if (toggleButton) {
96
- setupToggleButton(); // Call setup function
97
- } else {
98
- console.log("Toggle button not found, skipping listener setup.");
99
- }
100
-
101
- // Fetch data and populate the map layers
102
- fetchCoordinateData();
112
+ if (viewToggleButton) setupViewToggleButton();
113
+ if (clusterToggleButton) setupClusterToggleButton();
114
+ setupHeatmapControls();
115
+ fetchDataAndDraw();
103
116
 
104
117
  } catch (error) {
105
118
  console.error("ERROR during Leaflet initialization:", error);
@@ -107,137 +120,116 @@ document.addEventListener('DOMContentLoaded', function () {
107
120
  }
108
121
  }
109
122
 
110
- // --- Data Fetching ---
111
- function fetchCoordinateData() {
112
- // dataUrl should be set during initialization
113
- if (!dataUrl) {
114
- console.error("Cannot fetch data: dataUrl is not set.");
115
- return;
116
- }
117
123
 
124
+ // --- Data Fetching & Initial Drawing ---
125
+ function fetchDataAndDraw() {
126
+ if (!dataUrl) return;
118
127
  console.log("Fetching coordinates from:", dataUrl);
119
- updateStatus("Loading ticket locations..."); // Update status
128
+ updateStatus("Loading ticket locations...");
129
+ if (viewToggleButton) viewToggleButton.disabled = true;
130
+ if (clusterToggleButton) clusterToggleButton.disabled = true;
131
+ disableHeatmapControls(true);
120
132
 
121
133
  fetch(dataUrl)
122
134
  .then(response => {
123
- if (!response.ok) {
124
- // Throw an error with status text to be caught below
125
- throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
126
- }
135
+ if (!response.ok) throw new Error(`HTTP error! Status: ${response.status} ${response.statusText}`);
127
136
  return response.json();
128
137
  })
129
138
  .then(data => {
130
- if (data.error) { // Check for application-level errors from the backend
131
- throw new Error(`API Error: ${data.error}`);
132
- }
133
- // --- Adjust check for the new data structure ---
139
+ if (data.error) throw new Error(`API Error: ${data.error}`);
134
140
  if (!data || !data.locations || !Array.isArray(data.locations)) {
135
- console.warn("Invalid or empty data format received:", data);
136
- updateStatus("No valid geocoded ticket locations found.", false); // Inform user
137
- if (toggleButton) toggleButton.disabled = true; // Disable button if no data
138
- coordinateData = []; // Ensure it's empty
139
- return; // Stop processing if data is missing/invalid
141
+ console.warn("Invalid data format:", data);
142
+ updateStatus("No valid locations found.", false);
143
+ coordinateData = [];
144
+ hideStatus();
145
+ return;
140
146
  }
141
147
  if (data.locations.length === 0) {
142
- console.log("No coordinate data received (empty list).");
143
- updateStatus("No geocoded ticket locations found for this event.", false);
144
- if (toggleButton) toggleButton.disabled = true;
148
+ console.log("No locations received.");
149
+ updateStatus("No locations found for event.", false);
145
150
  coordinateData = [];
151
+ hideStatus();
146
152
  return;
147
153
  }
148
- // --- End structure check ---
149
154
 
150
- coordinateData = data.locations; // Store the [{lat: Y, lon: X, tooltip: Z}, ...] array
155
+ coordinateData = data.locations;
151
156
  console.log(`Received ${coordinateData.length} coordinates.`);
152
157
 
153
- // --- Create layers (but don't add to map yet) ---
154
- createMapLayers();
158
+ if (viewToggleButton) viewToggleButton.disabled = false;
159
+ if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
160
+ disableHeatmapControls(false);
155
161
 
156
- // --- Show the default view ---
162
+ createAllLayers();
157
163
  showCurrentView();
158
-
159
- // Adjust map bounds to fit markers if coordinates were found
160
164
  adjustMapBounds();
161
-
162
- // Enable button if it was disabled and we got data
163
- if (toggleButton) toggleButton.disabled = false;
164
165
  hideStatus();
165
166
 
166
- // Force redraw just in case (sometimes needed after dynamic content/bounds changes)
167
167
  setTimeout(function () {
168
168
  console.log("Forcing map.invalidateSize() after data load...");
169
- console.log("Map container element just before invalidateSize:", document.getElementById(mapContainerId)); // Check if it exists here
170
- if (map && document.getElementById(mapContainerId)) { // Add check before calling
169
+ const container = document.getElementById(mapContainerId);
170
+ if (map && container && container.offsetWidth > 0) {
171
171
  map.invalidateSize();
172
172
  } else {
173
- console.warn("Skipping invalidateSize because map or container is missing.");
173
+ console.warn(`Skipping invalidateSize. Map: ${!!map}, Container: ${!!container}, OffsetWidth: ${container ? container.offsetWidth : 'N/A'}`);
174
174
  }
175
175
  }, 100);
176
-
177
176
  })
178
177
  .catch(error => {
179
- console.error('Error fetching or processing coordinate data:', error);
180
- console.log("Map container element during fetch error:", document.getElementById(mapContainerId)); // Check if it exists here
181
- updateStatus(`Error loading map data: ${error.message}. Please try again later.`, true); // Show error in overlay
182
- if (toggleButton) toggleButton.disabled = true;
178
+ console.error('Error fetching/processing data:', error);
179
+ updateStatus(`Error loading map data: ${error.message}.`, true);
183
180
  });
184
181
  }
185
182
 
186
- // --- Layer Creation ---
187
- function createMapLayers() {
188
- if (!map || coordinateData.length === 0) {
189
- console.log("Skipping layer creation (no map or data).");
183
+
184
+ // --- Layer Creation Functions ---
185
+ function createAllLayers() {
186
+ createPinLayer();
187
+ createHeatmapLayer();
188
+ console.log("Layers created/updated.");
189
+ }
190
+
191
+ function createPinLayer() {
192
+ console.log(`Creating pin layer (Clustering: ${isClusteringEnabled})...`);
193
+ pinLayer = null;
194
+ if (coordinateData.length === 0) {
195
+ console.warn("No data for pin layer.");
190
196
  return;
191
197
  }
192
-
193
- // 1. Create Pin Layer (using MarkerCluster)
194
- console.log("Creating pin layer instance (marker cluster)...");
195
- pinLayer = L.markerClusterGroup(); // Initialize cluster group
196
- coordinateData.forEach((loc, index) => { // loc is now {lat, lon, tooltip, order_url}
198
+ const markers = [];
199
+ coordinateData.forEach((loc, index) => {
197
200
  try {
198
- if (loc.lat == null || loc.lon == null) { /* ... skip invalid ... */
199
- return;
200
- }
201
- const latLng = L.latLng(loc.lat, loc.lon);
202
- if (isNaN(latLng.lat) || isNaN(latLng.lng)) { /* ... skip invalid ... */
203
- return;
204
- }
205
-
206
- const marker = L.marker(latLng);
207
-
208
- // --- Use the enhanced tooltip from backend ---
209
- // Leaflet tooltips handle HTML content by default
210
- if (loc.tooltip) {
211
- marker.bindTooltip(loc.tooltip);
212
- }
213
- // --- End Tooltip ---
214
-
215
- // --- Add Click Listener to open order URL ---
216
- if (loc.order_url) { // Only add listener if URL was successfully generated
217
- marker.on('click', function () {
218
- console.log(`Marker clicked, opening URL: ${loc.order_url}`);
219
- // Open in a new tab, which is usually better for control panel links
220
- window.open(loc.order_url, '_blank');
221
- // If you prefer opening in the same tab:
222
- // window.location.href = loc.order_url;
223
- });
224
- } else {
225
- // Log if URL is missing for a marker, maybe backend issue
226
- console.warn(`Order URL missing for coordinate index ${index}, click disabled for this marker.`);
201
+ if (loc.lat == null || loc.lon == null || isNaN(loc.lat) || isNaN(loc.lon)) return;
202
+ const marker = L.marker(L.latLng(loc.lat, loc.lon));
203
+ if (loc.tooltip) marker.bindTooltip(loc.tooltip);
204
+ if (loc.order_url) {
205
+ marker.on('click', () => window.open(loc.order_url, '_blank'));
227
206
  }
228
- // --- End Click Listener ---
229
-
230
- pinLayer.addLayer(marker); // Add marker to cluster group
231
-
207
+ markers.push(marker);
232
208
  } catch (e) {
233
- console.error(`Error creating marker for coordinate ${index}:`, loc, e);
209
+ console.error(`Error creating marker ${index}:`, e);
234
210
  }
235
211
  });
236
- console.log("Pin layer instance created with markers (incl. tooltips and clicks).");
237
-
212
+ if (markers.length === 0) {
213
+ console.warn("No valid markers created.");
214
+ return;
215
+ }
216
+ if (isClusteringEnabled) {
217
+ pinLayer = L.markerClusterGroup();
218
+ pinLayer.addLayers(markers);
219
+ console.log("Marker cluster populated.");
220
+ } else {
221
+ pinLayer = L.layerGroup(markers);
222
+ console.log("Simple layer group populated.");
223
+ }
224
+ }
238
225
 
239
- // 2. Create Heatmap Layer (No changes needed here)
240
- console.log("Creating heatmap layer instance...");
226
+ function createHeatmapLayer() {
227
+ console.log("Creating heatmap layer...");
228
+ heatmapLayer = null;
229
+ if (coordinateData.length === 0) {
230
+ console.warn("No data for heatmap.");
231
+ return;
232
+ }
241
233
  try {
242
234
  const heatPoints = coordinateData.map(loc => {
243
235
  if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
@@ -245,35 +237,59 @@ document.addEventListener('DOMContentLoaded', function () {
245
237
  }
246
238
  return null;
247
239
  }).filter(p => p !== null);
248
-
249
240
  if (heatPoints.length > 0) {
250
241
  heatmapLayer = L.heatLayer(heatPoints, heatmapOptions);
251
- console.log("Heatmap layer instance created.");
252
- } else { /* ... handle no valid points ... */
253
- heatmapLayer = null;
242
+ console.log("Heatmap created:", heatmapOptions);
243
+ } else {
244
+ console.warn("No valid points for heatmap.");
254
245
  }
255
- } catch (e) { /* ... error handling ... */
256
- heatmapLayer = null;
246
+ } catch (e) {
247
+ console.error("Error creating heatmap:", e);
257
248
  }
249
+ }
258
250
 
259
- console.log("Map layer instances created/updated.");
251
+
252
+ // --- Layer Update Functions ---
253
+ function redrawPinLayer() {
254
+ if (!map) return;
255
+ console.log("Redrawing pin layer...");
256
+ if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
257
+ pinLayer = null;
258
+ createPinLayer();
259
+ if (currentView === 'pins' && pinLayer) {
260
+ console.log("Adding new pin layer.");
261
+ map.addLayer(pinLayer);
262
+ }
260
263
  }
261
264
 
262
- // --- Adjust Map Bounds ---
265
+ function updateHeatmap() {
266
+ if (!map || !heatmapLayer) {
267
+ console.warn("Cannot update heatmap.");
268
+ return;
269
+ }
270
+ console.log("Updating heatmap opts:", heatmapOptions);
271
+ try {
272
+ heatmapLayer.setOptions(heatmapOptions);
273
+ console.log("Heatmap opts updated.");
274
+ } catch (e) {
275
+ console.error("Error setting heatmap opts:", e);
276
+ }
277
+ }
278
+
279
+
280
+ // --- Adjust Map Bounds (Corrected) ---
263
281
  function adjustMapBounds() {
264
282
  if (!map || coordinateData.length === 0) return;
265
-
266
283
  try {
267
284
  let bounds = null;
268
- // Prefer using marker cluster bounds if available and valid
269
- if (pinLayer && typeof pinLayer.getBounds === 'function') {
285
+ if (currentView === 'pins' && pinLayer && typeof pinLayer.getBounds === 'function') {
270
286
  bounds = pinLayer.getBounds();
271
- console.log("Attempting to get bounds from pin layer (marker cluster).");
287
+ console.log("Attempting bounds from pin layer.");
272
288
  }
273
-
274
- // If no valid bounds from cluster, or only heatmap exists, calculate from raw data
289
+ // Calculate from raw if pin layer bounds unavailable/invalid or if in heatmap view
275
290
  if (!bounds || !bounds.isValid()) {
276
- console.log("Pin layer bounds invalid or unavailable, calculating bounds from raw coordinates.");
291
+ console.log("Calculating bounds from raw coordinates.");
292
+ // Filter valid lat/lon pairs
277
293
  const latLngs = coordinateData
278
294
  .map(loc => {
279
295
  if (loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)) {
@@ -282,97 +298,155 @@ document.addEventListener('DOMContentLoaded', function () {
282
298
  return null;
283
299
  })
284
300
  .filter(p => p !== null);
285
-
286
- if (latLngs.length > 0) {
287
- bounds = L.latLngBounds(latLngs);
288
- }
301
+ if (latLngs.length > 0) bounds = L.latLngBounds(latLngs);
289
302
  }
290
303
 
291
- // Fit map to bounds if valid bounds were found
304
+ // Apply bounds if valid
292
305
  if (bounds && bounds.isValid()) {
293
- console.log("Fitting map to calculated bounds...");
294
- map.fitBounds(bounds, {padding: [50, 50]}); // Add padding
306
+ console.log("Fitting map to bounds...");
307
+ map.fitBounds(bounds, {padding: [50, 50]});
295
308
  console.log("Bounds fitted.");
296
- } else if (coordinateData.length === 1) {
297
- // Special case for a single point
298
- console.log("Only one valid coordinate, setting view directly.");
309
+ // Handle single point case
310
+ } else if (coordinateData.filter(loc => loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon)).length === 1) {
311
+ console.log("Setting view for single coordinate.");
312
+ // --- Corrected logic to find the single valid coordinate ---
299
313
  const singleCoord = coordinateData.find(loc => loc.lat != null && loc.lon != null && !isNaN(loc.lat) && !isNaN(loc.lon));
300
- if (singleCoord) {
301
- map.setView([singleCoord.lat, singleCoord.lon], 13); // Zoom level 13 for single point
302
- } else {
303
- console.warn("Could not find the single valid coordinate to set view.");
304
- }
314
+ // --- End Correction ---
315
+ if (singleCoord) map.setView([singleCoord.lat, singleCoord.lon], 13);
316
+ else console.warn("Could not find single valid coordinate to set view.");
317
+ // Removed extra ')' here
305
318
  } else {
306
- console.warn("Could not determine valid bounds to fit the map.");
319
+ console.warn("Could not determine valid bounds.");
307
320
  }
308
321
  } catch (e) {
309
322
  console.error("Error fitting map bounds:", e);
310
323
  }
311
- }
324
+ } // End adjustMapBounds function
325
+
312
326
 
313
- // --- View Toggling ---
314
- function setupToggleButton() {
315
- updateButtonText(); // Set initial text
316
- toggleButton.addEventListener('click', () => {
317
- console.log("Toggle button clicked!");
327
+ // --- Control Setup Functions ---
328
+ function setupViewToggleButton() {
329
+ updateViewToggleButtonText();
330
+ viewToggleButton.addEventListener('click', () => {
331
+ console.log("View toggle clicked!");
318
332
  currentView = (currentView === 'pins') ? 'heatmap' : 'pins';
319
- showCurrentView(); // Update the map layers
320
- updateButtonText(); // Update the button text
333
+ showCurrentView();
334
+ updateViewToggleButtonText();
335
+ if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
336
+ });
337
+ console.log("View toggle listener setup.");
338
+ }
339
+
340
+ function setupClusterToggleButton() {
341
+ updateClusterToggleButtonText();
342
+ clusterToggleButton.disabled = (currentView !== 'pins');
343
+ clusterToggleButton.addEventListener('click', () => {
344
+ if (currentView !== 'pins') return;
345
+ console.log("Cluster toggle clicked!");
346
+ isClusteringEnabled = !isClusteringEnabled;
347
+ redrawPinLayer();
348
+ updateClusterToggleButtonText();
349
+ });
350
+ console.log("Cluster toggle listener setup.");
351
+ }
352
+
353
+ function setupHeatmapControls() {
354
+ if (!heatmapRadiusInput || !heatmapBlurInput || !heatmapMaxZoomInput || !radiusValueSpan || !blurValueSpan || !maxZoomValueSpan) {
355
+ console.error("Heatmap controls missing.");
356
+ return;
357
+ }
358
+ radiusValueSpan.textContent = heatmapOptions.radius;
359
+ heatmapRadiusInput.value = heatmapOptions.radius;
360
+ blurValueSpan.textContent = heatmapOptions.blur;
361
+ heatmapBlurInput.value = heatmapOptions.blur;
362
+ maxZoomValueSpan.textContent = heatmapOptions.maxZoom;
363
+ heatmapMaxZoomInput.value = heatmapOptions.maxZoom;
364
+ heatmapRadiusInput.addEventListener('input', (e) => {
365
+ const v = parseFloat(e.target.value);
366
+ heatmapOptions.radius = v;
367
+ radiusValueSpan.textContent = v;
368
+ updateHeatmap();
369
+ });
370
+ heatmapBlurInput.addEventListener('input', (e) => {
371
+ const v = parseFloat(e.target.value);
372
+ heatmapOptions.blur = v;
373
+ blurValueSpan.textContent = v;
374
+ updateHeatmap();
375
+ });
376
+ heatmapMaxZoomInput.addEventListener('input', (e) => {
377
+ const v = parseInt(e.target.value, 10);
378
+ heatmapOptions.maxZoom = v;
379
+ maxZoomValueSpan.textContent = v;
380
+ updateHeatmap();
321
381
  });
322
- console.log("Toggle button listener setup complete.");
382
+ console.log("Heatmap control listeners setup.");
323
383
  }
324
384
 
385
+ function disableHeatmapControls(disabled) {
386
+ if (heatmapRadiusInput) heatmapRadiusInput.disabled = disabled;
387
+ if (heatmapBlurInput) heatmapBlurInput.disabled = disabled;
388
+ if (heatmapMaxZoomInput) heatmapMaxZoomInput.disabled = disabled;
389
+ }
390
+
391
+
392
+ // --- View Switching Logic ---
325
393
  function showCurrentView() {
326
394
  console.log(`Showing view: ${currentView}`);
327
395
  if (!map) {
328
- console.warn("Map not initialized, cannot show view.");
396
+ console.warn("Map not init.");
329
397
  return;
330
398
  }
331
-
332
- // --- Safely remove existing layers ---
333
- console.log("Removing existing layers (if present)...");
334
- if (pinLayer && map.hasLayer(pinLayer)) {
335
- map.removeLayer(pinLayer);
336
- console.log("Removed pin layer");
337
- }
338
- if (heatmapLayer && map.hasLayer(heatmapLayer)) {
339
- map.removeLayer(heatmapLayer);
340
- console.log("Removed heatmap layer");
341
- }
342
- // --- End removal ---
343
-
344
- // --- Add the selected layer ---
399
+ console.log("Removing layers...");
400
+ if (pinLayer && map.hasLayer(pinLayer)) map.removeLayer(pinLayer);
401
+ if (heatmapLayer && map.hasLayer(heatmapLayer)) map.removeLayer(heatmapLayer);
345
402
  console.log(`Adding ${currentView} layer...`);
346
- try {
347
- if (currentView === 'pins' && pinLayer) {
348
- map.addLayer(pinLayer);
349
- console.log("Added pin layer to map.");
350
- } else if (currentView === 'heatmap' && heatmapLayer) {
351
- map.addLayer(heatmapLayer);
352
- console.log("Added heatmap layer to map.");
353
- } else {
354
- console.warn(`Cannot add layer for view "${currentView}": Corresponding layer instance is missing or null.`);
355
- // Maybe display a message if no layers could be shown?
356
- mapElement.innerHTML += '<p style="position: absolute; top: 10px; left: 50px; background: yellow; padding: 5px; z-index: 1000;">No data to display for this view.</p>';
357
- setTimeout(() => { // Clear message after a few seconds
358
- const msgElement = mapElement.querySelector('p[style*="yellow"]');
359
- if (msgElement) msgElement.remove();
360
- }, 3000);
403
+ let layerToAdd = null;
404
+ if (currentView === 'pins') {
405
+ layerToAdd = pinLayer;
406
+ if (heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'none';
407
+ if (clusterToggleButton) {
408
+ clusterToggleButton.style.display = 'inline-block';
409
+ clusterToggleButton.disabled = false;
361
410
  }
362
- } catch (e) {
363
- console.error(`Error adding ${currentView} layer:`, e);
411
+ } else {
412
+ layerToAdd = heatmapLayer;
413
+ if (heatmapOptionsPanel) heatmapOptionsPanel.style.display = 'block';
414
+ if (clusterToggleButton) {
415
+ clusterToggleButton.style.display = 'none';
416
+ clusterToggleButton.disabled = true;
417
+ }
418
+ }
419
+ if (layerToAdd) {
420
+ try {
421
+ map.addLayer(layerToAdd);
422
+ console.log(`Added ${currentView} layer.`);
423
+ } catch (e) {
424
+ console.error(`Error adding ${currentView}:`, e);
425
+ updateStatus(`Error display ${currentView}.`, true);
426
+ }
427
+ } else {
428
+ console.warn(`Layer instance missing for ${currentView}.`);
429
+ updateStatus(`No data for ${currentView}.`, false);
364
430
  }
365
- // --- End adding ---
366
431
  }
367
432
 
368
- function updateButtonText() {
369
- if (!toggleButton) return;
370
- const nextViewText = (currentView === 'pins') ? 'Heatmap' : 'Pin';
371
- toggleButton.textContent = `Switch to ${nextViewText} View`;
372
- console.log(`Button text updated to: ${toggleButton.textContent}`);
433
+
434
+ // --- Button Text Update Functions ---
435
+ function updateViewToggleButtonText() {
436
+ if (!viewToggleButton) return;
437
+ const next = (currentView === 'pins') ? 'Heatmap' : 'Pin';
438
+ viewToggleButton.textContent = `Switch to ${next} View`;
439
+ console.log(`View Btn text: ${viewToggleButton.textContent}`);
440
+ }
441
+
442
+ function updateClusterToggleButtonText() {
443
+ if (!clusterToggleButton) return;
444
+ clusterToggleButton.textContent = isClusteringEnabled ? 'Disable Clustering' : 'Enable Clustering';
445
+ console.log(`Cluster Btn text: ${clusterToggleButton.textContent}`);
373
446
  }
374
447
 
375
- // --- Start ---
448
+
449
+ // --- Start Initialization ---
376
450
  initializeMap();
377
451
 
378
452
  }); // End DOMContentLoaded