Skip to content

Instantly share code, notes, and snippets.

@mappingvermont
Created August 25, 2016 13:11
Show Gist options
  • Save mappingvermont/4270dd8ad712d3a2161d1c978f3841fb to your computer and use it in GitHub Desktop.
Save mappingvermont/4270dd8ad712d3a2161d1c978f3841fb to your computer and use it in GitHub Desktop.
Load GLAD tiles into Leaflet canvas, decode RGB value and filter by date
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>Filtering Pixels in Leaflet</title>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<!-- Load Leaflet from CDN-->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/leaflet/0.7.7/leaflet.css" />
<script src="https://cdn.jsdelivr.net/leaflet/0.7.7/leaflet.js"></script>
<!-- slider library-->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/8.2.1/nouislider.min.css" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/8.2.1/nouislider.min.js"></script>
<style>
body {
margin: 0;
padding: 0;
}
#map {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
}
#info-pane {
position: absolute;
top: 10px;
right: 10px;
min-width: 200px;
z-index: 10;
padding: 1em;
background: white;
}
.noUi-connect {
background: #CCC;
}
</style>
</head>
<body>
<div id="map"></div>
<div id="info-pane" class="leaflet-bar">
<div id="slider"></div>
<br>
<label id="min">
Start: 1/1/2015
</label><br>
<label id="max">
End: 12/31/2016
</label>
<br> Confidence Level
<select id="conf" onchange="redraw()">
<option value="all">All</option>
<option value="confirmed">Confirmed Only</option>
</select>
<hr>
<div id="pixel-value"></div>
</div>
<script>
function get_conf() {
val = document.getElementById("conf").value;
var out_array;
if (val == 'all') {
var out_array = [0, 1]
} else {
var out_array = [1]
}
return out_array
}
var dateSlider = document.getElementById('slider-date');
// create a UI slider for the end user to toggle the pixel range to display
var slider = document.getElementById('slider');
noUiSlider.create(slider, {
start: [0, 2],
//weekly timesteps
step: 0.0027,
connect: true,
range: {
min: 0,
max: 2
}
});
// Create a string representation of the date based on the slider val
function slider_to_date(slider_val) {
if (slider_val <= 1) {
var year = 15000
var day = parseInt(365 * slider_val)
} else {
var year = 16000
var day = parseInt(365 * (slider_val - 1))
}
return (year + day).toString()
}
function formatDate(date_str) {
if (date_str == '0') {
var date_text = 'Undefined'
} else {
var julian_day = parseInt(date_str.slice(-3).toString())
var year_char_len = date_str.length - 3
var year = 2000 + parseInt(date_str.slice(0, year_char_len))
var date = new Date(new Date(year, 0).setDate(julian_day));
var date_text = date.getMonth() + 1 + "/" + date.getDate() + "/" + date.getFullYear();
}
return date_text
}
// When the slider value changes, update the input and span
slider.noUiSlider.on('set', function(values, handle) {
document.getElementById('min').innerHTML = 'Start: ' + formatDate(slider_to_date(values[0]));
document.getElementById('max').innerHTML = 'End: ' + formatDate(slider_to_date(values[1]));
// redraw the tiles without resetting
redraw()
});
function redraw() {
for (t in glad._tiles)
glad._redrawTile(glad._tiles[t]);
}
var southWest = L.latLng(-90, -179),
northEast = L.latLng(90, 179),
worldBounds = L.latLngBounds(southWest, northEast);
var map = L.map('map', {
noWrap: true,
minZoom: 3,
maxZoom: 16,
maxBounds: worldBounds
//}).setView([1.322097, 15.779354], 10);
//}).setView([0, 15.779354], 6);
}).setView([0, 15], 3);
var Stamen_TonerLite = L.tileLayer('http://stamen-tiles-{s}.a.ssl.fastly.net/toner-lite/{z}/{x}/{y}.{ext}', {
attribution: 'Map tiles by <a href="http://stamen.com">Stamen Design</a>, <a href="http://creativecommons.org/licenses/by/3.0">CC BY 3.0</a> &mdash; Map data &copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>',
subdomains: 'abcd',
minZoom: 0,
maxZoom: 20,
ext: 'png'
});
map.addLayer(Stamen_TonerLite);
/*
var Esri_WorldImagery = L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles &copy; Esri &mdash; Source: Esri, i-cubed, USDA, USGS, AEX, GeoEye, Getmapping, Aerogrid, IGN, IGP, UPR-EGP, and the GIS User Community'
});
map.addLayer(Esri_WorldImagery);
*/
var glad = L.tileLayer.canvas({
noWrap: true,
attribution: 'USGS, Esri'
});
glad.drawTile = function(canvas, tilePoint, zoom) {
drawData = function(img, ctx) {
// Updates every time a tile is drawn, and every time the slider moves
ctx.drawImage(img, 0, 0);
var width = img.width;
var height = img.height;
var min = parseInt(slider_to_date(slider.noUiSlider.get()[0]));
var max = parseInt(slider_to_date(slider.noUiSlider.get()[1]));
//TODO add dropdown
//var conf = 1
var imageData = ctx.getImageData(0, 0, width, height);
var data = imageData.data;
// Filter image
data = filter(data, min, max, get_conf())
ctx.putImageData(imageData, 0, 0);
}
var tileIndex = tilePoint.x + ':' + tilePoint.y;
var tile = this._tiles[tileIndex];
var xhr = new XMLHttpRequest();
xhr.responseType = "arraybuffer";
var url = 'http://wri-tiles.s3.amazonaws.com/glad_prod/tiles/' + zoom + '/' + tilePoint.x + '/' + tilePoint.y + '.png';
xhr.open("Get", url, true);
xhr.send();
xhr.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
var arrayBufferView = new Uint8Array(xhr.response);
var blob = new Blob([arrayBufferView], {
type: "image/jpeg"
});
var imageUrl = URL.createObjectURL(blob);
var img = new Image();
img.onload = function() {
tile.img = img
drawData(tile.img, canvas.getContext('2d'));
}
img.src = imageUrl
}
}
};
map.on('mousemove', function(e) {
// the x/y of the tile url
var layerPoint = map.project(e.latlng).floor();
var tilePoint = layerPoint.divideBy(256).floor();
var block = glad._tiles[tilePoint.x + ':' + tilePoint.y].img;
var layerPoint_px = e.layerPoint
// Read the data value from the block if it exists
if (block) {
// No idea how this black magic works, but it does
var pointInTile = layerPoint.subtract(tilePoint.multiplyBy(256));
// read in the image
var canvas = document.createElement('canvas');
canvas.width = block.width;
canvas.height = block.height;
canvas.getContext('2d').drawImage(block, 0, 0, block.width, block.height);
// Get the rgba color, using the inversion
var image_data = canvas.getContext('2d').getImageData(pointInTile.x, pointInTile.y, 1, 1).data
var min = parseInt(slider_to_date(slider.noUiSlider.get()[0]));
var max = parseInt(slider_to_date(slider.noUiSlider.get()[1]));
var rgba = filter(image_data, min, max, get_conf())
//console.log(rgba);
var result_array = decode_date_text(rgba)
//TEMPORARY
decode_date_mouseover(rgba);
document.getElementById('pixel-value').innerHTML = "Date: " + result_array[0] + ', Confidence: ' + result_array[1] + '<br> Intensity: ' + result_array[2];
} else {
document.getElementById('pixel-value').innerHTML = "Undefined";
}
})
var filter = function(data, min_date, max_date, conf) {
for (var i = 0; i < data.length; i += 4) {
values = decode_date(data.slice(i, i + 4))
// check against confidence and date range
// if any of them are false,
if (values[0] >= min_date && values[0] <= max_date && conf.indexOf(values[1]) > -1) {
// Set color to pink if it's a valid pixel
//data[i] = 255,
//data[i + 1] = 102,
//data[i + 2] = 153
data[i + 3] = values[2]
//console.log('here');
} else {
// if it fails any of the conditions set alpha to 0, hiding the pixel
data[i + 3] = 0;
}
}
return data
};
var pad = function(num) {
var s = "00" + num;
return s.substr(s.length - 3);
}
var decode_date = function(rgba) {
// find the total days of the pixel by
// multiplying the red band by 255 and adding
// the green band to that
var total_days = rgba[0] * 255 + rgba[1];
// take the total days value and divide by 365 to
// get the year_offset. Add 15 to this (i.e 0 + 15 = 2015)
// or 1 + 15 = 2016
var year_int = parseInt(total_days / 365) + 15;
// Multiply by 1000 to give us year in YYDDD format
// (i.e. 15000 or 16000)
var year = parseInt((year_int * 1000))
// Find the remaining days to get the julian day for
// that year
var julian_day = total_days % 365;
// Add to get YYDDD date val
var date_val = year + julian_day;
// Convert the blue band to string, leading
// zeros if it's not currently three digits
// this occurs very rarely; where there's an intensity
// value but no date/confidence for it. Due to bilinear
// resampling
var band3_str = pad(rgba[2].toString());
// Grab confidence (the first value) from this string
// confidence is stored as 1/2, subtract one to make it 0/1
var confidence = parseInt(band3_str[0]) - 1
// Grab the raw intensity value from the pixel; ranges from 1 - 55
var intensity_raw = parseInt(band3_str.slice(1, 3))
// Scale the intensity to make it visible
var intensity = intensity_raw * 50
// Set intensity to 255 if it's > than that value
if (intensity > 255) {
intensity = 255
}
return [date_val, confidence, intensity]
};
var decode_date_mouseover = function(rgba) {
//used in debugging etc
var total_days = rgba[0] * 255 + rgba[1];
var year_int = parseInt(total_days / 365) + 15;
var year = parseInt((year_int * 1000))
var julian_day = total_days % 365;
var band3_str = pad(rgba[2].toString());
var confidence = parseInt(band3_str[0]) - 1
var intensity_raw = parseInt(band3_str.slice(1, 3))
var intensity = intensity_raw * 50
//console.log(rgba);
//console.log(band3_str, confidence, year, julian_day);
if (intensity > 255) {
intensity = 255
}
//intensity = 255;
var date_val = year + julian_day;
console.log(rgba, year, julian_day, confidence)
};
var decode_date_text = function(rgba) {
// Check if the pixel is being hidden
if (rgba[3] == 0) {
var out_vals = ['Undefined', -1]
} else {
values = decode_date(rgba)
date_str = values[0].toString()
confidence = values[1]
intensity = values[2]
var date = formatDate(date_str)
var out_vals = [date, confidence, intensity]
}
return out_vals
}
glad.addTo(map);
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment