google timeline | Read single google timeline page | Reconcile timeline data with calendar | Search

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.

Run example

npm run import -- "Find the average latitute and longitude at each destination"

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;

What the code could have been:

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:

  1. Imports:

  2. Constants:

  3. Helper Functions:

  4. averageDestinations Function:

  5. Overall Purpose: