This example shows how to use the color scale chooser and a dynamically generated shaded relief image.
Click on the color scale to change, remove or add the colors and values.
You can check a similar example that changes colors to an image with isobands
This tutorial has all the information needed to create the visualization.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="geotiff.min.js"></script>
<script src="http://d3js.org/topojson.v1.min.js"></script>
<script src="color-scale.js"></script>
<script>
var width = 680,
height = 500;
var projection = d3.geoAzimuthalEqualArea()
.rotate([-8.2, -46.8])
.translate([width/2, height/2])
.scale(12000);
var canvas = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height);
var context = canvas.node().getContext("2d");
d3.request("swiss.tiff")
.responseType('arraybuffer')
.get(function(error, tiffData){
d3.json("swiss.json", function(error, topojsonData) {
var countries = topojson.feature(topojsonData, topojsonData.objects.country);
var path = d3.geoPath()
.projection(projection).context(context);
var tiff = GeoTIFF.parse(tiffData.response);
var image = tiff.getImage();
var rasters = image.readRasters();
var tiepoint = image.getTiePoints()[0];
var pixelScale = image.getFileDirectory().ModelPixelScale;
var geoTransform = [tiepoint.x, pixelScale[0], 0, tiepoint.y, 0, -1*pixelScale[1]];
var invGeoTransform = [-geoTransform[0]/geoTransform[1], 1/geoTransform[1],0,-geoTransform[3]/geoTransform[5],0,1/geoTransform[5]];
var altData = new Array(image.getHeight());
for (var j = 0; j<image.getHeight(); j++){
altData[j] = new Array(image.getWidth());
for (var i = 0; i<image.getWidth(); i++){
altData[j][i] = rasters[0][i + j*image.getWidth()];
}
}
//Calculate shaded Relief. Outside the first loop, to be able to put it in a function
var azimuth = 315;
var angleAltitude = 45;
var azimuthrad = azimuth*Math.PI / 180;
var altituderad = angleAltitude*Math.PI / 180;
var shadedData = new Array(image.getHeight());
for (var j = 0; j<image.getHeight(); j++){
shadedData[j] = new Array(image.getWidth());
for (var i = 0; i<image.getWidth(); i++){
var gradX, gradY;
if(i==0) gradX = altData[j][i+1] - altData[j][i];
else if(i==image.getWidth()-1) gradX = altData[j][i] - altData[j][i-1];
else gradX = (altData[j][i+1] - altData[j][i])/2 + (altData[j][i] - altData[j][i-1])/2;
if(j==0) gradY = altData[j+1][i] - altData[j][i];
else if(j==image.getHeight()-1) gradY = altData[j][i] - altData[j-1][i];
else gradY = (altData[j+1][i] - altData[j][i])/2 + (altData[j][i] - altData[j-1][i])/2;
var slope = Math.PI/2 - Math.atan(Math.sqrt(gradX*gradX + gradY*gradY));
var aspect = Math.atan2(-gradY, gradX);
shadedData[j][i] = Math.sin(altituderad) * Math.sin(slope)
+ Math.cos(altituderad) * Math.cos(slope)
* Math.cos(azimuthrad - aspect);
}
}
var canvasShaded = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height)
.style("display","none");
var contextShaded = canvasShaded.node().getContext("2d");
var idShaded = contextShaded.createImageData(width,height);
var dataShaded = idShaded.data;
var posShaded = 0;
for(var j = 0; j<height; j++){
for(var i = 0; i<width; i++){
var pointCoords = projection.invert([i,j]);
var px = invGeoTransform[0] + pointCoords[0]* invGeoTransform[1];
var py = invGeoTransform[3] + pointCoords[1] * invGeoTransform[5];
var shadedValue;
if(Math.floor(px) >= 0 && Math.ceil(px) < image.getWidth() && Math.floor(py) >= 0 && Math.ceil(py) < image.getHeight()){
shadedValue = 255*(1+shadedData[Math.floor(py)][Math.floor(px)])/2;
} else {
shadedValue = 255;
}
dataShaded[posShaded] = shadedValue;
dataShaded[posShaded+1] = shadedValue;
dataShaded[posShaded+2] = shadedValue;
dataShaded[posShaded+3] = 255 - shadedValue;
posShaded=posShaded+4;
}
}
contextShaded.putImageData( idShaded, 0, 0);
var scaleValues = [{value: 200, color: "#c9d5a6"},
{value: 900, color: "#7fa67a"},
{value: 1600, color: "#976a2f"},
{value: 2300, color: "#79750a"},
{value: 3000, color: "#7ab5e3"},
{value: 3700, color: "#fefefe"},
{value: 4400, color: "#f605cf"}];
var scale = d3.ColorScaleChooser()
.squareWidth(50)
.squareHeight(30)
.scaleValues(scaleValues)
.title("Altitude (m)")
.on("change", function(d, min, max){
var scaleWidth = 256;
d3.select("#colorScaleCanvas").remove();
var canvasColorScale = d3.select("body").append("canvas")
.attr("width", scaleWidth)
.attr("height", 1)
.attr("id", "colorScaleCanvas")
.style("display","none");
var contextColorScale = canvasColorScale.node().getContext("2d");
var gradient = contextColorScale.createLinearGradient(0, 0, scaleWidth, 1);
for (var i = 0; i < d.length; ++i) {
var position = (d[i].value-min)/(max-min);
if(position >= 0 && position < scaleWidth){
gradient.addColorStop(position, d[i].color);
}
}
contextColorScale.fillStyle = gradient;
contextColorScale.fillRect(0, 0, scaleWidth, 1);
var csImageData = contextColorScale.getImageData(0, 0, scaleWidth, 1).data;
draw(csImageData, min, max);
});
var svg = d3.select("body").append("svg")
.attr("width", 150)
.attr("height", 500)
.style("position", "absolute")
.style("left", "10px")
.style("top", "10px");
svg.call(scale);
function draw(csImageData, min, max){
context.drawImage(canvasShaded.node(), 0, 0);
context.globalAlpha = 0.7;
context.fillStyle = '#ffffff';
context.fillRect(0,0,width,height);
context.fill();
context.globalAlpha = 1;
var scaleWidth = csImageData.length/4;
var canvasRaster = d3.select("body").append("canvas")
.attr("width", width)
.attr("height", height)
.style("display","none");
var contextRaster = canvasRaster.node().getContext("2d");
var id = contextRaster.createImageData(width,height);
var data = id.data;
var pos = 0;
for(var j = 0; j<height; j++){
for(var i = 0; i<width; i++){
var pointCoords = projection.invert([i,j]);
var px = invGeoTransform[0] + pointCoords[0]* invGeoTransform[1];
var py = invGeoTransform[3] + pointCoords[1] * invGeoTransform[5];
var value;
if(Math.floor(px) >= 0 && Math.ceil(px) < image.getWidth() && Math.floor(py) >= 0 && Math.ceil(py) < image.getHeight()){
var dist1 = (Math.ceil(px)-px)*(Math.ceil(py)-py);
var dist2 = (px-Math.floor(px))*(Math.ceil(py)-py);
var dist3 = (Math.ceil(px)-px)*(py-Math.floor(py));
var dist4 = (px-Math.floor(px))*(py-Math.floor(py));
if (dist1 != 0 || dist2!=0 || dist3!=0 || dist4!=0){
value = altData[Math.floor(py)][Math.floor(px)]*dist1+
altData[Math.floor(py)][Math.ceil(px)]*dist2 +
altData[Math.ceil(py)][Math.floor(px)]*dist3 +
altData[Math.ceil(py)][Math.ceil(px)]*dist4;
} else {
value = altData[Math.floor(py)][Math.floor(px)];
}
} else {
value = -999;
}
var c = Math.round((scaleWidth-1) * ((value - min)/(max-min)));
var alpha = 255;
if(c<0) c=0;
if(c>=scaleWidth) c=scaleWidth-1;
data[pos] = csImageData[c*4];;
data[pos+1] = csImageData[c*4+1];
data[pos+2] = csImageData[c*4+2];
data[pos+3] = alpha;
pos = pos + 4
}
}
contextRaster.putImageData( id, 0, 0);
context.save();
context.beginPath();
path(countries);
context.clip();
context.drawImage(canvasRaster.node(), 0, 0);
context.globalAlpha = 0.5;
context.drawImage(canvasShaded.node(), 0, 0);
context.restore();
context.beginPath();
context.strokeStyle = "#777";
path(countries);
context.stroke();
}
});
});
</script>
</body>