-
-
Save jeantimex/897dd2f73fbb758f0f66458d39dacce2 to your computer and use it in GitHub Desktop.
WebGL GeoJSON tile rendering II
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
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'; | |
} | |
} |
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 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, -0.01, 0, -1, 1, 0, 1); | |
uniform mat4 panzoom; | |
attribute vec3 xyz; | |
attribute vec3 rgb; | |
varying vec3 color; | |
void main() | |
{ | |
gl_Position = view * panzoom * vec4(xyz, 1); | |
color = rgb; | |
} | |
</script> | |
<script id="shader-fragment" type="x-shader/x-fragment"> | |
precision mediump float; | |
varying vec3 color; | |
void main() | |
{ | |
//gl_FragColor = vec4(0.44, 0.41, 0.29, 1); | |
gl_FragColor = vec4(color, 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, zoom) | |
{ | |
var pixel = 2 * Math.PI / (1 << (zoom + 8)), | |
scale = Math.pow(2, .8 * (zoom - 18)), | |
width = 10 * pixel * scale, | |
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['hwy'] in {motorway: 1, trunk: 1, primary: 1, secondary: 1, tertiary: 1})) | |
{ | |
continue; | |
} | |
var bigs = {motorway: 1, trunk: 1, primary: 1}; | |
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), | |
th = Math.atan2(p2.y - p1.y, p2.x - p1.x), | |
vx = Math.cos(th + Math.PI/2), | |
vy = Math.sin(th + Math.PI/2), | |
pa = {x: p1.x - vx * width, y: p1.y - vy * width}, | |
pb = {x: p2.x - vx * width, y: p2.y - vy * width}, | |
pc = {x: p2.x + vx * width, y: p2.y + vy * width}, | |
pd = {x: p1.x + vx * width, y: p1.y + vy * width}, | |
r = (props['hwy'] in bigs) ? 211/255 : 147/255, | |
g = (props['hwy'] in bigs) ? 54/255 : 161/255, | |
b = (props['hwy'] in bigs) ? 130/255 : 161/255, | |
z = (props['hwy'] in bigs) ? 1. : -1; | |
floats = floats.concat([pa.x, pa.y, z, r, g, b, pb.x, pb.y, z, r, g, b, pc.x, pc.y, z, r, g, b]); | |
floats = floats.concat([pa.x, pa.y, z, r, g, b, pc.x, pc.y, z, r, g, b, pd.x, pd.y, z, r, g, b]); | |
} | |
} | |
} | |
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 xyzrgb_buffer = gl.createBuffer(), | |
xyz_attrib = gl.getAttribLocation(program, 'xyz'), | |
rgb_attrib = gl.getAttribLocation(program, 'rgb'), | |
panzoom = gl.getUniformLocation(program, 'panzoom'), | |
length = 0; | |
gl.enableVertexAttribArray(xyz_attrib); | |
gl.enableVertexAttribArray(rgb_attrib); | |
gl.bindBuffer(gl.ARRAY_BUFFER, xyzrgb_buffer); | |
function data(xys) | |
{ | |
gl.bufferData(gl.ARRAY_BUFFER, xys, gl.DYNAMIC_DRAW); | |
length = xys.length/6; | |
} | |
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*6, 0); | |
gl.vertexAttribPointer(rgb_attrib, 3, gl.FLOAT, false, 4*6, 4*3); | |
gl.drawArrays(gl.TRIANGLES, 0, length); | |
} | |
return {draw: draw, data: data}; | |
} | |
//--> | |
</script> | |
</body> | |
</html> |
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
function Map(parent, proj, loc, zoom) | |
{ | |
this.queue = new Queue(); | |
this.timeout = false; | |
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); | |
var map = this; | |
if(map.timeout) { | |
clearTimeout(map.timeout); | |
} | |
map.timeout = setTimeout(function() { map.redraw() }, 50); | |
}, | |
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); | |
//console.log(tile.toKey(), data['features'].length, 'features'); | |
var f32array = features_array(map, data['features'], tile.coord.zoom); | |
console.log(tile.toKey(), f32array.length, 'array', 'node', node.id); | |
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; | |
if(map.timeout) { | |
clearTimeout(map.timeout); | |
} | |
map.timeout = setTimeout(function() { map.update() }, 25); | |
} | |
//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() } |
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
body | |
{ | |
background-color: #EEE8D5; | |
} | |
#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: #839496; | |
display: block; | |
position: absolute; | |
margin: 0; | |
padding: 0; | |
border: 0; | |
-webkit-transform-origin: 0px 0px; | |
} |
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
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