spash
Version:
Universal spacetime locator. Maps every location in the observable universe to a string.
295 lines (285 loc) • 10.2 kB
HTML
<meta charset=utf-8><title> Spacetime Locator </title>
<style>
html { max-width: 12cm; margin: auto; }
.centered { text-align: center; font-weight: bold; font-family: monospace; }
.logo { position: relative; top: 5px; }
.clickable { cursor: pointer; }
#spashNode { font-size: 3em; }
#googleMapNode { height: 400px; width: 100%; margin-top: 15px; display: none; }
footer { font-size: 0.9em; }
</style>
<h1> Spash: Spacetime Locator </h1>
<p class=centered>
<a href='https://github.com/espadrine/spash'>
<svg class=logo width="20" height="20" viewBox="12 12 40 40">
<path d="M32,13.4c-10.5,0-19,8.5-19,19c0,8.4,5.5,15.5,13,18c1,0.2,1.3-0.4,1.3-0.9c0-0.5,0-1.7,0-3.2 c-5.3,1.1-6.4-2.6-6.4-2.6C20,41.6,18.8,41,18.8,41c-1.7-1.2,0.1-1.1,0.1-1.1c1.9,0.1,2.9,2,2.9,2c1.7,2.9,4.5,2.1,5.5,1.6 c0.2-1.2,0.7-2.1,1.2-2.6c-4.2-0.5-8.7-2.1-8.7-9.4c0-2.1,0.7-3.7,2-5.1c-0.2-0.5-0.8-2.4,0.2-5c0,0,1.6-0.5,5.2,2 c1.5-0.4,3.1-0.7,4.8-0.7c1.6,0,3.3,0.2,4.7,0.7c3.6-2.4,5.2-2,5.2-2c1,2.6,0.4,4.6,0.2,5c1.2,1.3,2,3,2,5.1c0,7.3-4.5,8.9-8.7,9.4 c0.7,0.6,1.3,1.7,1.3,3.5c0,2.6,0,4.6,0,5.2c0,0.5,0.4,1.1,1.3,0.9c7.5-2.6,13-9.7,13-18.1C51,21.9,42.5,13.4,32,13.4z"/>
</svg></a>
<code class=selectable>npm install spash</code>
</p>
<p>
Every day, humanity goes more vertical. Petty constraints like gravity are
defied by <em>planes, kilometric skyscrapers, ambitious space programs</em>.
<a href="http://iss.astroviewer.net">The ISS</a> peacefully maintains
our orbital presence on the planet, and Elon Musk is already planning
<a href="https://en.wikipedia.org/wiki/Interplanetary_Transport_System">
a Martian settlement</a>.
</p>
<p>
Your place in the universe gets more important by the second. Here is its
identifier:
</p>
<section class=centered>
<output id=spashNode>E.ek0uvhuQQ1q</output><br>
<input type=button id=addDigitNode value='+'>
<input type=button id=rmDigitNode value='-'>
<input type=button id=editNode value='Edit'>
<label><input type=checkbox id=showTimeNode> Show time</label>
<label><input type=checkbox id=showMapNode> Show map</label>
</section>
<div id=googleMapNode></div>
<p id=geolocNode></p>
<p id=precisionNode></p>
<p id=noGeolocNode>
Your device does not allow access to geolocation data. Using a default location.
</p>
<p id=warningsNode></p>
<footer>
<p>
Full <a href=https://github.com/espadrine/spash/blob/master/Readme.md>
specification</a>.
Inspired by <a href=https://en.wikipedia.org/wiki/Geohash>Geohash</a>.
Licensed <a href=https://creativecommons.org/publicdomain/zero/1.0/>CC0</a>.
Built by <a href=https://twitter.com/espadrine>Thaddée Tyl</a>.
</p>
</footer>
<script src=./spash.js></script>
<script>
var geoloc = {
coords: {
latitude: 51.5221656,
longitude: -0.1086239,
altitude: 22.725,
accuracy: 2,
altitudeAccuracy: 1e-9,
},
timestamp: Date.now(),
};
var geoSpash;
var digits = 11;
var setGeoloc = function(geo) {
geoloc = geo;
geoSpash = spash.encode(geo, digits, 6);
var hash = geoSpash.iau + '.' + geoSpash.geolocHash;
if (showTimeHash) { hash += '.' + geoSpash.timeHash; }
spashNode.value = hash;
location.hash = hash;
showGeoloc();
accuracyWarnings(geoSpash, geo);
};
var gotGeoloc = function(geo) {
noGeolocNode.style.display = 'none';
setGeoloc(geo);
};
var noGeolocWarning = function() {
noGeolocNode.style.display = 'block';
setGeoloc(geoloc);
};
if (!location.hash) {
navigator.geolocation.getCurrentPosition(gotGeoloc, noGeolocWarning, {
enableHighAccuracy: true,
timeout: 25000,
maximumAge: 0
});
} else {
// The address bar already holds a hash.
addEventListener('DOMContentLoaded', function() {
updateSpash(location.hash);
});
}
var mapLink = function(geoloc, digits) {
return "https://www.google.fr/maps/@" + geoloc.coords.latitude +
"," + geoloc.coords.longitude + "," + getZoomFromDigits(digits) + "z";
};
var showGeoloc = function() {
var values = [
'<a href="' + mapLink(geoloc, digits) + '">Map</a>',
'<span class="clickable" onclick="changeLatitude(event)">' +
'Latitude</span>: ' + geoloc.coords.latitude + '°' +
'</span>',
'<span class="clickable" onclick="changeLongitude(event)">' +
'Longitude</span>: ' + geoloc.coords.longitude + '°' +
'</span>',
'<span class="clickable" onclick="changeAltitude(event)">' +
'Altitude</span>: ' + geoloc.coords.altitude + ' metres' +
'</span>',
];
if (showTimeHash) {
values.push('<span class="clickable" onclick="changeTime(event)">' +
'Time</span>: ' + new Date(geoloc.timestamp).toISOString());
}
geolocNode.innerHTML = values.join('<br>');
};
var accuracyWarnings = function(geoSpash, geo) {
var accuracies = [
'± ' + geoSpash.earthLatError + ' metres (latitude)',
'± ' + geoSpash.earthLongError + ' metres (longitude)',
'± ' + geoSpash.altError + ' metres (altitude)',
];
if (showTimeHash) {
accuracies.push('± ' + geoSpash.timeError + ' seconds (time)');
}
precisionNode.innerHTML = accuracies.join('<br>');
var geoAccuracy = geo.coords.accuracy || Infinity; // meters
var altAccuracy = (geo.coords.altitudeAccuracy) || Infinity;
var warn = [];
if (geoAccuracy > geoSpash.earthLongError ||
geoAccuracy > geoSpash.earthLatError) {
warn.push('Geographic coordinates are not accurate enough for this level of precision.');
}
if (geo.coords.altitudeAccuracy === null) {
warn.push('Altitude measurement unavailable; using sea level.');
} else if (altAccuracy > geoSpash.altError) {
warn.push('Altitude measurement is not accurate enough for this level of precision.');
}
warningsNode.innerHTML = warn.join('<br>');
};
var addDigit = function(event) {
digits++;
setGeoloc(geoloc);
setMapCenter(geoloc);
};
var rmDigit = function(event) {
if (digits <= 0) { return; }
digits--;
setGeoloc(geoloc);
setMapCenter(geoloc);
};
var changeLatitude = function(event) {
var newLat = prompt('Enter a new latitude.');
if (newLat === null) {return;}
newLat = +newLat;
if (newLat !== newLat) {return;}
geoloc.coords.latitude = newLat;
setGeoloc(geoloc);
setMapCenter(geoloc);
};
var changeLongitude = function(event) {
var newLong = prompt('Enter a new longitude.');
if (newLong === null) {return;}
newLong = +newLong;
if (newLong !== newLong) {return;}
geoloc.coords.longitude = newLong;
setGeoloc(geoloc);
setMapCenter(geoloc);
};
var changeAltitude = function(event) {
var newAlt = prompt('Enter a new altitude, in metres.');
if (newAlt === null) {return;}
newAlt = +newAlt;
if (newAlt !== newAlt) {return;}
geoloc.coords.altitude = newAlt;
setGeoloc(geoloc);
setMapCenter(geoloc);
};
var changeTime = function(event) {
var newTime = prompt('Enter a new time, in ISO 8601.');
if (newTime === null) {return;}
newTime = +new Date(newTime);
if (+newTime !== newTime) {return;}
geoloc.timestamp = newTime;
setGeoloc(geoloc);
setMapCenter(geoloc);
};
var updateSpash = function(newSpash) {
try {
var newGeo = spash.decode(newSpash);
} catch (e) {
alert('Invalid Spash');
return;
}
var parts = newSpash.split('.');
digits = parts[1].length;
setShowTimeHash(!!parts[2]);
setGeoloc(newGeo);
setMapCenter(geoloc);
};
var edit = function(event) {
var newSpash = prompt('Enter a spash.');
updateSpash(newSpash);
};
var showTimeHash = false;
var setShowTimeHash = function(show) {
showTimeHash = show;
showTimeNode.checked = show;
setGeoloc(geoloc);
};
var showTimeChanged = function(event) {
setShowTimeHash(event.target.checked);
};
var showMap = false;
var showMapChanged = function(event) {
showMap = event.target.checked;
if (showMap) {
loadMapLibrary();
googleMapNode.style.display = 'block';
} else {
googleMapNode.style.display = 'none';
}
};
var hashChanged = function(event) {
updateSpash(location.hash);
};
addDigitNode.addEventListener('click', addDigit);
rmDigitNode.addEventListener('click', rmDigit);
editNode.addEventListener('click', edit);
showTimeNode.addEventListener('change', showTimeChanged);
showMapNode.addEventListener('change', showMapChanged);
addEventListener('hashchange', hashChanged);
// Google Map
var googleMap, googleMapMarker;
var getZoomFromDigits = function(digits) {
return Math.floor(digits * 19 / 11);
};
var setMapCenter = function(geoloc) {
if (!googleMap) { return; }
var latLng = {
lat: geoloc.coords.latitude,
lng: geoloc.coords.longitude,
};
googleMap.setCenter(latLng);
var zoom = getZoomFromDigits(digits);
googleMap.setZoom(zoom);
googleMapMarker.setPosition(latLng);
};
var geolocAtFetchedElevation = geoloc;
var setSpashFromMapCenter = function() {
if (!googleMap) { return; }
var center = googleMap.getCenter();
var latLng = {lat: center.lat(), lng: center.lng()};
geoloc.coords.latitude = latLng.lat;
geoloc.coords.longitude = latLng.lng;
setGeoloc(geoloc);
setGeoloc(spash.decode(geoSpash.hash));
latLng = {
lat: geoloc.coords.latitude,
lng: geoloc.coords.longitude,
};
googleMapMarker.setPosition(latLng);
};
var initMap = function() {
googleMap = new google.maps.Map(googleMapNode);
googleMapMarker = new google.maps.Marker({map: googleMap});
googleMap.addListener('center_changed', setSpashFromMapCenter);
setMapCenter(geoloc);
}
var mapLibraryLoaded = false;
var loadMapLibrary = function() {
if (mapLibraryLoaded) { return; }
mapLibraryLoaded = true;
var script = document.createElement('script');
script.async = true;
script.defer = true;
script.src = "https://maps.googleapis.com/maps/api/js?key=AIzaSyB7tgHIP7-P8j_nV1W1ahUdXcXwB1fK18c&callback=initMap";
document.body.appendChild(script);
};
</script>