Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
169 changes: 100 additions & 69 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -181,80 +181,113 @@

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 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
// 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
}

// 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('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(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

// 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();
Expand Down Expand Up @@ -409,5 +442,3 @@
</script>
</body>
</html>