Skip to content

Instantly share code, notes, and snippets.

@pelson
Last active November 28, 2016 10:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pelson/eadc049527cc173cc886b7ff11e5edf7 to your computer and use it in GitHub Desktop.
Save pelson/eadc049527cc173cc886b7ff11e5edf7 to your computer and use it in GitHub Desktop.
Visualising the last 250 days of mslp using leaflet
This gist exceeds the recommended number of files (~10). To access all files, please clone this gist.
L.TimeDimension.Layer.ImageOverlay = L.TimeDimension.Layer.extend({
initialize: function(layer, options) {
L.TimeDimension.Layer.prototype.initialize.call(this, layer, options);
this._layers = {};
this._defaultTime = 0;
this._timeCacheBackward = this.options.cacheBackward || this.options.cache || 0;
this._timeCacheForward = this.options.cacheForward || this.options.cache || 0;
this._getUrlFunction = this.options.getUrlFunction;
this._baseLayer.on('load', (function() {
this._baseLayer.setLoaded(true);
this.fire('timeload', {
time: this._defaultTime
});
}).bind(this));
},
eachLayer: function(method, context) {
for (var prop in this._layers) {
if (this._layers.hasOwnProperty(prop)) {
method.call(context, this._layers[prop]);
}
}
return L.TimeDimension.Layer.prototype.eachLayer.call(this, method, context);
},
_onNewTimeLoading: function(ev) {
var layer = this._getLayerForTime(ev.time);
if (!this._map.hasLayer(layer)) {
this._map.addLayer(layer);
}
},
isReady: function(time) {
var layer = this._getLayerForTime(time);
return layer.isLoaded();
},
_update: function() {
if (!this._map)
return;
var time = this._map.timeDimension.getCurrentTime();
var layer = this._getLayerForTime(time);
if (this._currentLayer == null) {
this._currentLayer = layer;
}
if (!this._map.hasLayer(layer)) {
this._map.addLayer(layer);
} else {
this._showLayer(layer, time);
}
},
_showLayer: function(layer, time) {
if (this._currentLayer && this._currentLayer !== layer) {
this._currentLayer.hide();
this._map.removeLayer(this._currentLayer);
}
layer.show();
if (this._currentLayer && this._currentLayer === layer) {
return;
}
this._currentLayer = layer;
// Cache management
var times = this._getLoadedTimes();
var strTime = String(time);
var index = times.indexOf(strTime);
var remove = [];
// remove times before current time
if (this._timeCacheBackward > -1) {
var objectsToRemove = index - this._timeCacheBackward;
if (objectsToRemove > 0) {
remove = times.splice(0, objectsToRemove);
this._removeLayers(remove);
}
}
if (this._timeCacheForward > -1) {
index = times.indexOf(strTime);
var objectsToRemove = times.length - index - this._timeCacheForward - 1;
if (objectsToRemove > 0) {
remove = times.splice(index + this._timeCacheForward + 1, objectsToRemove);
this._removeLayers(remove);
}
}
},
_getLayerForTime: function(time) {
if (time == 0 || time == this._defaultTime) {
return this._baseLayer;
}
if (this._layers.hasOwnProperty(time)) {
return this._layers[time];
}
var url = this._getUrlFunction(this._baseLayer.getURL(), time);
imageBounds = this._baseLayer._bounds;
var newLayer = L.imageOverlay(url, imageBounds, this._baseLayer.options);
this._layers[time] = newLayer;
newLayer.on('load', (function(layer, time) {
layer.setLoaded(true);
if (this._map.timeDimension && time == this._map.timeDimension.getCurrentTime() && !this._map.timeDimension.isLoading()) {
this._showLayer(layer, time);
}
this.fire('timeload', {
time: time
});
}).bind(this, newLayer, time));
// Hack to hide the layer when added to the map.
// It will be shown when timeload event is fired from the map (after all layers are loaded)
newLayer.onAdd = (function(map) {
Object.getPrototypeOf(this).onAdd.call(this, map);
this.hide();
}).bind(newLayer);
return newLayer;
},
_getLoadedTimes: function() {
var result = [];
for (var prop in this._layers) {
if (this._layers.hasOwnProperty(prop)) {
result.push(prop);
}
}
return result.sort();
},
_removeLayers: function(times) {
for (var i = 0, l = times.length; i < l; i++) {
this._map.removeLayer(this._layers[times[i]]);
delete this._layers[times[i]];
}
},
});
L.timeDimension.layer.imageOverlay = function(layer, options) {
return new L.TimeDimension.Layer.ImageOverlay(layer, options);
};
L.ImageOverlay.include({
_visible: true,
_loaded: false,
_originalUpdate: L.imageOverlay.prototype._update,
_update: function() {
if (!this._visible && this._loaded) {
return;
}
this._originalUpdate();
},
setLoaded: function(loaded) {
this._loaded = loaded;
},
isLoaded: function() {
return this._loaded;
},
hide: function() {
this._visible = false;
if (this._image && this._image.style)
this._image.style.display = 'none';
},
show: function() {
this._visible = true;
if (this._image && this._image.style)
this._image.style.display = 'block';
},
getURL: function() {
return this._url;
},
});
<!DOCTYPE html>
<html>
<head>
<title>Animated data</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.0.2/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet@1.0.2/dist/leaflet.js"></script>
<script src='https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/Leaflet.fullscreen.min.js'></script>
<link href='https://api.mapbox.com/mapbox.js/plugins/leaflet-fullscreen/v1.0.1/leaflet.fullscreen.css' rel='stylesheet' />
<!-- Get the slider in. TODO: Avoid using rawgit as a CDN. -->
<script src='https://code.jquery.com/jquery-3.1.1.min.js'></script>
<script src='https://rawgit.com/nezasa/iso8601-js-period/master/iso8601.min.js'></script>
<link href='https://rawgit.com/socib/Leaflet.TimeDimension/master/dist/leaflet.timedimension.control.min.css' rel='stylesheet' />
<script src='https://rawgit.com/socib/Leaflet.TimeDimension/master/dist/leaflet.timedimension.min.js'></script>
<!-- Animatable image overlay -->
<script src='image_overlay.js'></script>
<style>
body {
padding: 0;
margin: 0;
}
html, body {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<div id="mapid" style="width: 100%; height: 100%;"></div>
<script>
var mymap = L.map('mapid', {crs: L.CRS.EPSG4326, fullscreenControl: true,
timeDimension: true,
timeDimensionOptions: {
timeInterval: "2016-03-23/2016-11-24",
period: "PT12H"
},
timeDimensionControlOptions: {
loopButton: true,
autoPlay: true,
limitSliders: true,
timeSliderDragUpdate: true,
playerOptions: {
loop: true,
transitionTime: 333,
}
},
timeDimensionControl: true,
}).setView([0, 180], 1);
// Our images are global images, so setup the bounds here.
var imageBounds = [[-90, 0], [90, 360]];
// create the title
var title = L.control({position: 'topright'});
title.onAdd = function (map) {
var div = L.DomUtil.create('div', 'command');
div.innerHTML = '<h1 id="map_title"></h1>';
return div;
};
title.addTo(mymap);
var mslpLayer = L.imageOverlay('mslp_201603230000.png', imageBounds, {
opacity: 1.0,
});
var coastlineLayer = L.imageOverlay('coastlines.png', imageBounds, {
opacity: 1.0,
});
function pad(n, width, z) {
z = z || '0';
n = n + '';
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
}
var getMSPLUrl = function(baseUrl, time) {
var beginUrl = baseUrl.substring(0, baseUrl.lastIndexOf("/"));
var dt = new Date(time)
var url = (beginUrl + 'mslp_' +
+ dt.getUTCFullYear()
+ pad((dt.getUTCMonth() + 1), 2)
+ pad(dt.getUTCDate(), 2)
+ pad(dt.getUTCHours(), 2)
+ pad(dt.getUTCMinutes(), 2) + '.png');
return url;
};
L.TimeDimension.Layer.BaseImageOverlay = L.TimeDimension.Layer.ImageOverlay.extend({
_onNewTimeLoading: function(ev) {
var layer = this._getLayerForTime(ev.time);
if (!this._map.hasLayer(layer)) {
this._map.addLayer(layer);
layer.bringToBack();
}
},
});
var mslpLayer_t = new L.TimeDimension.Layer.BaseImageOverlay(mslpLayer, {
getUrlFunction: getMSPLUrl,
});
mslpLayer_t.addTo(mymap);
coastlineLayer.addTo(mymap);
// mymap.on('click', function(e) {
// console.log(e.latlng.lat, e.latlng.lng)
// });
L.TimeDimension.Layer.TrackGeoJson = L.TimeDimension.Layer.GeoJson.extend({
_getFeatureBetweenDates: function(feature, minTime, maxTime) {
// An identical copy to the superclass, except that the index (where necessary) always includes the lowest index when slicing.
// This gives the effect of the track growing during its validity time, and not being visible when there is no validity time.
var featureStringTimes = this._getFeatureTimes(feature);
if (featureStringTimes.length == 0) {
return feature;
}
var featureTimes = [];
for (var i = 0, l = featureStringTimes.length; i < l; i++) {
var time = featureStringTimes[i]
if (typeof time == 'string' || time instanceof String) {
time = Date.parse(time.trim());
}
featureTimes.push(time);
}
if (featureTimes[0] > maxTime || featureTimes[l - 1] < minTime) {
return null;
}
var index_min = null,
index_max = null,
l = featureTimes.length;
if (featureTimes[l - 1] > minTime) {
for (var i = 0; i < l; i++) {
if (index_min === null && featureTimes[i] > minTime) {
// set index_min the first time that current time is greater the minTime
index_min = i;
}
if (featureTimes[i] > maxTime) {
index_max = i;
break;
}
}
}
if (index_min === null) {
index_min = 0;
}
if (index_max === null) {
index_max = l;
}
var new_coordinates = [];
if (feature.geometry.coordinates[0].length) {
new_coordinates = feature.geometry.coordinates.slice(index_min, index_max);
// This is the new line.
new_coordinates = feature.geometry.coordinates.slice(0, index_max);
} else {
new_coordinates = feature.geometry.coordinates;
}
return {
type: 'Feature',
properties: feature.properties,
geometry: {
type: feature.geometry.type,
coordinates: new_coordinates
}
};
},
});
var myStyle = {
"color": "blue",
"weight": 5,
"opacity": 0.65
};
var myLayer = L.geoJSON([], myStyle);
var td_myLayer = new L.TimeDimension.Layer.TrackGeoJson(myLayer,
{
// Duration: The amount of time that a feature should remain after it is out of time.
duration: 'PT0M',
addLastPoint: true,
});
td_myLayer.addTo(mymap);
function addDataToMap(data) {
myLayer.addData(data);
};
$.getJSON("tracks.json", addDataToMap);
mymap.timeDimension.on('timeload', function(data) {
var date = new Date(mymap.timeDimension.getCurrentTime());
$('#map_title').text(date.toISOString());
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment