Skip to content
Open
Show file tree
Hide file tree
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
15 changes: 8 additions & 7 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@
<!-- Intro, before data has been uploaded -->
<div id="intro" class="content-box">
<h2>Location History Visualizer</h2>
<div class="note">
<strong>Note:</strong> This tool only generates heatmaps. If you're looking for minute-by-minute analysis of your Location History, including accuracy information, location-based search, advanced filtering and measurement tools, and more, check out <strong><a href="/">Location History Visualizer Pro</a></strong>.
</div>
<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 with heatmaps. <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="fallback">Alternatively, select your <b>LocationHistory.json</b> file directly: <input name="file" type="file" id="file"></input></p>
<p class="credit">An <a target="_blank" href="https://github.com/theopolisme/location-history-visualizer">open source</a> project by the makers of <a href="/">Location History Visualizer Pro</a>.</p>
<p>Welcome to <b>Location History Visualizer</b>, a tool for visualizing your collected Google Timeline data with heatmaps. <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.</i></p>
<p>First download your data:</p>
<ul style="list-style: none; padding: 0;">
<li><strong>On Android:</strong> <span style="color: #34a853;">Phone settings</span> → <span style="color: #34a853;">Location</span> → <span style="color: #34a853;">Location Services</span> → <span style="color: #34a853;">Timeline</span> → <span style="color: #34a853;">Export Timeline data</span></li>
<li><strong>On iOS:</strong> <span style="color: #007aff;">Google Maps app settings</span> → <span style="color: #007aff;">Personal Content</span> → <span style="color: #007aff;">Export Timeline Data</span></li>
</ul>
<p>You should get a file ending in .json which you can upload here: <input name="file" type="file" id="file"></input></p>
<p class="credit"><a target="_blank" href="https://github.com/Lucas-vdr-Horst/location-history-visualizer">This project</a> is just a fix for the newest json format. All actual credit goes to <a target="_blank" href="https://github.com/theopolisme/location-history-visualizer">the original</a>.</p>
</div>

<!-- Shown in interim while processing is in progress -->
Expand Down
96 changes: 63 additions & 33 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,29 +66,62 @@
$( '#intro' ).addClass( 'hidden' );
$( '#working' ).removeClass( 'hidden' );

var SCALAR_E7 = 0.0000001; // Since Google Takeout stores latlngs as integers
var latlngs = [];
function done() {
status( 'Generating map...' );
heat._latlngs = latlngs;

heat.redraw();
stageThree( /* numberProcessed */ latlngs.length );
}

var os = new oboe();

os.node( 'locations.*', function ( location ) {
var latitude = location.latitudeE7 * SCALAR_E7,
longitude = location.longitudeE7 * SCALAR_E7;
os.node('semanticSegments.*.timelinePath.*', function (entry) {
if (!entry.point) return oboe.drop; // Ensure the field exists

// Extract latitude and longitude from the 'point' string
var match = entry.point.match(/([-+]?[0-9]*\.?[0-9]+)°,\s*([-+]?[0-9]*\.?[0-9]+)°/);

if (match) {
var latitude = parseFloat(match[1]);
var longitude = parseFloat(match[2]);

if (type === 'json' && !isNaN(latitude) && !isNaN(longitude)) {
latlngs.push([latitude, longitude]);
}
}

return oboe.drop;
} ).done(done);

// Handle negative latlngs due to google unsigned/signed integer bug.
if ( latitude > 180 ) latitude = latitude - (2 ** 32) * SCALAR_E7;
if ( longitude > 180 ) longitude = longitude - (2 ** 32) * SCALAR_E7;
os.node('*.activity', function (entry) {
if (!entry.start || !entry.end) return oboe.drop; // Ensure the fields exist

if ( type === 'json' ) latlngs.push( [ latitude, longitude ] );
return oboe.drop;
} ).done( function () {
status( 'Generating map...' );
heat._latlngs = latlngs;
// Extract latitude and longitude from the 'start' and 'end' strings
var startMatch = entry.start.match(/geo:([-+]?[0-9]*\.?[0-9]+),([-+]?[0-9]*\.?[0-9]+)/);
var endMatch = entry.end.match(/geo:([-+]?[0-9]*\.?[0-9]+),([-+]?[0-9]*\.?[0-9]+)/);

heat.redraw();
stageThree( /* numberProcessed */ latlngs.length );
if (startMatch) {
var startLatitude = parseFloat(startMatch[1]);
var startLongitude = parseFloat(startMatch[2]);

} );
if (!isNaN(startLatitude) && !isNaN(startLongitude)) {
latlngs.push([startLatitude, startLongitude]);
}
}

if (endMatch) {
var endLatitude = parseFloat(endMatch[1]);
var endLongitude = parseFloat(endMatch[2]);

if (!isNaN(endLatitude) && !isNaN(endLongitude)) {
latlngs.push([endLatitude, endLongitude]);
}
}

return oboe.drop;
}).done(done);

var fileSize = prettySize( file.size );

Expand Down Expand Up @@ -182,35 +215,32 @@
var prettyFileSize = prettySize(fileSize);
var chunkSize = 512 * 1024; // bytes
var offset = 0;
var self = this; // we need a reference to the current object
var chunkReaderBlock = null;
var startTime = Date.now();
var endTime = Date.now();
var readEventHandler = function ( evt ) {
if ( evt.target.error == null ) {
offset += evt.target.result.length;
var chunk = evt.target.result;
var percentLoaded = ( 100 * offset / fileSize ).toFixed( 0 );
status( percentLoaded + '% of ' + prettyFileSize + ' loaded...' );
oboeInstance.emit( 'data', chunk ); // callback for handling read chunk
var decoder = new TextDecoder("utf-8");

var readEventHandler = function (evt) {
if (evt.target.error == null) {
offset += evt.target.result.byteLength;
var chunk = decoder.decode(evt.target.result, { stream: true }); // Preserve multi-byte characters
var percentLoaded = (100 * offset / fileSize).toFixed(0);
status(percentLoaded + "% of " + prettyFileSize + " loaded...");
oboeInstance.emit("data", chunk);
} else {
return;
}
if ( offset >= fileSize ) {
oboeInstance.emit( 'done' );
return;
}
chunkReaderBlock(offset, chunkSize, file);
};

// of to the next chunk
chunkReaderBlock( offset, chunkSize, file );
}

chunkReaderBlock = function ( _offset, length, _file ) {
chunkReaderBlock = function (_offset, length, _file) {
var r = new FileReader();
var blob = _file.slice( _offset, length + _offset );
var blob = _file.slice(_offset, length + _offset);
r.onload = readEventHandler;
r.readAsText( blob );
}
r.readAsArrayBuffer(blob); // Use ArrayBuffer instead of readAsText
};

// now let's start the read with the first block
chunkReaderBlock( offset, chunkSize, file );
Expand Down