Skip to content

Instantly share code, notes, and snippets.

@migurski migurski/README.md
Last active Dec 14, 2015

Embed
What would you like to do?
GL-Solar, false-color edition
self.addEventListener('message', onmessage);
var pi = Math.PI;
function onmessage(e)
{
var start = (new Date()).getTime();
var node_id = e.data.node_id,
features = e.data.features,
zoom = e.data.zoom,
list = features_list(features, zoom);
var end = (new Date()).getTime();
self.postMessage({node_id: node_id, list: list, elapsed: end - start});
}
function features_list(features, zoom)
{
var reds = {motorway: 1, motorway_link: 1, trunk: 1, trunk_link: 1, primary: 1};
var pixel = 2 * pi / (1 << (zoom + 8)),
floats = [];
for(var i in features)
{
var props = features[i]['properties'],
geometry = features[i]['geometry'],
parts = (geometry['type'] == 'LineString') ? [geometry['coordinates']] : geometry['coordinates'];
if(zoom < 14 && props['kind'] != 'major_road' && props['kind'] != 'highway')
{
continue;
}
var widths = highway_widths(props['highway'], props['kind'], zoom),
inner = widths[0],
outer = widths[1],
incap = inner/7,
outcap = inner/20;
var layer = highway_layer(props['highway'], props['explicit_layer'], props['is_bridge'], props['is_tunnel']);
for(var j in parts)
{
for(var k = 0; k < parts[j].length - 1; k++)
{
// Positions of line segment start and end in mercator
var loc1 = {lon: parts[j][k][0], lat: parts[j][k][1]},
loc2 = {lon: parts[j][k+1][0], lat: parts[j][k+1][1]},
p1 = project(loc1),
p2 = project(loc2);
// Offsets to the front, back and sides of line segment in mercator
var θ = Math.atan2(p2.y - p1.y, p2.x - p1.x),
ux = Math.cos(θ),
uy = Math.sin(θ),
vx = Math.cos+ pi/2),
vy = Math.sin+ pi/2);
// Positions of outer corners of line segment capped line in mercator
var pa = {x: p1.x - vx*inner - ux*incap, y: p1.y - vy*inner - uy*incap},
pb = {x: p2.x - vx*inner + ux*incap, y: p2.y - vy*inner + uy*incap},
pc = {x: p2.x + vx*inner + ux*incap, y: p2.y + vy*inner + uy*incap},
pd = {x: p1.x + vx*inner - ux*incap, y: p1.y + vy*inner - uy*incap},
z = layer;
// Render colors, including alpha value based on is_tunnel
var r = (props['highway'] in reds) ? 211/255 : 147/255,
g = (props['highway'] in reds) ? 54/255 : 161/255,
b = (props['highway'] in reds) ? 130/255 : 161/255,
a = (props['is_tunnel'] == 'yes') ? .4 : 1;
// Two triangles covering this line segment, with (x, y, z, r, g, b, a) values.
floats = floats.concat([pa.x, pa.y, z, 1, .5, 0, a, pb.x, pb.y, z, 1, 0, 0, a, pc.x, pc.y, z, 1, .5, 0, a]);
floats = floats.concat([pa.x, pa.y, z, 1, .5, 0, a, pc.x, pc.y, z, 1, .5, 0, a, pd.x, pd.y, z, 1, 1, 0, a]);
// Two additional triangles for bridge casings.
if(zoom >= 15 && props['is_bridge'] == 'yes')
{
// Positions of outer corners of line segment capped line in mercator
var pa = {x: p1.x - vx*outer - ux*outcap, y: p1.y - vy*outer - uy*outcap},
pb = {x: p2.x - vx*outer + ux*outcap, y: p2.y - vy*outer + uy*outcap},
pc = {x: p2.x + vx*outer + ux*outcap, y: p2.y + vy*outer + uy*outcap},
pd = {x: p1.x + vx*outer - ux*outcap, y: p1.y + vy*outer - uy*outcap},
z = layer - 10;
// Render colors for map background and adjusted z-index.
var r = 253/255,
g = 246/255,
b = 227/255;
floats = floats.concat([pa.x, pa.y, z, 1, .5, 0, a, pb.x, pb.y, z, 1, 0, 0, a, pc.x, pc.y, z, 1, .5, 0, a]);
floats = floats.concat([pa.x, pa.y, z, 1, .5, 0, a, pc.x, pc.y, z, 1, 0, 0, a, pd.x, pd.y, z, 1, 1, 0, a]);
}
}
}
}
return floats;
}
function project(loc)
{
var λ = pi * loc.lon / 180,
φ = pi * loc.lat / 180;
var x = λ,
y = Math.log(Math.tan(pi/4 + φ/2));
return {x: x, y: y};
}
//
// Larger numbers cause roads to shrink faster on zoom out.
//
var highway_coefficients = {
motorway: .6, trunk: .6, primary: .6, secondary: .6, tertiary: .6,
motorway_link: .7, trunk_link: .7, primary_link: .7, secondary_link: .7, tertiary_link: .7
};
//
// Get highway width in mercator radians.
//
function highway_widths(highway, kind, zoom)
{
var pixel = 2 * pi / (1 << (zoom + 8)),
coeff = (highway in highway_coefficients) ? highway_coefficients[highway] : .8,
coeff = (kind == 'path' ? .9 : coeff),
scale = Math.pow(2, coeff * (zoom - 18));
if(highway == 'motorway') {
var inner = 14;
} else if(kind == 'path' || kind == 'rail' || highway == 'service') {
var inner = 3;
} else {
var inner = 6.5;
}
return [inner * pixel * scale, (inner + 4) * pixel * scale];
}
//
// Smaller numbers prioritize roads in front of other roads.
//
var highway_priorities = {
motorway: 0, trunk: 1, primary: 2, secondary: 3, tertiary: 4,
motorway_link: 5, trunk_link: 5, primary_link: 5, secondary_link: 5, tertiary_link: 5,
residential: 6, unclassified: 6, road: 6,
unclassified: 7, service: 7, minor: 7
};
//
// Get highway layer (z-index) as an integer.
//
function highway_layer(highway, explicit_layer, is_bridge, is_tunnel)
{
// explicit layering mostly wins
var layer = (explicit_layer == undefined) ? 0 : explicit_layer * 1000;
// implicit layering less important.
if(is_bridge == 'yes')
{
layer += 100;
}
if(is_tunnel == 'yes')
{
layer -= 100;
}
// leave the +/-10 order of magnitude open for bridge casings.
// adjust slightly based on priority derived from highway type
layer -= (highway in highway_priorities) ? highway_priorities[highway] : 9;
return layer;
}
function linkProgram(gl, vsource, fsource)
{
if(gl == undefined)
{
alert("Your browser does not support WebGL, try Google Chrome? Sorry.");
throw "Your browser does not support WebGL, try Google Chrome? Sorry.";
}
var program = gl.createProgram(),
vshader = createShader(gl, vsource, gl.VERTEX_SHADER),
fshader = createShader(gl, fsource, gl.FRAGMENT_SHADER);
gl.attachShader(program, vshader);
gl.attachShader(program, fshader);
gl.linkProgram(program);
if(!gl.getProgramParameter(program, gl.LINK_STATUS))
{
throw gl.getProgramInfoLog(program);
}
return program;
}
function createShader(gl, source, type)
{
var shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if(!gl.getShaderParameter(shader, gl.COMPILE_STATUS))
{
throw gl.getShaderInfoLog(shader);
}
return shader;
}
// http://paulirish.com/2011/requestanimationframe-for-smart-animating/
// http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
// requestAnimationFrame polyfill by Erik Möller
// fixes from Paul Irish and Tino Zijdel
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
function endianness()
{
if(window.ArrayBuffer == undefined)
{
alert("Your browser does not support ArrayBuffer, try Google Chrome? Sorry.");
throw "Your browser does not support ArrayBuffer, try Google Chrome? Sorry.";
}
var b = new ArrayBuffer(4),
f = new Float32Array(b),
u = new Uint32Array(b);
f[0] = 1.0;
if(u[0] == 32831) {
return 'big';
} else {
return 'little';
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>GL-Solar False-Color Edition</title>
<script src="http://teczno.com/squares/Squares-D3-0.0.5.min.js" type="application/javascript"></script>
<script src="gl-boilerplate.js" type="application/javascript"></script>
<script src="tile-queue.js" type="application/javascript"></script>
<script src="map.js" type="application/javascript"></script>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="map"></div>
<script id="shader-vertex" type="x-shader/x-vertex">
const mat4 view = mat4 (2.0/CANVAS_WIDTH, 0, 0, 0, 0, -2.0/CANVAS_HEIGHT, 0, 0, 0, 0, -0.0001, 0, -1, 1, 0, 1);
uniform mat4 panzoom;
attribute vec3 xyz;
attribute vec4 rgba;
varying vec4 color;
void main()
{
gl_Position = view * panzoom * vec4(xyz, 1);
color = rgba;
}
</script>
<script id="shader-fragment" type="x-shader/x-fragment">
precision mediump float;
const vec3 bg = vec3(0.992, 0.965, 0.890);
varying vec4 color;
void main()
{
// instead of using the full RGBA, do a linear mix with background color
gl_FragColor = vec4(mix(bg.rgb, color.rgb, color.a), 1);
}
</script>
<script type="application/javascript">
<!--
var ctx = get_webgl_context();
var geo = new sq.Geo.Mercator();
var map = new Map(document.getElementById('map'), geo, {lat: 37.8043, lon: -122.2712}, 15);
function get_webgl_context(matrix)
{
var map = document.getElementById('map'),
c = document.createElement('canvas');
c.width = map.clientWidth;
c.height = map.clientHeight;
c.style.position = 'absolute';
map.insertBefore(c, null);
var gl = c.getContext('experimental-webgl'),
vsource = document.getElementById('shader-vertex').text,
vsource = vsource.replace('CANVAS_WIDTH', c.width.toFixed(1)),
vsource = vsource.replace('CANVAS_HEIGHT', c.height.toFixed(1)),
fsource = document.getElementById('shader-fragment').text,
program = linkProgram(gl, vsource, fsource);
gl.useProgram(program);
var xyzrgba_buffer = gl.createBuffer(),
xyz_attrib = gl.getAttribLocation(program, 'xyz'),
rgba_attrib = gl.getAttribLocation(program, 'rgba'),
panzoom = gl.getUniformLocation(program, 'panzoom'),
length = 0;
gl.enableVertexAttribArray(xyz_attrib);
gl.enableVertexAttribArray(rgba_attrib);
gl.bindBuffer(gl.ARRAY_BUFFER, xyzrgba_buffer);
function data(xys)
{
gl.bufferData(gl.ARRAY_BUFFER, xys, gl.DYNAMIC_DRAW);
length = xys.length/7;
}
function draw(size, ul, lr)
{
// mx+b style transformation.
var mx = size.x / (lr.x - ul.x), bx = -mx * ul.x,
my = size.y / (lr.y - ul.y), by = -my * ul.y;
var matrix = new Float32Array([mx, 0, 0, 0, 0, my, 0, 0, 0, 0, 1, 0, bx, by, 0, 1]);
gl.clearColor(253/255, 246/255, 227/255, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.uniformMatrix4fv(panzoom, false, matrix);
gl.vertexAttribPointer(xyz_attrib, 3, gl.FLOAT, false, 4*7, 0);
gl.vertexAttribPointer(rgba_attrib, 4, gl.FLOAT, false, 4*7, 4*3);
gl.drawArrays(gl.TRIANGLES, 0, length);
}
return {draw: draw, data: data};
}
//-->
</script>
</body>
</html>
function Map(parent, proj, loc, zoom)
{
this.queue = new Queue();
this.timeout = false;
this.worker = new Worker('feature-arrayer.js');
this.selection = d3.select(parent);
this.parent = parent;
var size = sq.Mouse.element_size(this.parent), coord = proj.locationCoordinate(loc).zoomTo(zoom);
this.grid = new sq.Grid.Grid(size.x, size.y, coord, 0);
this.projection = proj;
sq.Mouse.link_control(this.selection, new sq.Mouse.Control(this, false));
sq.Hash.link_control(this);
var map = this;
d3.select(window).on('resize.map', function() { map.update_gridsize() });
this.worker.addEventListener('message', function(e) { map.new_data(e.data.node_id, e.data.list, e.data.elapsed) }, false);
this.selection.selectAll('div.tile').remove();
this.redraw(false);
}
Map.prototype = {
update_gridsize: function()
{
var size = sq.Mouse.element_size(this.parent);
this.grid.resize(size.x, size.y);
},
pointLocation: function(point)
{
var coord = this.grid.pointCoordinate(point ? point : this.grid.center);
return this.projection.coordinateLocation(coord);
},
locationPoint: function(loc)
{
var coord = this.projection.locationCoordinate(loc);
return this.grid.coordinatePoint(coord);
},
setCenterZoom: function(loc, zoom)
{
this.grid.setCenter(this.projection.locationCoordinate(loc, zoom));
this.redraw(true);
},
onMoved: function(callback)
{
this.moved_callback = callback;
},
redraw: function(moved)
{
var tiles = this.grid.visibleTiles(),
join = this.selection.selectAll('div.tile').data(tiles, tile_key);
var map = this;
join.exit()
.remove()
.each(function(tile, i) { map.exit_handler(tile, this) });
join.enter()
.append('div')
.attr('class', 'tile')
.style('position', 'absolute')
.style('margin', '0')
.style('padding', '0')
.style('border', '0')
.style('-webkit-transform-origin', '0px 0px')
.each(function(tile, i) { map.enter_handler(tile, this) });
this.selection.selectAll('div.tile')
.style('left', tile_left)
.style('top', tile_top)
.style('width', tile_width)
.style('height', tile_height);
if(this.moved_callback)
{
this.moved_callback(this);
}
this.queue.process();
this.render();
},
update: function()
{
var len = 0,
offs = [];
// get the total length of all arrays
this.selection.selectAll('div.tile')
.each(function() { if(this.array) { len += this.array.length } });
var xys = new Float32Array(len),
off = 0;
// concatenate all arrays to xys
this.selection.selectAll('div.tile')
.each(function() { if(this.array) { xys.set(this.array, off); offs.push(off); off += this.array.length } });
ctx.data(xys);
var map = this;
if(map.timeout) {
clearTimeout(map.timeout);
}
map.timeout = setTimeout(function() { map.redraw() }, 100);
},
render: function()
{
var keys = [];
for(var key in this.arrays)
{
keys.push(key);
}
var size = sq.Mouse.element_size(this.parent),
nw = this.pointLocation({x: 0, y: 0}),
se = this.pointLocation(size),
ul = this.projection.project(nw),
lr = this.projection.project(se);
ctx.draw(size, ul, lr);
},
exit_handler: function(tile, node)
{
this.queue.cancel(node);
var map = this;
if(map.timeout) {
clearTimeout(map.timeout);
}
map.timeout = setTimeout(function() { map.update() }, 25);
},
enter_handler: function(tile, node)
{
if(tile.coord.zoom < 12)
{
return;
}
var map = this;
var callback = function(data)
{
map.queue.close(node);
map.worker.postMessage({node_id: node.id, features: data['features'], zoom: tile.coord.zoom});
}
node.id = this.next_int().toString();
node.onjson = callback;
this.queue.append(node, 'http://tile.openstreetmap.us/vectiles-highroad/'+tile.toKey()+'.json');
},
new_data: function(node_id, list, elapsed)
{
console.log('node', node_id, list.length, 'array in', elapsed, 'msec');
var f32array = new Float32Array(list);
this.selection.selectAll('div.tile')
.each(function() { if(this.id == node_id) { this.array = f32array } });
var map = this;
if(map.timeout) {
clearTimeout(map.timeout);
}
map.timeout = setTimeout(function() { map.update() }, 25);
},
next_int: function()
{
if(this.number == undefined)
{
this.number = 0;
}
return ++this.number;
}
}
function tile_key(tile) { return tile.toKey() }
function tile_left(tile) { return tile.left() }
function tile_top(tile) { return tile.top() }
function tile_width(tile) { return tile.width() }
function tile_height(tile) { return tile.height() }
function tile_xform(tile) { return tile.transform() }
body
{
background-color: #EEE8D5;
}
#map
{
width: 960px;
height: 500px;
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
}
div.tile
{
color: #839496;
display: block;
}
function Queue()
{
this.queue = [];
this.queue_by_id = {};
this.open_request_count = 0;
this.requests_by_id = {};
}
Queue.prototype = {
append: function(node, href)
{
var request = new Request(node, href);
this.queue.push(request);
this.queue_by_id[request.id] = request;
},
cancel: function(node)
{
this.close(node);
var request = this.queue_by_id[node.id];
if(request)
{
request.deny();
delete this.queue_by_id[node.id];
}
},
close: function(node)
{
var request = this.requests_by_id[node.id];
if(request)
{
request.deny();
delete this.requests_by_id[node.id];
this.open_request_count--;
}
},
process: function()
{
//this.queue.sort(Request.prototype.compare);
//console.log('processing', this.open_request_count, 'open req count', this.queue.length, 'queue');
while(this.open_request_count < 4 && this.queue.length > 0)
{
var request = this.queue.shift(),
loading = request.load();
if(loading)
{
this.requests_by_id[request.id] = request;
this.open_request_count++;
}
delete this.queue_by_id[request.id];
}
}
};
function Request(node, href)
{
this.id = node.id;
this.sort = node.sort;
this.node = node;
this.href = href;
}
Request.prototype = {
deny: function()
{
this.node = null;
},
load: function()
{
if(this.node && this.node.parentNode)
{
d3.json(this.href, this.node.onjson);
return true;
}
return false;
},
compare: function(a, b)
{
return b.sort - a.sort;
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.