diff --git a/index.html b/index.html index d0d11da..d90b9d1 100644 --- a/index.html +++ b/index.html @@ -90,6 +90,10 @@


+
+
+
+
@@ -181,93 +185,127 @@ markers = L.layerGroup().addTo(map); // Create group of markers - function parseInput(rawInput) { - // Try to parse JSON: - let data = false; - try { - data = JSON.parse(rawInput); - } catch (e) { - alert("Invalid data. The data must be in JSON format."); - console.error(e); - return; - } + function parseInput(rawInput) { + // Try to parse JSON: + let data = false; + try { + data = JSON.parse(rawInput); + } catch (e) { + alert("Invalid data. The data must be in JSON format."); + console.error(e); + return; + } - // With valid JSON, attempt to load waypoints and create list. - if (typeof data.timelineObjects == "undefined") { - alert("Invalid data: must contain 'timelineObjects' array."); - return; - } + // With valid JSON, attempt to load waypoints and create list. + // Support both old timelineObjects format and new semanticSegments format + let segments = []; + if (typeof data.timelineObjects != "undefined") { + // Old format: timelineObjects with placeVisit + segments = data.timelineObjects.filter(d => typeof d.placeVisit != "undefined"); + } else if (typeof data.semanticSegments != "undefined") { + // New format: semanticSegments with visit + segments = data.semanticSegments.filter(d => typeof d.visit != "undefined"); + } else { + alert("Invalid data: must contain 'timelineObjects' array or 'semanticSegments' array."); + return; + } + + // Iterate all segments, adding location data + for (let d of segments) { + let place, startTime, endTime, address, lat, lng; + + if (d.placeVisit) { + // Old format + place = d.placeVisit; + address = place.location.address; + lat = place.location.latitudeE7 / 10000000.0; + lng = place.location.longitudeE7 / 10000000.0; + startTime = place.duration.startTimestamp; + endTime = place.duration.endTimestamp; + } else if (d.visit) { + // New format + place = d.visit; + const candidate = place.topCandidate; + address = candidate.semanticType || "Unknown Location"; + + // Parse latLng string like "45.330919°, -74.0003116°" + const latLngMatch = candidate.placeLocation.latLng.match(/([-\d.]+)°,\s*([-\d.]+)°/); + if (latLngMatch) { + lat = parseFloat(latLngMatch[1]); + lng = parseFloat(latLngMatch[2]); + } else { + console.warn("Could not parse latLng:", candidate.placeLocation.latLng); + continue; // Skip this entry + } + + startTime = d.startTime; + endTime = d.endTime; + } else { + continue; // Skip invalid entries + } - // Iterate all objects inside timelineObjects, adding all 'placeVisit' objects as locations. - for (let d of data.timelineObjects) { - if (typeof d.placeVisit == "undefined") { continue; } // Skip this object. - const place = d.placeVisit; - - // Load the object as a location: - let l = $("#lstLocations .location.template").clone(true); - $("#lstLocations").append(l); - - l.removeClass('template'); - l.prop('locationData', place); // Save the data structure with the object. - l.find('.location-name').text(place.location.address); - const start = moment(place.duration.startTimestamp); - const end = moment(place.duration.endTimestamp); - - l.find('.arrived').text(start.format('Y-M-D H:m:s')); - l.find('.departed').text(end.format('Y-M-D H:m:s')); - l.find('.duration').text(end.diff(start, 'minutes') + " min"); - l.find('.longitude').text(place.location.longitudeE7 / 10000000.0); - l.find('.latitude').text(place.location.latitudeE7 / 10000000.0); - - // Add the placeholder to the map - let m = L.marker( - [place.location.latitudeE7 / 10000000.0, place.location.longitudeE7 / 10000000.0], - { title: place.location.address } - ) - .addTo(markers); - - // Add popup to marker: - m.bindPopup(place.location.address); - - // On click, highlight the location. - m.on('click', () => { - $("#lstLocations .location").removeClass('highlighted'); - l.addClass('highlighted'); - l[0].scrollIntoView({ - behavior: 'smooth', // You can use 'auto' for instant scrolling - block: 'center', // You can use 'center' or 'end' to control the alignment - inline: 'nearest' // You can use 'start' or 'end' to control horizontal alignment + // Load the object as a location: + let l = $("#lstLocations .location.template").clone(true); + $("#lstLocations").append(l); + + l.removeClass('template'); + l.prop('locationData', place); // Save the data structure with the object. + l.find('.location-name').text(address); + const start = moment(startTime); + const end = moment(endTime); + + l.find('.arrived').text(start.format('YYYY-MM-DD H:mm:ss')); + l.find('.departed').text(end.format('YYYY-MM-DD H:mm:ss')); + l.find('.duration').text(end.diff(start, 'minutes') + " min"); + l.find('.longitude').text(lng); + l.find('.latitude').text(lat); + + // Add the placeholder to the map + let m = L.marker([lat, lng], { title: address }).addTo(markers); + + // Add popup to marker: + m.bindPopup(address); + + // On click, highlight the location. + m.on('click', () => { + $("#lstLocations .location").removeClass('highlighted'); + l.addClass('highlighted'); + l[0].scrollIntoView({ + behavior: 'smooth', // You can use 'auto' for instant scrolling + block: 'center', // You can use 'center' or 'end' to control the alignment + inline: 'nearest' // You can use 'start' or 'end' to control horizontal alignment + }); }); - }); - l.prop('marker', m); // Save pointer to marker + l.prop('marker', m); // Save pointer to marker + l.data('filtered', true); // Default to filtered in - // If list item is clicked, scroll to the marker and open its tooltip. - l.on('click', () => { - map.flyTo(m.getLatLng()); - m.openPopup(); - $("#lstLocations .location").removeClass('highlighted'); - l.addClass('highlighted'); - }); - } + // If list item is clicked, scroll to the marker and open its tooltip. + l.on('click', () => { + map.flyTo(m.getLatLng()); + m.openPopup(); + $("#lstLocations .location").removeClass('highlighted'); + l.addClass('highlighted'); + }); + } - if ($("#chkConnectSequentially").is(":checked")) { - connectSequentially(); + if ($("#chkConnectSequentially").is(":checked")) { + connectSequentially(); + } } - } function toggleLocationList() { $("#locationListContainer").toggle(); } function selectAll(flag) { - $("#lstLocations .location").not('.template').each(function() { + $("#lstLocations .location").not('.template').filter(function(){ return $(this).data('filtered'); }).each(function() { $(this).find('.selected').prop('checked', flag); }); } function invertSelection() { - $("#lstLocations .location").not('.template').each(function() { + $("#lstLocations .location").not('.template').filter(function(){ return $(this).data('filtered'); }).each(function() { let el = $(this).find('.selected'); el.prop('checked', !el.is(':checked')); }); @@ -286,7 +324,7 @@ results += `\n`; // Iterate location list, extracting all selected items. - $(".locations .location").not('.template').each(function() { + $(".locations .location").not('.template').filter(function(){ return $(this).data('filtered'); }).each(function() { let el = $(this); if (el.find('.selected').is(':checked')) { results += `"${el.find('.location-name').text()}","${el.find('.arrived').text()}","${el.find('.departed').text()}","${el.find('.duration').text()}","${el.find('.description').val()}"`; @@ -298,7 +336,7 @@ }); } else if (format === "gpx") { results += `\n\n`; - $(".locations .location").not('.template').each(function() { + $(".locations .location").not('.template').filter(function(){ return $(this).data('filtered'); }).each(function() { let el = $(this); if (el.find('.selected').is(':checked')) { results += `\n`; @@ -310,7 +348,7 @@ results += ``; } else if (format === "kml") { results += `\n\n\n`; - $(".locations .location").not('.template').each(function() { + $(".locations .location").not('.template').filter(function(){ return $(this).data('filtered'); }).each(function() { let el = $(this); if (el.find('.selected').is(':checked')) { results += `\n`; @@ -336,7 +374,9 @@ function connectSequentially() { clearConnections(); // Clear any existing connections - let locations = $(".locations .location").not('.template').toArray().map(el => { + let locations = $(".locations .location").not('.template').filter(function(){ + return $(this).data('filtered') !== false; + }).toArray().map(el => { let $el = $(el); return { lat: parseFloat($el.find('.latitude').text()), @@ -406,8 +446,42 @@ $("#chkDisableMarkers").on('change', function() { toggleMarkers(this.checked); // Toggle marker view based on checkbox state }); + + function applyFilter() { + let from = $('#dateFrom').val(); + let to = $('#dateTo').val(); + $('.location').not('.template').each(function(){ + let arrived = $(this).find('.arrived').text(); + let date = parseArrived(arrived); + let show = (!from || date >= from) && (!to || date <= to); + $(this).data('filtered', show); + $(this).toggle(show); + let marker = $(this).prop('marker'); + marker.setOpacity(show ? 1 : 0); + }); + clearConnections(); + if ($("#chkConnectSequentially").is(":checked")) { + connectSequentially(); + } + } + + function clearFilter() { + $('#dateFrom').val(''); + $('#dateTo').val(''); + applyFilter(); + } + + $('#btnFilter').on('click', applyFilter); + $('#btnClearFilter').on('click', clearFilter); + + function parseArrived(arrived) { + return arrived.split(' ')[0]; + } + + $(document).ready(() => { + $('#dateFrom').val(''); + $('#dateTo').val(''); + }); - -