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.

RIP Jacques Bertin

jacques bertin

French cartographer Jacques Bertin died in Paris on May 3rd. This news has been out on a few English-language lists, but I otherwise hadn’t seen it mentioned, so thought I’d note it here. Bertin’s Semiologie and seven visual variables greatly influenced cartographic and graphic design theory and education over the last 40 years. Sadly out of print for years, his Semiology of Graphics will be reissued in extended form by ESRI Press this November.

photo © pavilion tone

indiemapper

Axis Maps (Andy, Ben, Dave, Mark, and I) just released indiemapper, a sweet browser-based thematic and reference mapping application.

For some more indie maps, check out our gallery. To try it out, just head over to indiemapper.com: you can sign up for a free 30 day trial without entering a credit card; after your trial, indie is just $30/month ($20 academic). Oh, and I guess there’ll be a free version, but it’d be pretty embarrassing if you were caught using that.

A little history

Indiemapper took a long time coming. The seeds were planted in December 2007. I had just discovered Edwin van Rijkom’s AS3 SHP library, which lets you load and draw ESRI shapefiles at run-time in Flash. Using widely available formulae for simple projections like Mercator and Lambert conformal conic I began loading and projecting shapefiles in Flash at run-time pretty quickly. These were heady times, and this seemed pretty cool.

Andrew “Woody” Woodruff joined in and we set off to create a full-fledged thematic mapping tool in Flash. We designed the product for ourselves, honestly, and the cartographers we knew. We hoped it’d be useful to other map-makers, whether casual or professional, who may not need nor want to learn the extensive analytical capabilities of a modern GIS, but do want a multitude of symbology and design options for their geographical data.

We presented a super-early version of indiemapper to MACDAD on May 9, 2008.

As you can see, the basic workflow and trademark indie (#0099cc) blue were already in place.

We had some pretty gnarly styling panels, though; at this point we were just working in Flash and creating all the panels from scratch.

Of course things like getting Master’s degrees, real paying projects, moving, and having actual jobs (however briefly) got in the way, and development on indiemapper didn’t restart for another year or so. Axis Maps (where Andy became a partner in Summer 2008) took up development of indie while I went and did something else for a while. Axis Maps released indieprojector as a preview in May 2009, not realizing it’d be another 11 months before the full thing was ready. I joined the team later in 2009, and have been co-developer (with Andy) since.

Development

Mark and Ben are our static and interaction designers here, with Mark also providing the “learn more” content, drawing from his years of academic cartography experience. Dave is our project manager, marketing guru, and AJAX guy; he created the backend to our user management and sharing systems.

Andy and I both wrote AS3 code and MXML markup, but he was mostly focused on the former with me working more on the latter. This meant I programmed more of the framework and UI, while Andy was rocking the math-heavy projections work and all the exporting/imp/svg stuff.

When I joined the company, I immediately set to work refactoring the indiemapper code base to incorporate the MVC framework Mate. Mate, as they say, is “a tag-based, event-driven Flex framework”. I had to use Cairngorm for work a while back, and by comparison Mate is quite lightweight and unobtrusive. There’s much more to say here, but I’ll save that for later.

Though it’s old school what with FXG, I still really like Degrafa, and used it a fair amount for skinning. Aside from Mate, Degrafa, and a few open source Flex components, we pretty much wrote our own little AS3 GIS SDK.

This is just the beginning

Given our platform and development philosophy, we can quickly respond to our users’ needs in a thematic and reference mapping tool. Please let us know what you want.

I think our first goals are:

  • performance improvements so you can load larger geodata files faster
  • attribute editing and joining
  • more charting options for your data as an aid to classification and symbology selection
  • more symbologies, like chorodot and additional cartogram styles
  • custom choropleth color schemes and graduated symbol size schemes
  • line generalization

But let us know what else you’d like to see. We’ve already been able to respond to a few new feature requests and, oh, maybe one or two bugs in our first week.