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.
- {pretix_map-0.1.2.dist-info → pretix_map-0.1.4.dist-info}/METADATA +1 -1
- {pretix_map-0.1.2.dist-info → pretix_map-0.1.4.dist-info}/RECORD +13 -12
- pretix_mapplugin/__init__.py +1 -1
- pretix_mapplugin/management/commands/geocode_existing_orders.py +153 -107
- pretix_mapplugin/migrations/0002_remove_ordergeocodedata_geocoded_timestamp_and_more.py +32 -0
- pretix_mapplugin/models.py +30 -10
- pretix_mapplugin/static/pretix_mapplugin/css/salesmap.css +40 -7
- pretix_mapplugin/static/pretix_mapplugin/js/salesmap.js +299 -225
- pretix_mapplugin/templates/pretix_mapplugin/map_page.html +68 -25
- {pretix_map-0.1.2.dist-info → pretix_map-0.1.4.dist-info}/WHEEL +0 -0
- {pretix_map-0.1.2.dist-info → pretix_map-0.1.4.dist-info}/entry_points.txt +0 -0
- {pretix_map-0.1.2.dist-info → pretix_map-0.1.4.dist-info}/licenses/LICENSE +0 -0
- {pretix_map-0.1.2.dist-info → pretix_map-0.1.4.dist-info}/top_level.txt +0 -0
@@ -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 (
|
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
|
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';
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
// ---
|
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
|
30
|
-
|
31
|
-
|
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' : '';
|
61
|
+
p.className = isError ? 'text-danger' : '';
|
38
62
|
}
|
39
|
-
statusOverlay.style.display = 'flex';
|
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';
|
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
|
58
|
-
return;
|
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;
|
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);
|
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
|
-
|
95
|
-
if (
|
96
|
-
|
97
|
-
|
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...");
|
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)
|
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
|
136
|
-
updateStatus("No valid
|
137
|
-
|
138
|
-
|
139
|
-
return;
|
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
|
143
|
-
updateStatus("No
|
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;
|
155
|
+
coordinateData = data.locations;
|
151
156
|
console.log(`Received ${coordinateData.length} coordinates.`);
|
152
157
|
|
153
|
-
|
154
|
-
|
158
|
+
if (viewToggleButton) viewToggleButton.disabled = false;
|
159
|
+
if (clusterToggleButton) clusterToggleButton.disabled = (currentView !== 'pins');
|
160
|
+
disableHeatmapControls(false);
|
155
161
|
|
156
|
-
|
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
|
-
|
170
|
-
if (map &&
|
169
|
+
const container = document.getElementById(mapContainerId);
|
170
|
+
if (map && container && container.offsetWidth > 0) {
|
171
171
|
map.invalidateSize();
|
172
172
|
} else {
|
173
|
-
console.warn(
|
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
|
180
|
-
|
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
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
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
|
-
|
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
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
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
|
-
|
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
|
209
|
+
console.error(`Error creating marker ${index}:`, e);
|
234
210
|
}
|
235
211
|
});
|
236
|
-
|
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
|
-
|
240
|
-
console.log("Creating heatmap layer
|
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
|
252
|
-
} else {
|
253
|
-
|
242
|
+
console.log("Heatmap created:", heatmapOptions);
|
243
|
+
} else {
|
244
|
+
console.warn("No valid points for heatmap.");
|
254
245
|
}
|
255
|
-
} catch (e) {
|
256
|
-
|
246
|
+
} catch (e) {
|
247
|
+
console.error("Error creating heatmap:", e);
|
257
248
|
}
|
249
|
+
}
|
258
250
|
|
259
|
-
|
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
|
-
|
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
|
-
|
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
|
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("
|
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
|
-
//
|
304
|
+
// Apply bounds if valid
|
292
305
|
if (bounds && bounds.isValid()) {
|
293
|
-
console.log("Fitting map to
|
294
|
-
map.fitBounds(bounds, {padding: [50, 50]});
|
306
|
+
console.log("Fitting map to bounds...");
|
307
|
+
map.fitBounds(bounds, {padding: [50, 50]});
|
295
308
|
console.log("Bounds fitted.");
|
296
|
-
|
297
|
-
|
298
|
-
console.log("
|
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
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
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
|
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
|
-
// ---
|
314
|
-
function
|
315
|
-
|
316
|
-
|
317
|
-
console.log("
|
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();
|
320
|
-
|
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("
|
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
|
396
|
+
console.warn("Map not init.");
|
329
397
|
return;
|
330
398
|
}
|
331
|
-
|
332
|
-
|
333
|
-
|
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
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
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
|
-
}
|
363
|
-
|
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
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
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
|
-
|
448
|
+
|
449
|
+
// --- Start Initialization ---
|
376
450
|
initializeMap();
|
377
451
|
|
378
452
|
}); // End DOMContentLoaded
|