Published
Edited
Mar 4, 2020
1 fork
10 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
JSZip = require("jszip@3/dist/jszip.min.js")

Insert cell
Insert cell
import {vl} from '@vega/vega-lite-api'
Insert cell
Insert cell
files = zip.file(/^Takeout\/Location History\/Semantic Location History\/.*\.json$/)
Insert cell
fileContents = await Promise.all( files.map(f => f.async("string")))
Insert cell
// For each file content, extract activities, then flaten all activities in a single array
activities = fileContents.map(parseActivities).flat()
Insert cell
activities.forEach(co2e)
Insert cell
/**
* Parse a location history
* @param {String} historyStr: a semantic activity location history .json file
* @return the parsed activities
*/
function parseActivities(historyStr) {
const weCareAboutActivityType = function(type) {
return ['FLYING', 'IN_VEHICLE', 'IN_PASSENGER_VEHICLE', 'IN_TAXI'].includes(type);
}
const weCareAboutActivity = function(activitySegment) {
return activitySegment && activitySegment.distance && weCareAboutActivityType(activitySegment.activityType);
}
const activityTypeToTransportationMode = function(type) {
switch(type) {
case 'FLYING':
return 'Air';
case 'IN_TAXI':
case 'IN_PASSENGER_VEHICLE':
case 'IN_VEHICLE':
return 'Road';
default:
return type;
}
}
/*
* This function uses heuristics to potentially inject an activityType if not present.
* E.g. long distance and high average speed should be a flight
*/
const preProcessActivity = function (activitySegment) {
const FLIGHT_MIN_DISTANCE_KM = 500;
const FLIGHT_MIN_AVERAGE_SPEED_KMH = 300;
const FLIGHT_MAX_AVERAGE_SPEED_KMH = 1000;
const CAR_MAX_DISTANCE_KM = 1000;
const CAR_MIN_DISTANCE_KM = 50;
const CAR_MAX_AVERAGE_SPEED_KMH = 130;
const CAR_MIN_AVERAGE_SPEED_KMH = 50;
// we need duration to compute speed.
if( !activitySegment
|| !activitySegment.duration
|| !activitySegment.duration.endTimestampMs
|| !activitySegment.duration.startTimestampMs) {
return;
}
const speedKmH = function(activitySegment) {
const km = activitySegment.distance / 1000;
const h = (activitySegment.duration.endTimestampMs - activitySegment.duration.startTimestampMs) / (1000 * 60 * 60);
return km / h;
}
if(activitySegment.distance > FLIGHT_MIN_DISTANCE_KM * 1000
&& speedKmH(activitySegment) > FLIGHT_MIN_AVERAGE_SPEED_KMH
&& speedKmH(activitySegment) < FLIGHT_MAX_AVERAGE_SPEED_KMH) {
activitySegment.activityType = 'FLYING';
return;
}
if(activitySegment.distance > CAR_MIN_DISTANCE_KM * 1000
&& activitySegment.distance < CAR_MAX_DISTANCE_KM * 1000
&& speedKmH(activitySegment) > CAR_MIN_AVERAGE_SPEED_KMH
&& speedKmH(activitySegment) < CAR_MAX_AVERAGE_SPEED_KMH) {
activitySegment.activityType = 'IN_PASSENGER_VEHICLE';
return;
}
}
const history = JSON.parse(historyStr)
if(!history.timelineObjects) { return; }
let activities = [];
history.timelineObjects.forEach(timelineObject => {
const activitySegment = timelineObject.activitySegment;
preProcessActivity(activitySegment);
if(weCareAboutActivity(activitySegment)) {
const activity = {
id: activitySegment.duration.startTimestampMs,
datetime: new Date(parseInt(activitySegment.duration.startTimestampMs, 10)),
durationHours: (activitySegment.duration.endTimestampMs - activitySegment.duration.startTimestampMs) / (1000 * 60 * 60),
distanceKilometers: activitySegment.distance / 1000,
activityType: 'ACTIVITY_TYPE_TRANSPORTATION',
transportationMode: activityTypeToTransportationMode(activitySegment.activityType),
};
activities.push(activity);
}
// For Debug only
else if(activitySegment
&& !['WALKING', 'IN_SUBWAY', 'CYCLING', 'SAILING', 'BOATING', 'IN_BUS', 'HIKING', 'IN_TRAIN'].includes(activitySegment.activityType)
&& activitySegment.distance > 100 * 1000
&& (activitySegment.duration.endTimestampMs - activitySegment.duration.startTimestampMs) > 1000 * 60 * 60
) {
console.log(activitySegment);
}
})
return activities;
}
Insert cell
/** For a given activity, populate its kCO2e attribute **/
function co2e(activity) {
// Poor man's CO2 model.
// This is copy pasting values from the "Transportation" chart at https://www.tmrow.com/climatechange
// TODO: use better model, import https://github.com/tmrowco/northapp-contrib/tree/master/co2eq
switch(activity.transportationMode) {
case 'Air':
return activity.kCO2e = activity.distanceKilometers * 300 / 1000;
case 'Road':
return activity.kCO2e = activity.distanceKilometers * 183 / 1000;
}
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more