Skip to content

Instantly share code, notes, and snippets.

@Sumbera
Last active September 29, 2015 09:44
Show Gist options
  • Save Sumbera/c8764cc3367e671df841 to your computer and use it in GitHub Desktop.
Save Sumbera/c8764cc3367e671df841 to your computer and use it in GitHub Desktop.
Polyline tessellation with MapBox-gl-js

Quick test of polyline tessellation capabilities of the new MapBox-gl-js, blogged here

<!doctype html>
<html>
<head>
<title>Polyline tessellation with MapBox-gl-js</title>
<meta charset="utf-8">
<style>
html, body {
height: 100%;
padding: 0;
margin: 0;
background: rgb(14, 21, 30);
height: 100%;
}
#map {
position: absolute;
height: 100%;
width: 100%;
background-color: #333;
}
</style>
<!-- vertex shader -->
<script id="vshader" type="x-shader/x-vertex">
uniform mat4 u_matrix;
attribute vec4 a_vertex;
attribute float a_pointSize;
attribute vec4 a_color;
varying vec4 v_color;
void main() {
// Set the size of the point
gl_PointSize = a_pointSize;
// multiply each vertex by a matrix.
gl_Position = u_matrix * a_vertex;
// pass the color to the fragment shader
v_color = a_color;
}
</script>
<!-- fragment shader -->
<script id="fshader" type="x-shader/x-fragment">
precision mediump float;
varying vec4 v_color;
void main() {
// -- squares
gl_FragColor = v_color;
gl_FragColor =vec4(0.8, 0.1,0.1, 0.9); // v_color;
}
</script>
</head>
<body>
<div id="map"></div>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.css" />
<script src="http://cdn.leafletjs.com/leaflet-0.7.2/leaflet.js"></script>
<script src="http://www.sumbera.com/gist/js/leaflet/canvas/L.CanvasOverlay.js"></script>
<script>
// -- module, require mockups so we can use orig files unmodified
module = {};
reqMap = {
'./elementgroups.js': 'ElementGroups',
'./buffer.js' : 'Buffer'
};
require = function (jsFile) {
return eval(reqMap[jsFile]);
};
</script>
<!-- all mapbox dependency for tesselation of the polyline -->
<script src="http://www.sumbera.com/gist/js/mapbox/pointGeometry.js"></script>
<script src="http://www.sumbera.com/gist/js/mapbox/buffer.js"></script>
<script src="http://www.sumbera.com/gist/js/mapbox/linevertexbuffer.js"></script>
<script src="http://www.sumbera.com/gist/js/mapbox/lineelementbuffer.js"></script>
<script src="http://www.sumbera.com/gist/js/mapbox/elementgroups.js"></script>
<script src="http://www.sumbera.com/gist/js/mapbox/linebucket.js"></script>
<script src="http://www.sumbera.com/gist/data/route.js" charset="utf-8"></script>
<script>
var leafletMap = L.map('map').setView([38.911, -77.0521], 16);
L.tileLayer("http://{s}.sm.mapstack.stamen.com/(toner-background,$fff[difference],$fff[@23],$fff[hsl-saturation@20],toner-lines[destination-in])/{z}/{x}/{y}.png")
.addTo(leafletMap);
var glLayer = L.canvasOverlay()
.drawing(drawingOnCanvas)
.addTo(leafletMap);
var canvas = glLayer.canvas();
glLayer.canvas.width = canvas.clientWidth;
glLayer.canvas.height = canvas.clientHeight;
var gl = canvas.getContext('experimental-webgl', { antialias: true });
var pixelsToWebGLMatrix = new Float32Array(16);
var mapMatrix = new Float32Array(16);
// -- WebGl setup
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, document.getElementById('vshader').text);
gl.compileShader(vertexShader);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, document.getElementById('fshader').text);
gl.compileShader(fragmentShader);
// link shaders to create our program
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
gl.useProgram(program);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
gl.enable(gl.BLEND);
// gl.disable(gl.DEPTH_TEST);
// ----------------------------
var u_matLoc = gl.getUniformLocation(program, "u_matrix");
gl.aPointSize = gl.getAttribLocation(program, "a_pointSize");
pixelsToWebGLMatrix.set([2 / canvas.width, 0, 0, 0, 0, -2 / canvas.height, 0, 0, 0, 0, 0, 0, -1, 1, 0, 1]);
gl.viewport(0, 0, canvas.width, canvas.height);
gl.uniformMatrix4fv(u_matLoc, false, pixelsToWebGLMatrix);
// -- data
var verts = [];
var rawVerts = [];
for (var i = 0 ; i < data.geometry.coordinates.length; i++) {
var d = data.geometry.coordinates[i];
pixel = LatLongToPixelXY(d[1], d[0]);
rawVerts.push(new Point(pixel.x, pixel.y));
}
var u_linewidth = { x: 0.0002 };
// override
LineVertexBuffer.prototype.add = function (point, extrude, tx, ty, linesofar) {
point.x = point.x + (u_linewidth.x * LineVertexBuffer.extrudeScale * extrude.x * 0.015873);
point.y = point.y + (u_linewidth.x * LineVertexBuffer.extrudeScale * extrude.y * 0.015873);
verts.push( point.x, point.y);
return this.index;
};
// -- we don't use these buffers, override them later, just set them for addLine func
var bucket = new LineBucket({}, {
lineVertex: (LineVertexBuffer.prototype.defaultLength = 16, new LineVertexBuffer()),
lineElement: (LineElementBuffer.prototype.defaultLength = 16, new LineElementBuffer())
});
bucket.addLine(rawVerts,"miter","butt",2,1);
// tirangles or point count
var colorLoc = gl.getAttribLocation(program, "a_color");
var vertLoc = gl.getAttribLocation(program, "a_vertex");
var numPoints = verts.length / 2;
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(verts), gl.STATIC_DRAW);
gl.vertexAttribPointer(vertLoc, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(vertLoc);
glLayer.redraw();
function drawingOnCanvas(canvasOverlay, params) {
if (gl == null) return;
gl.clear(gl.COLOR_BUFFER_BIT);
pixelsToWebGLMatrix.set([2 / canvas.width, 0, 0, 0, 0, -2 / canvas.height, 0, 0, 0, 0, 0, 0, -1, 1, 0, 1]);
gl.viewport(0, 0, canvas.width, canvas.height);
var pointSize = Math.max(leafletMap.getZoom() - 4.0, 1.0);
gl.vertexAttrib1f(gl.aPointSize, pointSize);
// -- set base matrix to translate canvas pixel coordinates -> webgl coordinates
mapMatrix.set(pixelsToWebGLMatrix);
var bounds = leafletMap.getBounds();
var topLeft = new L.LatLng(bounds.getNorth(), bounds.getWest());
var offset = LatLongToPixelXY(topLeft.lat, topLeft.lng);
// -- Scale to current zoom
var scale = Math.pow(2, leafletMap.getZoom());
scaleMatrix(mapMatrix, scale, scale);
translateMatrix(mapMatrix, -offset.x, -offset.y);
// -- attach matrix value to 'mapMatrix' uniform in shader
gl.uniformMatrix4fv(u_matLoc, false, mapMatrix);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, numPoints);
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
/*
function latlonToPixels(lat, lon) {
initialResolution = 2 * Math.PI * 6378137 / 256, // at zoomlevel 0
originShift = 2 * Math.PI * 6378137 / 2;
// -- to meters
var mx = lon * originShift / 180;
var my = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
my = my * originShift / 180;
// -- to pixels at zoom level 0
var res = initialResolution;
x = (mx + originShift) / res,
y = (my + originShift) / res;
return { x: x, y: 256- y };
}
*/
// -- converts latlon to pixels at zoom level 0 (for 256x256 tile size) , inverts y coord )
// -- source : http://build-failed.blogspot.cz/2013/02/displaying-webgl-data-on-google-maps.html
function LatLongToPixelXY(latitude, longitude) {
var pi_180 = Math.PI / 180.0;
var pi_4 = Math.PI * 4;
var sinLatitude = Math.sin(latitude * pi_180);
var pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (pi_4)) * 256;
var pixelX = ((longitude + 180) / 360) * 256;
var pixel = { x: pixelX, y: pixelY };
return pixel;
}
function translateMatrix(matrix, tx, ty) {
// translation is in last column of matrix
matrix[12] += matrix[0] * tx + matrix[4] * ty;
matrix[13] += matrix[1] * tx + matrix[5] * ty;
matrix[14] += matrix[2] * tx + matrix[6] * ty;
matrix[15] += matrix[3] * tx + matrix[7] * ty;
}
function scaleMatrix(matrix, scaleX, scaleY) {
// scaling x and y, which is just scaling first two columns of matrix
matrix[0] *= scaleX;
matrix[1] *= scaleX;
matrix[2] *= scaleX;
matrix[3] *= scaleX;
matrix[4] *= scaleY;
matrix[5] *= scaleY;
matrix[6] *= scaleY;
matrix[7] *= scaleY;
}
</script>
</body>
</html>
@donSchoe
Copy link

donSchoe commented Sep 2, 2015

hi stanislav,
thanks for your helpful blog posts and demo scripts.

could you explain how you created the modelview matrix? (lines 138 or 196):

pixelsToWebGLMatrix.set([2 / canvas.width, 0, 0, 0, 0, -2 / canvas.height, 0, 0, 0, 0, 0, 0, -1, 1, 0, 1]);

simplified 4x4 view:

  2 / w,      0, 0, 0,
      0, -2 / h, 0, 0,
      0,      0, 0, 0,
     -1,      1, 0, 1

2/w scales along the canvas width. -2/h scales along the canwas height. but why 2 and -2? and what are -1 and 1 in the last line actually doing?

i was able to create my own webgl overlay for leaflet and thanks to your blogs i was totally able to create a working version of my own code which i fully understand except this matrix. i somehow fail to reverse engineer this one. could you tell me how you calculated this transformation?

thank you so much,
alex

@donSchoe
Copy link

donSchoe commented Sep 2, 2015

ok! i try to answer my own question. i just noticed webgl seems to read matrices in another direction, so the -1 and 1 in the bottom line are a simple translation to ensure [0,0] is in the top left corner.

it's the same as:

translateMatrix(pixelsToWebGLMatrix, -1, 1);
scaleMatrix(pixelsToWebGLMatrix, 2 / canvas.width, -2 / canvas.height);

@Sumbera
Copy link
Author

Sumbera commented Sep 29, 2015

:)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment