Interactive mapping with HTML5, JavaScript, and Canvas

Part 1: Loading, projecting, and drawing geodata

I’m getting into more canvas and JavaScript for interactive mapping. Much of the Flash/ActionScript work I’ve written or come to rely upon is directly portable to JS/canvas. What’s missing is a sweet RIA framework and IDE for the kind of development Flash and Flex have made possible for years.

Luckily it’s not hard to roll our own interactive web map using web standard technologies. In this post I’m just showing off the basics: dynamically loading geodata, projecting it client-side, and rendering to the canvas element.

Hopefully the above map shows up for you. It’s loaded into this blog post with dynamic KML data, projected using the Proj4js library, and drawn onto HTML’s canvas element using JavaScript. You can check out the P.O.C. on a separate page.

Loading geographic data

It all starts with data. Points, polylines, or polygons — typically defined by latitude/longitude coordinates. Your data may be in a CSV file or in a database. For a simple interactive web map it’s best if it’s in a common GIS file format, like the Shapefile or KML.

These days, it’s not too hard to load a geographic layer on top of a web map — using Google Maps or OpenLayers, say. But since we’re looking down the road to interactivity, custom projections, and thematic mapping, it’s best to roll our own. Luckily, getting the data in is pretty easy.

In ActionScript I would use Edwin van Rijkom’s ESRI SHP parser, my own E00 parser, or some simple custom methods I’ve written to load in KML documents. Tom Carden of Stamen has done some great work porting the AS3 SHP library to JavaScript, with additional classes and methods to allow basic layering, panning, and zooming.

Carden’s classes are great; for demo purposes, and to keep this as lightweight as possible, I’ve just written a quick JavaScript method to grab what I need from a KML document:

$.get( "data/kml/generalized_african_countries.kml", function( xml ) {
    var features = new Array();
    $( xml ).find( 'Placemark' ).each( function() {
        var rings = new Array();
        $( this ).find( 'outerBoundaryIs' ).each( function() {
            var ring = new Array();
            var coordsText = $( this ).find( 'coordinates' ).text();
            var coordStrings = coordsText.split( ' ' );
            for ( var coordText in coordStrings ) {
                var coordinate = new Array();
                var coordSplit = coordStrings[ coordText ].split( ',' );
                for ( var coordInd in coordSplit ) coordinate.push( Number( coordSplit[ coordInd ] ) );
                ring.push( coordinate );
            }
            rings.push( ring );
        } );
        features.push( rings );
    } );
       
    /* feature coordinates all loaded -- now do something with them */
       
} );

You’ll notice a bit of jQuery in there. And you’ll also notice that it grabs only coordinate data and works only for polygons. But it produces an array of feature coordinates, which is an array of ring coordinates, which is an array of lat/long coordinates, which is all we need for the current application.

Projecting geographic data

One of my biggest beefs with the typical online map providers is that they’re all rendered in a Mercator projection. No problem for most purposes (and great for producing those 90 degree road intersections), but not so great for country-level mapping and bad for many thematic mapping pursuits. That’s one reason we’re rolling our own here.

PROJ.4 is a generally sweet projections library, originally written in C by Gerald Evenden then of the USGS. It’s been ported to JavaScript as Proj4js. To use it you just have to define source and a dest objects:

Proj4js.defs[ 'albersEqualArea_Africa' ] = '+title= albers_AFR\
                                            +proj=aea\
                                            +lat_1=20\
                                            +lat_2=-23\
                                            +lat_0=0\
                                            +lon_0=25\
                                            +x_0=0\
                                            +y_0=0\
                                            +ellps=WGS84\
                                            +datum=WGS84\
                                            +units=m\
                                            +no_defs'
;
var source = new Proj4js.Proj( 'WGS:84' );
var dest = new Proj4js.Proj( 'albersEqualArea_Africa' );

And thereafter you can call

Proj4js.transform( source, dest, pt );

where pt is any object with x and y properties. So all coordinates gathered from the KML above can be run through the Proj4js.transform() method, in this case applying a custom Albers Equal Area projection (proj=aea) for the African continent.

Drawing geographic data on the canvas element

The results of the above can be easily rendered to HTML’s canvas element using JavaScript. I’m used to ActionScript’s Graphics class, and its assorted vector drawing methods. Of course, given the common ECMAScript heritage, the JS methods are nearly identical. So the projected linework is rendered thusly:

function drawPolygonFeatures( features, minX, maxX, minY, maxY )
{
    var c_canvas = document.getElementById( "map" );
    var context = c_canvas.getContext("2d");
    var multiFactor = Math.min( c_canvas.width / ( maxX - minX ), c_canvas.height / ( maxY - minY ) );
    var x = 0; var y = 0;
    for ( var featureNum in features ) {
        for ( var ringNum in features[ featureNum ] ) {
            var ring = features[ featureNum ][ ringNum ];
            context.moveTo( ( ring[ 0 ][ 0 ] - minX ) * multiFactor, c_canvas.height - ( ring[ 0 ][ 1 ] - minY ) * multiFactor );                  
            for ( var coordNum = 1; coordNum < ring.length; coordNum++ ) {
                x = ( ring[ coordNum ][ 0 ] - minX ) * multiFactor;
                y = c_canvas.height - ( ring[ coordNum ][ 1 ] - minY ) * multiFactor;
                context.lineTo( x, y );
            }
        }
    }
    context.shadowOffsetX = context.shadowOffsetY = 3;
    context.shadowBlur    = 4;
    context.shadowColor   = 'rgba(0, 0, 0, 0.5)';
    context.fillStyle = "#0099cc";
    context.fill();
    context.shadowOffsetX = context.shadowOffsetY = context.shadowBlur = 0;
    context.strokeStyle = "#fff";
    context.stroke();
}

That method’s made a bit longer by that bitchin’ drop shadow (sorry Firefox, but you Konqueror folks should be cool). See above, or the P.O.C. on a separate page.

Up next

So far this has been pretty sweet: we’ve loaded coordinate data dynamically, projected it, and drawn it to the canvas element. But it hasn’t exactly lived up to the “interactive” part of the title. Next time I hope to get going on panning and zooming, feature mouse-over, and perhaps even attribute loading and thematic mapping.

14 Comments

  1. Great writeup. Thanks!

    Your too right about the need for a decent IDE and better frameworks. HTML5 will get big gains the as soon as we get some decent tools.

    Posted June 15, 2010 at 8:28 am | Permalink
  2. Great to see you are getting into HTML5. We are on the process of chaging from Flash to HTML5 to do very interactive map visualizations. We are missing Tools as you say (this try and error way of working is horrible), plus pixel manipulation.

    Posted June 16, 2010 at 5:14 am | Permalink
  3. Bitchin drop shadow looks bitchin in Firefox 3.6.3.

    Joel
    Posted June 21, 2010 at 6:31 pm | Permalink
  4. This is great info. Is there a way to import data directly from a flat text file, or csv? What about for territory mapping and alignment?

    Posted August 5, 2010 at 7:33 am | Permalink
  5. Looks promising. How would you go about making the different countries more interactive with rollovers and anchors etc?

    That would be really cool and no Flash!

    Adam
    Posted September 13, 2010 at 10:46 am | Permalink
  6. MINI CANVAS

    transform photos into works of art by printing them onto high quality real fibre artist cotton canvas prints.

    Wyne
    Posted November 17, 2010 at 7:21 am | Permalink
  7. Besides the fact that there are no IDE’s, there is no API. The Canvas is not interactive, and is much lower than , say a flex Canvas. There is no Sprite or UIComponent object in JS… So, no, there is not a direct mapping from AS3 to JS.

    var s = new Sprite();
    s.x = 50;
    s.y = 50;

    canvas.addChild(s);

    Posted March 16, 2011 at 1:10 pm | Permalink
  8. Hi Zach,
    I am currently working on a project for Gothenburg university, Sweden, and I would like to get in contact with you. I have some questions to ask you, and perhaps you could help us out with an application for the website we’re creating. Please send me an email, and I’ll tell you more about it.

    /Richard

    Richard Svensson
    Posted April 2, 2011 at 6:30 am | Permalink
  9. Is it possible to put links on the states?

    macangelo
    Posted May 13, 2011 at 1:35 am | Permalink
  10. Are you by any chance using the ex-canvas script (http://code.google.com/p/explorercanvas/) to make the canvas element compatible with IE? When I test the page in IE, the graphic of Africa doesn’t render.

    Emily
    Posted January 17, 2012 at 1:49 pm | Permalink
  11. I loved as much as you’ll receive carried out right here. The sketch is attractive, your authored material stylish. nonetheless, you command get got an edginess over that you wish be delivering the following. unwell unquestionably come further formerly again since exactly the same nearly a lot often inside case you shield this hike.

    Posted March 21, 2012 at 11:28 am | Permalink
  12. Loading dynamic data can some times be a task. Wonderful projection that you give here.

    Posted June 29, 2013 at 1:40 am | Permalink
  13. So how can you use an area rug to create a dramatic effect in different rooms.

    The professional pet photography experts are flexible to either have the photo shoots in their studios, at your home or even in the outdoor settings
    where the dogs are very much comfortable to catch those natural moments off guard.
    You don’t have to spend a fortune to decorate your home.

    Posted June 13, 2014 at 5:32 am | Permalink
  14. hermes replica bags birkin indiemaps.com/blog » Interactive mapping with HTML5, JavaScript, and Canvas

    Posted July 12, 2014 at 3:04 am | Permalink

3 Trackbacks

  1. By Linkroll for June 15th « Dialogue Earth on June 15, 2010 at 6:40 pm

    [...] Shared Interactive mapping with HTML5, JavaScript, and Canvas. [...]

  2. [...] HTML5 over Flash http://www.tile5.org/ – Mostly for mobile http://indiemaps.com/blog/2010/06/interactive-mapping-with-html5-javascript-and-canvas/ [...]

  3. [...] what about KML format? You could use KML and JavaScript as well and IndieMaps has a good writeup on mapping with HTML5, JavaScript, and Canvas plus a proof of concept for a map of Africa. Looks good but I guess you have to figure out how to [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *