Skip to content

Commit dff22f0

Browse files
committed
Fix undefined coordinates in map rendering
Fix indefinite spin Signed-off-by: JeffMboya <jangina.mboya@gmail.com>
1 parent b1e0fa2 commit dff22f0

File tree

2 files changed

+152
-46
lines changed

2 files changed

+152
-46
lines changed

telemetry.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ import (
1313
type Telemetry struct {
1414
Services pq.StringArray `json:"services,omitempty" db:"services"`
1515
Service string `json:"service,omitempty" db:"service"`
16-
Longitude float64 `json:"longitude,omitempty" db:"longitude"`
17-
Latitude float64 `json:"latitude,omitempty" db:"latitude"`
16+
Longitude float64 `json:"longitude" db:"longitude"`
17+
Latitude float64 `json:"latitude" db:"latitude"`
1818
IpAddress string `json:"-" db:"ip_address"`
1919
MacAddress string `json:"-" db:"mac_address"`
2020
Version string `json:"magistrala_version,omitempty" db:"mg_version"`

web/static/app.js

Lines changed: 150 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,13 @@ function getCountryCoordinates(country, callback) {
253253
})
254254
.then(function (data) {
255255
if (data.length > 0) {
256-
var lat = parseFloat(data[0].lat);
257-
var lng = parseFloat(data[0].lon);
258-
callback(lat, lng);
256+
try {
257+
const context = `country lookup for "${country}"`;
258+
const coords = assertValidLatLng(data[0].lat, data[0].lon, context);
259+
callback(coords.lat, coords.lng);
260+
} catch (validationError) {
261+
console.error(validationError.message);
262+
}
259263
} else {
260264
console.log("Coordinates not found for country: " + country);
261265
}
@@ -265,77 +269,179 @@ function getCountryCoordinates(country, callback) {
265269
});
266270
}
267271

272+
function assertValidLatLng(latRaw, lngRaw, context = "record") {
273+
if (latRaw === undefined || latRaw === null) {
274+
throw new Error(`Invalid coordinate in ${context}: latitude is ${latRaw}. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
275+
}
276+
if (lngRaw === undefined || lngRaw === null) {
277+
throw new Error(`Invalid coordinate in ${context}: longitude is ${lngRaw}. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
278+
}
279+
280+
const latNum = Number(latRaw);
281+
const lngNum = Number(lngRaw);
282+
283+
if (isNaN(latNum)) {
284+
throw new Error(`Invalid coordinate in ${context}: latitude "${latRaw}" cannot be parsed as number. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
285+
}
286+
if (isNaN(lngNum)) {
287+
throw new Error(`Invalid coordinate in ${context}: longitude "${lngRaw}" cannot be parsed as number. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
288+
}
289+
290+
if (!isFinite(latNum)) {
291+
throw new Error(`Invalid coordinate in ${context}: latitude ${latNum} is not finite. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
292+
}
293+
if (!isFinite(lngNum)) {
294+
throw new Error(`Invalid coordinate in ${context}: longitude ${lngNum} is not finite. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
295+
}
296+
297+
if (latNum < -90 || latNum > 90) {
298+
throw new Error(`Invalid coordinate in ${context}: latitude ${latNum} is outside valid range [-90, 90]. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
299+
}
300+
if (lngNum < -180 || lngNum > 180) {
301+
throw new Error(`Invalid coordinate in ${context}: longitude ${lngNum} is outside valid range [-180, 180]. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
302+
}
303+
304+
if (latNum === 0 && lngNum === 0) {
305+
throw new Error(`Invalid coordinate in ${context}: coordinates (0, 0) are likely invalid. Source: ${JSON.stringify({lat: latRaw, lng: lngRaw})}`);
306+
}
307+
308+
return { lat: latNum, lng: lngNum };
309+
}
310+
268311
async function logJSONData() {
269312
// Get map data from global variable injected by Go template
313+
const spinner = document.getElementById("spinner-overlay");
314+
270315
if (!window.MAP_DATA) {
271316
console.error("MAP_DATA not found. Check if the Go template is rendering correctly.");
317+
if (spinner) {
318+
spinner.style.display = "none";
319+
}
272320
return;
273321
}
274322

323+
const safetyTimeout = setTimeout(() => {
324+
console.warn("Safety timeout: forcing spinner to hide");
325+
if (spinner) {
326+
spinner.style.display = "none";
327+
}
328+
}, 5000);
329+
275330
try {
276331
const obj = JSON.parse(window.MAP_DATA);
277332
const telemetryData = obj.Telemetry || [];
333+
334+
console.log("Processing", telemetryData.length, "telemetry entries");
278335

279336
// Show spinner while loading markers
280-
const spinner = document.getElementById("spinner-overlay");
281337
if (spinner && telemetryData.length > 0) {
282338
spinner.style.display = "flex";
283339
}
284340

285341
// Process markers in batches to avoid blocking the UI
286-
const batchSize = 100;
342+
const batchSize = 50;
343+
let processedCount = 0;
344+
287345
for (let i = 0; i < telemetryData.length; i += batchSize) {
288346
const batch = telemetryData.slice(i, i + batchSize);
289347

290-
// Process batch asynchronously
348+
// Process batch asynchronously with proper yielding
291349
await new Promise(resolve => {
292-
requestAnimationFrame(() => {
293-
batch.forEach((tel) => {
294-
const last_seen = new Date(tel.last_seen);
295-
const marker = L.circle([tel.latitude, tel.longitude], {
296-
radius: 1000,
297-
}).bindPopup(
298-
`<h3>Deployment details</h3>
299-
<p style="font-size: 12px;">version:\t${
300-
tel.magistrala_version
301-
}</p>
302-
<p style="font-size: 12px;">last seen:\t${last_seen}</p>
303-
<p style="font-size: 12px;">country:\t${
304-
tel.country
305-
}</p>
306-
<p style="font-size: 12px;">city:\t${tel.city}</p>
307-
<p style="font-size: 12px;">Services:\t${tel.services.join(
308-
", "
309-
)}</p>`
310-
);
311-
312-
allMarkers.push({
313-
marker: marker,
314-
data: tel
350+
setTimeout(() => {
351+
try {
352+
requestAnimationFrame(() => {
353+
try {
354+
batch.forEach((tel) => {
355+
if (!tel || typeof tel !== 'object') {
356+
return;
357+
}
358+
359+
if (!('latitude' in tel) || !('longitude' in tel)) {
360+
const context = `telemetry entry (ip: ${tel.ip_address || 'unknown'}, country: ${tel.country || 'unknown'})`;
361+
console.error(`Missing coordinate properties in ${context}. Available keys: ${Object.keys(tel).join(', ')}`);
362+
return;
363+
}
364+
365+
let coords;
366+
try {
367+
const context = `telemetry entry (ip: ${tel.ip_address || 'unknown'}, country: ${tel.country || 'unknown'})`;
368+
coords = assertValidLatLng(tel.latitude, tel.longitude, context);
369+
} catch (validationError) {
370+
console.error(validationError.message);
371+
return;
372+
}
373+
374+
const last_seen = new Date(tel.last_seen);
375+
const marker = L.circle([coords.lat, coords.lng], {
376+
radius: 1000,
377+
}).bindPopup(
378+
`<h3>Deployment details</h3>
379+
<p style="font-size: 12px;">version:\t${
380+
tel.magistrala_version || "unknown"
381+
}</p>
382+
<p style="font-size: 12px;">last seen:\t${last_seen}</p>
383+
<p style="font-size: 12px;">country:\t${
384+
tel.country || "-"
385+
}</p>
386+
<p style="font-size: 12px;">city:\t${tel.city || "-"}</p>
387+
<p style="font-size: 12px;">Services:\t${(tel.services || []).join(
388+
", "
389+
)}</p>`
390+
);
391+
392+
allMarkers.push({
393+
marker: marker,
394+
data: tel
395+
});
396+
processedCount++;
397+
});
398+
} catch (err) {
399+
console.error("Error processing batch:", err);
400+
}
401+
resolve();
315402
});
316-
});
317-
resolve();
318-
});
403+
} catch (err) {
404+
console.error("Error in requestAnimationFrame:", err);
405+
resolve();
406+
}
407+
}, 10);
319408
});
320409
}
321410

322-
// Add all markers to the map and apply filters
323-
map.addLayer(markerClusterGroup);
324-
filterMarkers({});
411+
console.log("Processed", processedCount, "markers, adding to map...");
325412

326-
// Fit map bounds to show all markers
327-
if (allMarkers.length > 0) {
328-
const bounds = L.latLngBounds(allMarkers.map(item => [item.data.latitude, item.data.longitude]));
329-
map.fitBounds(bounds, { padding: [50, 50] });
413+
try {
414+
allMarkers.forEach(function(item) {
415+
markerClusterGroup.addLayer(item.marker);
416+
});
417+
418+
map.addLayer(markerClusterGroup);
419+
console.log("Markers added to map, hiding spinner...");
420+
421+
clearTimeout(safetyTimeout);
422+
if (spinner) {
423+
spinner.style.display = "none";
424+
console.log("Spinner hidden");
425+
}
426+
427+
setTimeout(() => {
428+
try {
429+
filterMarkers({});
430+
} catch (err) {
431+
console.error("Error filtering markers:", err);
432+
}
433+
}, 100);
434+
} catch (err) {
435+
console.error("Error adding markers to map:", err);
436+
clearTimeout(safetyTimeout);
437+
if (spinner) {
438+
spinner.style.display = "none";
439+
}
330440
}
331441

332-
// Hide spinner
333-
if (spinner) {
334-
spinner.style.display = "none";
335-
}
336442
} catch (error) {
337443
console.error("Error loading map data:", error);
338-
const spinner = document.getElementById("spinner-overlay");
444+
clearTimeout(safetyTimeout);
339445
if (spinner) {
340446
spinner.style.display = "none";
341447
}

0 commit comments

Comments
 (0)