Skip to content

Instantly share code, notes, and snippets.

@migurski
Created February 24, 2013 18:32
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save migurski/5024953 to your computer and use it in GitHub Desktop.
Save migurski/5024953 to your computer and use it in GitHub Desktop.
WebGL GeoJSON tile rendering
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>Saturday</title>
<script src="http://teczno.com/squares/Squares-D3-0.0.4.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"><canvas id="c" width="960" height="420"></div>
<script id="shader-vertex" type="x-shader/x-vertex">
const mat4 view = mat4 (2.0/960.0, 0, 0, 0, 0, -2.0/420.0, 0, 0, 0, 0, 1, 0, -1, 1, 0, 1);
uniform mat4 panzoom;
attribute vec2 xy;
void main()
{
gl_Position = view * panzoom * vec4(xy, 0, 1);
}
</script>
<script id="shader-fragment" type="x-shader/x-fragment">
precision mediump float;
void main()
{
gl_FragColor = vec4(0.44, 0.41, 0.29, 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 features_array(map, features)
{
var floats = [];
for(var i in features)
{
var geometry = features[i]['geometry'],
parts = (geometry['type'] == 'LineString') ? [geometry['coordinates']] : geometry['coordinates'];
for(var j in parts)
{
for(var k = 0; k < parts[j].length - 1; k++)
{
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 = map.projection.project(loc1),
p2 = map.projection.project(loc2);
floats.push(p1.x);
floats.push(p1.y);
floats.push(p2.x);
floats.push(p2.y);
}
}
}
return new Float32Array(floats);
}
function get_webgl_context(matrix)
{
var c = document.getElementById('c'),
gl = c.getContext('experimental-webgl'),
vsource = document.getElementById('shader-vertex').innerText,
fsource = document.getElementById('shader-fragment').innerText,
program = linkProgram(gl, vsource, fsource);
gl.useProgram(program);
var xy_buffer = gl.createBuffer(),
xy_attrib = gl.getAttribLocation(program, 'xy'),
panzoom = gl.getUniformLocation(program, 'panzoom'),
length = 0;
gl.enableVertexAttribArray(xy_attrib);
gl.bindBuffer(gl.ARRAY_BUFFER, xy_buffer);
function data(xys)
{
gl.bufferData(gl.ARRAY_BUFFER, xys, gl.DYNAMIC_DRAW);
length = xys.length/2;
}
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(0.83, 0.77, 0.56, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.enable(gl.DEPTH_TEST);
gl.uniformMatrix4fv(panzoom, false, matrix);
gl.vertexAttribPointer(xy_attrib, 2, gl.FLOAT, false, 4*2, 0);
gl.drawArrays(gl.LINES, 0, length);
}
return {draw: draw, data: data};
}
//-->
</script>
</body>
</html>
function Map(parent, proj, loc, zoom)
{
this.queue = new Queue();
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));
var map = this;
d3.select(window).on('resize.map', function() { map.update_gridsize() });
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);
this.redraw(true);
},
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);
},
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')
.text(tile_key)
.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);
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 } });
//console.log('updated', offs.length, 'node arrays', offs);
ctx.data(xys);
this.redraw();
},
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);
this.update();
},
enter_handler: function(tile, node)
{
if(tile.coord.zoom < 12)
{
return;
}
var map = this;
var callback = function(data)
{
map.queue.close(node);
//console.log(tile.toKey(), data['features'].length, 'features');
var f32array = features_array(map, data['features']);
//console.log(tile.toKey(), f32array.length, 'array');
var sw = map.projection.coordinateLocation(tile.coord.down()),
ne = map.projection.coordinateLocation(tile.coord.right()),
ll = map.projection.project(sw),
ur = map.projection.project(ne);
node.array = f32array;
map.update();
}
//d3.json('http://www.openstreetmap.us/~migurski/tiles/streets/'+tile.toKey()+'.geojson', callback);
node.id = this.next_int().toString();
node.onjson = callback;
this.queue.append(node, 'http://www.openstreetmap.us/~migurski/tiles/streets/'+tile.toKey()+'.geojson');
},
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() }
#map
{
border: 1px solid black;
width: 960px;
height: 420px;
position: relative;
overflow: hidden;
margin: 0;
padding: 0;
}
#c
{
position: absolute;
margin: 0;
padding: 0;
border: 0;
}
div.tile
{
color: rgb(112, 105, 74);
text-shadow: 0 0 3px rgb(212, 196, 143);
display: block;
position: absolute;
margin: 0;
padding: 0;
border: 0;
-webkit-transform-origin: 0px 0px;
}
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