Commit d2519e2b authored by theopolisme's avatar theopolisme
Browse files

Initial commit

parents
Loading
Loading
Loading
Loading

.gitignore

0 → 100644
+2 −0
Original line number Diff line number Diff line
.DS_Store
node_modules

README.md

0 → 100644
+34 −0
Original line number Diff line number Diff line
# location-history-visualizer

**Available online: [theopolisme.github.io/location-history-visualizer]()**

A tool for visualizing your complete, consolidated, collected Google [Location History](https://google.com/locationhistory).

It works directly in your web browser – no software to download, no packages to install. **Everyone deserves to know what data is being collected about them, without having to fiddle with cryptic pieces of software.**

*location-history-visualizer* takes raw Google Takeout output and produces a heatmap of all of your location data over time, overlaid on an interactive map.

## Packages used
* [leaflet.js](http://leafletjs.com/), for rendering the interactive map
* [leaflet.heat](https://github.com/Leaflet/Leaflet.heat), for rendering the heatmap overlay
* [filestream](https://github.com/DamonOehlman/filestream), for dealing with gigantic Google Takeout files
* [oboe.js](http://oboejs.com), for processing said gigantic files
* [browserify](http://browserify.org/), for helping filestream  work properly in the browser


## License

Copyright (C) 2014 Theopolisme <theopolismewiki@gmail.com>

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

index.css

0 → 100644
+115 −0
Original line number Diff line number Diff line
body {
    padding: 0;
    margin: 0;
    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
    font-size: 14px;
}

html, body, #map {
    height: 100%;
    width: 100%;
}

body.working {
	cursor: progress;
}

.hidden {
	display: none !important;
}

#map {
	z-index: 0;
}

.container {
	display: table;
	position: absolute;
	height: 100%;
	width: 100%;
	z-index: 9999;
}

.map-active .container {
	/* so that clicks go through to the map! */
	pointer-events: none;
}

.content {
	display: table-cell;
	vertical-align: middle;
}

.content-box {
	margin: 0 auto;
	width: 50%;
	min-width: 300px;
	max-width: 500px;
	background-color: rgba(255,255,255,0.9);
	padding: 10px;
	border-radius: 10px;
}

h2 {
	text-align: center
}

.credit {
	font-size: 0.8em;
	text-align: center;
}

#working {
	text-align: center;
}

#done {
	cursor: pointer;
}

/* http://tobiasahlin.com/spinkit/ */

.spinner {
  margin: 20px auto 0;
  width: 70px;
  text-align: center;
}

.spinner > div {
  width: 18px;
  height: 18px;
  background-color: #333;

  border-radius: 100%;
  display: inline-block;
  -webkit-animation: bouncedelay 1.4s infinite ease-in-out;
  animation: bouncedelay 1.4s infinite ease-in-out;
  /* Prevent first frame from flickering when animation starts */
  -webkit-animation-fill-mode: both;
  animation-fill-mode: both;
}

.spinner .bounce1 {
  -webkit-animation-delay: -0.32s;
  animation-delay: -0.32s;
}

.spinner .bounce2 {
  -webkit-animation-delay: -0.16s;
  animation-delay: -0.16s;
}

@-webkit-keyframes bouncedelay {
  0%, 80%, 100% { -webkit-transform: scale(0.0) }
  40% { -webkit-transform: scale(1.0) }
}

@keyframes bouncedelay {
  0%, 80%, 100% { 
    transform: scale(0.0);
    -webkit-transform: scale(0.0);
  } 40% { 
    transform: scale(1.0);
    -webkit-transform: scale(1.0);
  }
}

index.html

0 → 100644
+60 −0
Original line number Diff line number Diff line
<!DOCTYPE html>
<html>
<head>
    <title>location-history-visualizer</title>
    <link rel="stylesheet" href="index.css"/>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.css"/>
</head>
<body>

<div class="container">
<div class="content">

    <!-- Fork me on GitHub -->
    <a href="https://github.com/theopolisme/location-history-visualizer" target="_blank"><img style="position: absolute; top: 0; right: 0; border: 0;" src="https://camo.githubusercontent.com/52760788cde945287fbb584134c4cbc2bc36f904/68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6769746875622f726962626f6e732f666f726b6d655f72696768745f77686974655f6666666666662e706e67" alt="Fork me on GitHub" data-canonical-src="https://s3.amazonaws.com/github/ribbons/forkme_right_white_ffffff.png"></a>

    <!-- Intro, before data has been uploaded -->
    <div id="intro" class="content-box">
        <h2>location-history-visualizer</h2>
        <p>Welcome to <b>location-history-visualizer</b>, a tool for visualizing your collected Google <a href="https://google.com/locationhistory" target="_blank">Location History</a> data. <i>Don't worry&mdash;all processing and visualization happens directly on your computer, so rest assured that nobody is able to access your Location History but you... and Google, of course.</i> ^.^</p>
        <p>To start off, you'll need to go to <a href="https://google.com/takeout" target="_blank">Google Takeout</a> to download your Location History data: on that page, deselect everything except Location History by clicking "Select none" and then reselecting "Location History". Then hit "Next" and, finally, click "Create archive". Once the archive has been created, click "Download". Unzip the downloaded file, and open the "Location History" folder within. <b>Then, drag and drop <i>LocationHistory.json</i> from inside that folder onto this page.</b> Let the visualization begin!</p>
        <p class="credit">A project by <a href="https://github.com/theopolisme" target="_blank">@theopolisme</a>. Made in 2014 in Memphis, Tennessee.</p>
    </div>

    <!-- Shown in interim while processing is in progress -->
    <div id="working" class="content-box hidden">
        <h2>Processing data...</h2>
        <div class="spinner">
            <div class="bounce1"></div>
            <div class="bounce2"></div>
            <div class="bounce3"></div>
        </div>
        <p><span id="currentStatus">Waking up...</span></p>
        <p>This will take a while... sit back, get a cup of tea or something.</p>
        <p><i>Why does it take so long?</i> Depending on how long Google's been tracking your location, you may have hundreds of thousands of [latitude, longitude] pairs, every one of which must be loaded, analyzed, and plotted. That's a lot of dots.</p>
    </div>

    <!-- Content displayed once processing complete -->
    <div id="done" class="content-box hidden">
        <h2>Render complete!</h2>
        <p>Successfully processed <span id="numberProcessed"></span> data points to generate the heatmap. Click inside this box to dismiss it and start exploring...</p>
        <p><i>Zoom</i> by scrolling, double-clicking, or using the buttons in the upper lefthand corner. <i>Navigate</i> by clicking and dragging.</p>
        <p class="credit">A project by <a href="https://github.com/theopolisme" target="_blank">@theopolisme</a>. Made in 2014 in Memphis, Tennessee.</p>
    </div>

</div>
</div>

<div id="map"></div>

<!-- dropzone, outta here! -->
<div id="null" class="hidden"></div>

<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.3/leaflet.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/3.8.4/dropzone.min.js"></script>
<script src="lib/leaflet.heat.min.js"></script>
<script src="lib/stream.js"></script>
<script src="index.js"></script>
</body>
</html>

index.js

0 → 100644
+108 −0
Original line number Diff line number Diff line
( function ( $, L, oboe, FileReadStream ) {
	var map;

	// Start at the beginning
	stageOne();

	////// STAGE 1 - ZE VELCOME UNT ZE UPLOAD //////

	function stageOne () {
		var dropzone;

		// Initialize the map
		map = L.map( 'map' ).setView([0,0], 2);
		L.tileLayer( 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
			attribution: 'location-history-visualizer is open source and available <a href="https://github.com/theopolisme/location-history-visualizer">on GitHub</a>. Map data &copy; <a href="http://openstreetmap.org">OpenStreetMap</a> contributors.',
			maxZoom: 18
		} ).addTo( map );

		// Initialize the dropzone
		dropzone = new Dropzone( document.body, {
			url: '/',
			previewsContainer: '#null',
			clickable: false,
			accept: function ( file, done ) {
				if ( file.type !== 'application/json' ) {
					alert( 'Whoops, make sure you upload a .json file.' );
				} else {
					stageTwo( file );
					dropzone.disable(); // Your job is done, buddy
				}
			}
		} );
	}

	////// STAGE 2 - ZE PROCESSING //////

	function stageTwo ( file ) {
		var heat = L.heatLayer( [], {
				blur: 20
 			} ).addTo( map ),
			SCALAR_E7 = 0.0000001; // Since Google Takeout stores latlngs as integers

		// First, change tabs
		$( 'body' ).addClass( 'working' );
		$( '#intro' ).addClass( 'hidden' );
		$( '#working' ).removeClass( 'hidden' );

		// Now start working!
		processFile( file );

		function status ( message ) {
			$( '#currentStatus' ).text( message );
		}

		function processFile ( file ) {
			var pointNo = 0,
				filestream = new FileReadStream( file );

			status( 'Reading file...' );

			oboe( filestream )
				.on( 'node', {
					'locations.*': function ( location ) {
						// Add the new point... prevent lots of redraws by writing to _latlngs
						pointNo += 1;
						status( 'Adding point #' + pointNo.toLocaleString() + '...' );
						heat._latlngs.push( [ location.latitudeE7 * SCALAR_E7, location.longitudeE7 * SCALAR_E7 ] );
					},
					'locations': function () {
						// Don't need any other data now
						this.abort();
						// Also, trigger the next step :D
						renderMap();
					}
				} )
				.on( 'fail', function () {
					status( 'Something went wrong reading your JSON file. Ensure you\'re uploading a "direct-from-Google" JSON file and try again, or create an issue on GitHub if the problem persists.' );
   				} );

			function renderMap () {
				heat.redraw();
				// Stage 3!
				stageThree( /* numberProcessed */ pointNo );
			}
		}
	}

	////// STAGE 3 - THEY GROW UP SO FAST //////

	function stageThree ( numberProcessed ) {
		var $done = $( '#done' );

		// Change tabs :D
		$( 'body' ).removeClass( 'working' );
		$( '#working' ).addClass( 'hidden' );
		$done.removeClass( 'hidden' );

		// Update count
		$( '#numberProcessed' ).text( numberProcessed.toLocaleString() );

		// Fade away when clicked
		$done.one( 'click', function () {
			$( 'body' ).addClass( 'map-active' );
			$done.fadeOut();
		} );
	}

}( jQuery, L, oboe, FileReadStream ) );