Skip to content

Instantly share code, notes, and snippets.

@Sumbera
Last active February 12, 2024 16:20
Show Gist options
  • Star 6 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save Sumbera/c6fed35c377a46ff74c3 to your computer and use it in GitHub Desktop.
Save Sumbera/c6fed35c377a46ff74c3 to your computer and use it in GitHub Desktop.
Many points with Leaflet WebGL

Leaflet and WebGL sample rendering 80T points for more info read blog post

inspired by this and by very nice WebGL tutorial here

<!doctype html>
<html>
<head>
<title>Many Points with leaflet 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() {
float border = 0.05;
float radius = 0.5;
vec4 color0 = vec4(0.0, 0.0, 0.0, 0.0);
vec4 color1 = vec4(v_color[0], v_color[1], v_color[2], 0.2);
vec2 m = gl_PointCoord.xy - vec2(0.5, 0.5);
float dist = radius - sqrt(m.x * m.x + m.y * m.y);
float t = 0.0;
if (dist > border)
t = 1.0;
else if (dist > 0.0)
t = dist / border;
// float centerDist = length(gl_PointCoord - 0.5);
// works for overlapping circles if blending is enabled
gl_FragColor = mix(color0, color1, t);
/*
// -- another way for circle
float centerDist = length(gl_PointCoord - 0.5);
float radius = 0.5;
// works for overlapping circles if blending is enabled
gl_FragColor = vec4(v_color[0], v_color[1], v_color[2], 0.2 * step(centerDist, radius));
*/
/*
// simple circles
float d = distance (gl_PointCoord, vec2(0.5,0.5));
if (d < 0.5 ){
gl_FragColor =vec4(v_color[0], v_color[1], v_color[2], 0.2);
}
else{
discard;
}
*/
// -- squares
//gl_FragColor = v_color;
//gl_FragColor =vec4(v_color[0], v_color[1], v_color[2], 0.2); // 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 src="http://www.sumbera.com/gist/86T.js" charset="utf-8"></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")
//L.tileLayer("http://{s}.sm.mapstack.stamen.com/(toner-lite,$fff[difference],$fff[@23],$fff[hsl-saturation@20])/{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");
var colorLoc = gl.getAttribLocation(program, "a_color");
var vertLoc = gl.getAttribLocation(program, "a_vertex");
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 = [];
data.map(function (d, i) {
pixel = LatLongToPixelXY(d[0], d[1]);
//-- 2 coord, 3 rgb colors interleaved buffer
verts.push(pixel.x, pixel.y, Math.random(), Math.random(), Math.random());
});
var numPoints = data.length ;
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);
gl.vertexAttribPointer(vertLoc, 2, gl.FLOAT, false,fsize*5,0);
gl.enableVertexAttribArray(vertLoc);
// -- offset for color buffer
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.POINTS, 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>
@robertleeplummerjr
Copy link

I was so inspired by your work (along with many others) by your fantastic blog post, I went and turned your example here into a library for reuse. http://robertleeplummerjr.github.io/Leaflet.glify/
Great work, you have no idea how important this is.

@Sumbera
Copy link
Author

Sumbera commented Sep 29, 2015

Hi Robert, thanks for your comment ! it is great to see it is useful.:)

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