This code processes Google Timeline data to identify and categorize destinations by finding nearby geographical locations associated with each timeline entry. It then calculates average coordinates for these destinations and filters out entries that are not relevant.
npm run import -- "Find the average latitute and longitude at each destination"
var importer = require('../Core');
var _ = require('underscore');
var fs = require('fs');
var PROFILE_PATH = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
var PROJECT_PATH = PROFILE_PATH + '/Timeline';
function toRadians(angle) {
return angle * (Math.PI / 180);
}
function straightDistance(lat1, lon1, lat2, lon2) {
var R = 6371e3; // metres
var φ1 = toRadians(lat1);
var φ2 = toRadians(lat2);
var Δφ = toRadians(lat2 - lat1);
var Δλ = toRadians(lon2 - lon1);
var a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
var d = R * c;
return d;
}
function averageDestinations(geoLocations, timelineLocations) {
var destinations = [];
for (const d of timelineLocations) {
if ((d.name + '') == '' || (d.name + '').indexOf('Driving') > -1) {
continue;
}
const nearest = _.sortBy(
geoLocations,
l => Math.abs(new Date(l.time).getTime() - new Date(d.time).getTime()))
.slice(0, 3);
// make sure it isn't off by much
const averageLat = nearest.map(n => n.latitudeE7)
.reduce((a, b) => a + b, 0) / nearest.length / 10000000;
const averageLon = nearest.map(n => n.longitudeE7)
.reduce((a, b) => a + b, 0) / nearest.length / 10000000;
if (nearest.filter(nearby => straightDistance(
nearby.latitudeE7 / 10000000,
nearby.longitudeE7 / 10000000,
averageLat,
averageLon
) < 2000).length > 0) {
destinations.push(Object.assign(d, {
averageLat: averageLat,
averageLon: averageLon,
locations: nearest.map(nearby => Object.assign(nearby, {
averageLat: nearby.latitudeE7 - averageLat,
averageLon: nearby.longitudeE7 - averageLon,
averageDist: straightDistance(
nearby.latitudeE7 / 10000000,
nearby.longitudeE7 / 10000000,
averageLat,
averageLon
)
}))
}));
} else {
console.log('too far! ' + d.name + ' - ' + d.time);
console.log(nearest.map(nearby => straightDistance(
nearby.latitudeE7 / 10000000,
nearby.longitudeE7 / 10000000,
averageLat,
averageLon
)));
}
destinations.push(Object.assign(d, {
averageLat: averageLat,
averageLon: averageLon
}));
}
return destinations;
}
module.exports = averageDestinations;
const { readFileSync } = require('fs');
const _ = require('underscore');
const core = require('../Core');
const PROFILE_PATH = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
const PROJECT_PATH = PROFILE_PATH + '/Timeline';
/**
* Converts degrees to radians.
* @param {number} angle Angle in degrees.
* @returns {number} Angle in radians.
*/
function toRadians(angle) {
return angle * (Math.PI / 180);
}
/**
* Calculates the straight distance between two points on the Earth's surface.
* @param {number} lat1 Latitude of the first point.
* @param {number} lon1 Longitude of the first point.
* @param {number} lat2 Latitude of the second point.
* @param {number} lon2 Longitude of the second point.
* @returns {number} Distance in meters.
*/
function straightDistance(lat1, lon1, lat2, lon2) {
const R = 6371e3; // metres
const φ1 = toRadians(lat1);
const φ2 = toRadians(lat2);
const Δφ = toRadians(lat2 - lat1);
const Δλ = toRadians(lon2 - lon1);
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const d = R * c;
return d;
}
/**
* Calculates the average destination based on the nearest locations.
* @param {object[]} geoLocations Geolocation data.
* @param {object[]} timelineLocations Timeline locations.
* @returns {object[]} Destinations with average locations.
*/
function averageDestinations(geoLocations, timelineLocations) {
const destinations = [];
// Remove driving locations and filter nearby locations
const filteredTimelineLocations = timelineLocations.filter(d => d.name!== '' && d.name.indexOf('Driving') === -1);
for (const d of filteredTimelineLocations) {
const nearest = _.sortBy(
geoLocations,
l => Math.abs(new Date(l.time).getTime() - new Date(d.time).getTime())
).slice(0, 3);
// Check if the nearest locations are not too far
if (!nearest.some(n => straightDistance(
n.latitudeE7 / 10000000,
n.longitudeE7 / 10000000,
nearest[0].latitudeE7 / 10000000,
nearest[0].longitudeE7 / 10000000
) < 2000)) {
console.log(`Too far! ${d.name} - ${d.time}`);
console.log(nearest.map(n => straightDistance(
n.latitudeE7 / 10000000,
n.longitudeE7 / 10000000,
nearest[0].latitudeE7 / 10000000,
nearest[0].longitudeE7 / 10000000
)));
continue;
}
// Calculate average location
const averageLat = nearest.map(n => n.latitudeE7).reduce((a, b) => a + b, 0) / nearest.length / 10000000;
const averageLon = nearest.map(n => n.longitudeE7).reduce((a, b) => a + b, 0) / nearest.length / 10000000;
const locations = nearest.map(n => ({
...n,
averageLat: n.latitudeE7 - averageLat,
averageLon: n.longitudeE7 - averageLon,
averageDist: straightDistance(
n.latitudeE7 / 10000000,
n.longitudeE7 / 10000000,
averageLat,
averageLon
)
}));
destinations.push({
...d,
averageLat,
averageLon,
locations
});
}
return destinations;
}
module.exports = averageDestinations;
This code analyzes Google Timeline data to identify and categorize destinations.
Here's a breakdown:
Imports:
importer
: A custom module likely containing utility functions.underscore
: A library for working with arrays and objects.fs
: For file system operations.Constants:
PROFILE_PATH
: Path to the user's profile directory.PROJECT_PATH
: Path to a project directory within the user's profile.Helper Functions:
toRadians
: Converts degrees to radians.straightDistance
: Calculates the straight-line distance between two points on Earth using the Haversine formula.averageDestinations
Function:
geoLocations
(likely containing geographical data) and timelineLocations
(likely containing data from the Google Timeline).timelineLocations
, filtering out entries that are empty or related to driving.geoLocations
based on time proximity.Overall Purpose: