Tuesday, February 5, 2013

Displaying WebGL data on Google Maps

Off-topic:
I haven't really payed too much attention to my blog lately. A mix of several projects have kept me occupied and without much time to write. Anyway, I've got some posts on the "pipeline" and I'll try to make up for the lost time :)


On-topic:
Recently I watched a video from the Google Maps Team (don't remember the link, sorry) were I was blown away by a demo using WebGL on top of a map. Basically it displayed thousands and thousands of points on a map without any performance hit whatsoever. I was very interested in exploring the demo and fortunately the Google lads were nice enough to create a simplified version of it inside the Google Maps Utility Library.

You can view this demo directly from the library repository here.





So, I'll begin by editing this example to show the same information set that I used in a previous blog-post, particularly one about using Canvas to display thousands of points in Bing Maps (link).



Basically it contains all the localities of mainland Portugal (about 140.000 points) and should be a nice test for this technology.

The canvas version fared itself quite nicely, but I expect the webgl to perform much better.









Regarding WebGL, and if you're not familiar with it (I'm not an expert myself) you might be a little bit startled by its complexity. WebGL is based on OpenGL ES 2.0, on which you basically have to do "everything" by hand as you've got to program shaders.

It's important to mention that WebGL is only supported on state of the art browsers, hence IE does not support it (not even IE 10). Anyway, if you're reading my blog you're certainly a person of good taste, and are probably using one of the latest versions of Chrome or Firefox :D.

Disclaimer: This post is not a WebGL tutorial. If you want more info on this technology check this online tutorial.

Now here's what I want to do:
  • Replace the random points with real points loaded from a file (about 140.000 points)
  • Change the points size according to the zoom level 

The google maps toolkit WebGL demo displays thousands of random points inside a square in the United States. If you look at the code you see that the square bounds are:

      x: [40;80]   y: [88, 109]

But those aren't geographic coordinates, so what are they? Well, the points are basically represented in the Spherical Mercator projection used in Google Maps at zoom level zero, which is represented as a 256x256 tile.


To draw my points, which are in WGS84 coordinates, I've got to convert them to the spherical mercator projection, thus using the following function:
var pi_180 = Math.PI / 180.0;
var pi_4 = Math.PI * 4;

function latLongToPixelXY(latitude, longitude) {

    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;
}
Then I fill the points buffer. The points should be stored as: [x1 y1 x2 y2 x3 y3 ... ]:
var rawData = new Float32Array(2 * points.length);
for (var i = 0; i < points.length; i++) {
    var pixel = latLongToPixelXY(points[i].lat, points[i].lon);
    rawData[i * 2] = pixel.x;
    rawData[i * 2 + 1] = pixel.y;
}
Now, for the final touch, I'll modify the Vertex shader so that the points size varies according to the zoom level. First of all, add a new attribute called "aPointSize" to the vertex shader and use that value to set the gl_PointSize.
<script id="pointVertexShader" type="x-shader/x-vertex">
    attribute vec4 worldCoord;
    attribute float aPointSize;

    uniform mat4 mapMatrix;

    void main() {
        // transform world coordinate by matrix uniform variable
        gl_Position = mapMatrix * worldCoord;
        gl_PointSize = aPointSize;

    }
</script>
Now, when creating the shader program, we need to get the attribute:
gl.aPointSize = gl.getAttribLocation(pointProgram, "aPointSize");
Afterwards, each time the zoom level is changed, we need to set this attribute (the formula used for the point size is a little bit "empirical"):
var pointSize = Math.max(currentZoom - 6.0, 1.0);
gl.vertexAttrib1f(gl.aPointSize, pointSize);
And voilá, a map with 140.000 points drawn continuously using WebGL.


You can also view the running demo here.

I'm not particularly impressed with the result as it flickers a little bit with large resolutions. Anyway, WebGL is certainly the future, and I'll probably revisit this demo later to improve it a little bit.

6 comments:

  1. This comment has been removed by the author.

    ReplyDelete
  2. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Great to see your working example. There aren't many WebGL + GMaps mashups out there.

      Best regards,
      David Rodecker
      Founder & CTO, LocalSplash
      getting local business online

      Delete
  3. Hi, Good effort. just wondering is it possible to change the points color based on an attribute? i.e. if the data source is
    [lat1,lon1,attribute1],[lat2,lon2,attribute1] and [lat3,lon3,attribute2]

    attribute1 points will have a red color and attribute2 will have green color.

    ReplyDelete
  4. Yes it this, pretty similar to what I'm doing with the point size.

    ReplyDelete