Created
August 25, 2016 13:11
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> — Map data © <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 © Esri — 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