Click for a new map and palette. Move the mouse sideways to change where the light source is.
[There is also a pure SVG map with elevation contours.]
This SVG filter generates elevation, flat waters and icy mesas. Also see my previous filter and twitter/@monfera for visualizations including glitchy shots from making this block. A couple of D3 color scales are added but otherwise there’s no dependency, it’s kept minimal.
This block has:
Sometimes we lean on D3 for things that the underlying standards already provide, or miss opportunities. I’m impressed by Nadieh Bremer’s work with filters which shows how much can be done beyond the basics.
Palette copyrights in the source. Palette authors:
This is what I wanted:
I found that:
Built with blockbuilder.org
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SVG generative map</title>
<script src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<svg width=960 height=500 version="1.1" xmlns="//www.w3.org/2000/svg">
<defs>
<filter id="bump">
<!--generate noise-->
<feTurbulence id="noise" seed="603883" type="fractalNoise"
baseFrequency=".001" numOctaves="12" result="turbulence" />
<!--scale the noise and keep the alpha channel only-->
<feComponentTransfer in="turbulence" result="bumpMap">
<feFuncA type="table" tableValues="-1.9 2.3"/>
<feFuncR type="gamma" amplitude="0" />
<feFuncG type="gamma" amplitude="0" />
<feFuncB type="gamma" amplitude="0" />
</feComponentTransfer>
<!--make a bump map out of the noise
Safari chokes on multiline attrs so... split lines and use Chrome-->
<feConvolveMatrix in="bumpMap" result="fineContours" order="3"
kernelMatrix="10 10 10 10 -80 10 10 10 10" />
<!--convert the alpha channel to grayscale RGB channels for palette -->
<feColorMatrix in="bumpMap" result="grayscaleBumpMap" mode="matrix"
values="0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1" />
<!--pick up colors from the palette and map them into the channels-->
<feComponentTransfer in="grayscaleBumpMap" result="coloredBumpMap">
<feFuncR id="geoR" type="discrete" />
<feFuncG id="geoG" type="discrete" />
<feFuncB id="geoB" type="discrete" />
</feComponentTransfer>
<!--Elevation run color alterations 2.-->
<!--desaturate the original palette a bit-->
<feColorMatrix id="saturate" type="saturate" values="0.8"
in="coloredBumpMap" result="saturationAdjusted"/>
<!--adjust the gamma for darker tones
... lighter tones would be better if there are overlays-->
<feComponentTransfer in="saturationAdjusted" result="colorAdjusted">
<feFuncR id="gammaR" type="gamma" amplitude="1" exponent="2.5"/>
<feFuncG id="gammaG" type="gamma" amplitude="1" exponent="2.5"/>
<feFuncB id="gammaB" type="gamma" amplitude="1" exponent="1.8"/>
</feComponentTransfer>
<!--join the elevation coloring, contour lines and original SVG input-->
<feBlend in="colorAdjusted" in2="fineContours" result="topoMap"/>
<feComposite in="SourceGraphic" in2="topoMap" result="blendedContents"/>
<!--make light-->
<feSpecularLighting in="bumpMap" lighting-color="#fff" surfaceScale="100"
specularConstant="1" specularExponent="2" result="light">
<fePointLight id="specularPointLight" x="960" y="-500" z="500" />
</feSpecularLighting>
<!--output contents with light-->ec
<feComposite in="blendedContents" in2="light"
operator = "arithmetic" k1="0" k2="1" k3="0.3" k4="0"/>
</filter>
</defs>
<g style="filter: url('#bump'); transform: scale(0.5)"
width="1920" height="1000">
<rect id="r" width="1920" height="1000" style="fill:black;fill-opacity:0"/>
<text x="60" y="980" style="font: bold 42px 'Arial Black'">@monfera</text>
<text id="meta" x="1880" y="980" text-anchor="end"
style="font: bold 42px 'Arial Black'"></text>
</g>
<script>
// Elements
var noise = document.getElementById('noise')
var light = document.getElementById('specularPointLight')
var sat = document.getElementById('saturate')
var gamma = document.getElementById('gamma')
var container = document.getElementById('r')
var geom = document.getElementById('geom')
var meta = document.getElementById('meta')
// Elevation run colors
//wiki-schwarzwald-cont
//Copyright Jide and W-j-s (https://als.wikipedia.org/wiki/Benutzer:W-j-s)
// soliton.vm.bytemark.co.uk
// /pub/cpt-city/wkp/schwarzwald/wiki-schwarzwald-cont.png.index.html
// Creative commons attribution share-alike 3.0 unported
function fromByte(d) { return d / 255}
var wikiSchwarzwald = {
name: "wiki-schwarzwald-cont by Jide and W-j-s",
r: [
174,175,176,176,177,176,176,178,181,186,192,198,204,210,217,224,231,
238,245,250,248,238,226,213,198,184,170,154,140,125,110,94,77,62,49,
39,30,24,18,14,9,7,12, 24,40,52,64,76,87,99,110,120,128,137,147,156,
166,176,187,197,207,218,228,238,246,248,244,238,232,226,220,216,211,
206,200,192,186,180,174,169,163,157,151,146,141,135,130,125,122,119,
118,117,117,117,116,116,114,114,112,111,110,110,109,108,108,108,107,
106,106,107,110,113,116,118,121,125,128,131,135,138,140,144,147,150,
152,156,158,160,163,166,167,170,172,174,178,181,184,188,192,196,200,
204,208,212,216,218,221,225,229,233,235
].map(fromByte),
g: [
239,240,242,242,242,243,244,246,246,247,247,248,249,250,250,251,252,
252,252,252,249,244,240,235,228,222,216,211,205,199,194,188,182,176,
171,165,160,154,148,142,137,132,130,130,132,136,140,142,146,148,150,
154,156,160,162,164,166,170,173,176,177,179,180,182,182,176,166,155,
144,132,122,111,102,92,84,74,66,58,49,42,36,30,23,18,14,8,5,4,8,13,16,
18,20,21,22,24,26,29,31,33,35,36,38,40,40,42,44,44,46,48,52,57,62,66,
70,74,79,85,90,96,101,106,111,116,122,129,135,141,147,154,160,167,172,
174,178,181,184,188,192,196,200,204,206,210,214,216,219,223,227,231,233
].map(fromByte),
b: [
213,211,208,202,196,190,186,181,178,178,178,178,178,177,178,178,178,
179,179,178,172,162,151,140,128,118,108,98,89,82,74,66,57,50,44,42,43,
46,49,52,56,60,63,63, 61,60,59,59,56,54,52,50,48,46,43,41,39,36,34,30,
28,24,20,14,8,4,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,1,1,0,0,0,2,2,2,4,
4,4,4,5,6,6,7,8,8,8,9,10,10,10,11,12,12,14,18,23,28,32,37,43,50,56,63,
69,76,84,90,96,104,112,120,130,139,147,156,164,171,174,178,181,184,
188,192,196,200,204,208,212,216,218,221,225,229,233,235
].map(fromByte),
water: {r: 174/255, g: 213/255, b: 239/255},
gamma: {r: 1, g: 1, b: 1},
saturation: 0.8,
cut: 26 // start with greens
}
//tv-a
//Copyright Jim Mossman //www.esri.com/news/arcuser/0101/shademax.html
// soliton.vm.bytemark.co.uk/pub/cpt-city/jm/tv/tn/tv-a.png.index.html
// //soliton.vm.bytemark.co.uk/pub/cpt-city/jm/copying.html
var tva = {
name: "tv-a by Jim Mossman",
r: [115,115,115,115,140,140,148,148,155,155,163,163,171,171,178,178,186,
186,194,194,201,201,209,209,217,217,224,224,232,232,240,240,247,247,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255
].map(fromByte),
g: [255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
255,255,255,247,247,240,240,232,232,225,225,218,218,211,211,204,204,
197,197,190,190,183,183,176,176,169,169,162,162,155,155,160,160,172,
172,183,183,189,189,195,195,201,201,210,210,218,218,226,226,232,232,
237,237,243,243,247,247,251,251,
].map(fromByte),
b: [143,143,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,
115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,115,
115,115,119,119,123,123,127,127,127,127,127,127,127,127,127,127,127,
127,127,127,127,127,127,127,127,127,127,127,127,127,141,141,155,155,
168,168,176,176,183,183,190,190,200,200,210,210,220,220,227,227,233,
233,240,240,245,245,250,250
].map(fromByte),
water: {r: 174/255, g: 213/255, b: 239/255},
gamma: {r: 4, g: 4, b: 4},
saturation: 0.8,
cut: 0
}
function runToPalette(name, d3Palette) {
var palette = {
name: name,
r: [],
g: [],
b: [],
water: {r: 90/255, g: 105/255, b: 120/255},
gamma: {r: 1, g: 1, b: 1},
saturation: 1,
cut: 0
}
var color, i
for(i = 0; i <= 1000; i ++) {
color = d3.color(d3Palette(i / 1000))
palette.r.push(color.r / 255)
palette.g.push(color.g / 255)
palette.b.push(color.b / 255)
}
return palette
}
var viridis = runToPalette("Viridis run from D3", d3.interpolateViridis)
var magma = runToPalette("Magma run from D3", d3.interpolateMagma)
// ice effect
var ice = {
name: "grayscale/ice",
r: [],
g: [],
b: [],
gamma: {r: 1.5, g: 1.3, b: 1},
saturation: 0.8,
cut: 0
}
var coal = {
name: "text invisible anyway",
r: [0, 0],
g: [0, 0],
b: [0, 0],
gamma: {r: 1, g: 1, b: 1},
saturation: 1,
cut: 0
}
var palettes = [tva, wikiSchwarzwald, ice, magma, viridis]
var paletteIndex = 0
function setPalette() {
var p = palettes[paletteIndex++ % palettes.length]
// add color for water bodies:
var geoR= (p.water ? [p.water.r] : []).concat(p.r.slice(p.cut)).join(' ')
var geoG= (p.water ? [p.water.g] : []).concat(p.g.slice(p.cut)).join(' ')
var geoB= (p.water ? [p.water.b] : []).concat(p.b.slice(p.cut)).join(' ')
// set the palette on the receiving SVG filter channels
document.getElementById('geoR').setAttribute('tableValues', geoR)
document.getElementById('geoG').setAttribute('tableValues', geoG)
document.getElementById('geoB').setAttribute('tableValues', geoB)
// set saturation
sat.setAttribute("values", p.saturation)
// set gamma
document.getElementById('gammaR').setAttribute('exponent', p.gamma.r)
document.getElementById('gammaG').setAttribute('exponent', p.gamma.g)
document.getElementById('gammaB').setAttribute('exponent', p.gamma.b)
// set palette name
meta.innerHTML = p.name
}
//Interactions
function moveLight(e) {
light.setAttribute('x', 2 * e.x)
//sat.setAttribute('values', .4 + .6 * Math.round(3 * (1 - e.y/500)) / 3)
}
function newMap() {
setPalette()
var newSeed = Math.round(1e6 * Math.random())
noise.setAttribute('seed', newSeed)
console.log("Seed: ", newSeed)
// some good seeds: 453109 394778 221947 601567
}
container.addEventListener("mousemove", moveLight)
container.addEventListener("click", newMap)
// Initial palette
setPalette()
</script>
</svg>
</body>
</html>