Skip to content

Instantly share code, notes, and snippets.

@Sumbera
Last active Feb 27, 2021
Embed
What would you like to do?
WebGL polygon triangulation with earcut.js

Simple earcut 1.4 test of triangulation for polygons for webgl drawing. Viedo of 250MB large geojson file:

see discussion also here

<!doctype html>
<html>
<head>
<title>Many polygons with leaflet, earcut and WebGL</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.a = 0.8;
// 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 = {};
</script>
<script src="http://www.sumbera.com/gist/js/earcut/earcut-1.4.2.js"></script>
<script src="http://www.sumbera.com/gist/data/CZDistricts.js"></script>
<!-- <script src="../data/obceS.js"></script> -->
<script>
var leafletMap = L.map('map').setView([50.00, 14.44], 8);
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);
// ----------------------------
// look up the locations for the inputs to our shaders.
var u_matLoc = gl.getUniformLocation(program, "u_matrix");
gl.aPointSize = gl.getAttribLocation(program, "a_pointSize");
// Set the matrix to some that makes 1 unit 1 pixel.
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 = [];
//-- verts only
var start = new Date();
for (var f = 0; f < data.features.length ; f++) {
rawVerts = [];
var feature = data.features[f];
//***
for (var i = 0; i < feature.geometry.coordinates[0].length; i++) {
rawVerts.push([feature.geometry.coordinates[0][i][1], feature.geometry.coordinates[0][i][0]]);
}
rawVerts.pop();
currentColor = [Math.random(), Math.random(), Math.random()]; //[0.1, 0.6, 0.1];
var triangles = earcut([rawVerts]);
for (var i = 0; i < triangles.length; i++) {
pixel = LatLongToPixelXY(triangles[i][0], triangles[i][1]);
verts.push(pixel.x, pixel.y, currentColor[0], currentColor[1], currentColor[2]);
}
}
console.log("updated at " + new Date().setTime(new Date().getTime() - start.getTime()) + " ms ");
// tirangles or point count
var numPoints = verts.length / 5;
console.log("num points: " + numPoints);
var vertBuffer = gl.createBuffer();
var vertArray = new Float32Array(verts);
var fsize = vertArray.BYTES_PER_ELEMENT;
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertArray, gl.STATIC_DRAW);
var vertLoc = gl.getAttribLocation(program, "a_vertex");
gl.vertexAttribPointer(vertLoc, 2, gl.FLOAT, false, fsize * 5, 0);
gl.enableVertexAttribArray(vertLoc);
// -- offset for color buffer
var colorLoc = gl.getAttribLocation(program, "a_color");
gl.vertexAttribPointer(colorLoc, 3, gl.FLOAT, false, fsize * 5, fsize * 2);
gl.enableVertexAttribArray(colorLoc);
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.TRIANGLES, 0, numPoints);
}
// Returns a random integer from 0 to range - 1.
function randomInt(range) {
return Math.floor(Math.random() * range);
}
// -- 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment