Modest Maps

DIY Map Provider Tutorial

One of the core features of Modest Maps is support for custom geographical tile sets. This tutorial describes how to turn a map found on the web into a suitable map provider for Modest Maps with the help of Zoomifyer. We start with a map of AC Transit's bus lines in Oakland, found on 511.org's AC Transit schedule and route collection (downloadable PDF's are in the right-hand column):

  1. The PDF map must first be converted into a bitmap image. We open it in Adobe Photoshop, crop off the edges, and save it to a .psd file. This will be the base map. The AC Transit example results in a 100MB file, 11258 pixels wide and 7085 pixels tall.

  2. To accurately place geographical markers on the map, we have to provide some hints in the form of three well-chosen marker points. Good markers are widely-spaced, forming a broad triangle covering as much of the map surface as possible. Here, we choose points in Berkeley (top-left corner), Alameda (bottom-center), and East Oakland (right-center). Find the latitude and longitude of each marker point with a free tool such as Pierre Gorissen's Lat., Long. Popup.

    Also note the x, y position of each marker point in the base image. Here is Photoshop's info palette showing the current mouse position:

  3. Enter the latitude and longitude degrees for each point in our map transformation calculator, along with the row (y) and column (x). The zoom can be determined from the width and height of the base image:

    1. Take larger of the width and height, in this case 11258.
    2. Divide its logarithm by the logarithm of 2. Here, log(11258) / log(2) = 13.439701
    3. Round that up. Here we get 14.

    The choice of projection (linear vs. mercator) matters, but at the street/city scale it can probably be ignored. We choose Mercator. Calculating these values outputs a piece of Actionscript code we will use in our map provider class:

    var t:Transformation = new Transformation(1449749.02835779, -2150945.931013119, 4632165.032378035,
                                              -2162454.5923735118, -1441023.4254683803, -3581364.9874006994);
    
    __projection = new MercatorProjection(14, t);
                
  4. We then run the file through Zoomifyer EZ, a tool for converting large bitmaps into small tile images. After it's done its job, you'll have a directory with a file called "ImageProperties.xml" and a bunch of sub-directories called "TileGroup0", "TileGroup1", etc. Throw the whole lot onto a public web server someplace. Here's ours:

    Note that all the images are 256 x 256 JPEG's, at a variety of scales.

  5. This is enough information to create our sub-class of AbstractZoomifyMapProvider, ACTransitMapProvider. Remember the URL of the directory where the tile images are stored (step 4, "http://actransit.modestmaps.com/"), the width and height of the base image (step 1, 11258 x 7085), and the calculated transformation and projection (step 3).

    New sets of map tiles can be used by implementing the IMapProvider interface. New sets of Zoomifyer map tiles can be used by sub-classing the AbstractZoomifyMapProvider class.

    Ours looks like this:

    import org.casaframework.event.DispatchableInterface;
    import com.modestmaps.core.Coordinate;
    import com.modestmaps.geo.MercatorProjection;
    import com.modestmaps.geo.Transformation;
    import com.modestmaps.mapproviders.AbstractZoomifyMapProvider;
    import com.modestmaps.mapproviders.IMapProvider;
    
    class ACTransitMapProvider
    extends AbstractZoomifyMapProvider
    implements IMapProvider, DispatchableInterface
    {
        public function ACTransitMapProvider()
        {
            super();
            defineImageProperties('http://actransit.modestmaps.com/', 11258, 7085);
            
           /*
            * Euclid Ave. & Ridge Rd., Berkeley
            * 37.876022, -122.260365
            *   = coord(2296, 172, 14)
            * 
            * Monarch St. & W. Tower Ave., Alameda
            * 37.783503, -122.308559
            *   = coord(7061, 3350, 14)
            * 
            * 140th Ave. & E 14th. St., Oakland
            * 37.713159, -122.139795
            *   = coord(2929, 10960, 14)
            */
            var t:Transformation = new Transformation(1449749.02835779, -2150945.931013119, 4632165.032378035,
                                                      -2162454.5923735118, -1441023.4254683803, -3581364.9874006994);
            
            __projection = new MercatorProjection(14, t);
        }
    
        public function toString():String
        {
            return "AC_TRANSIT";
        }
    }
                
  6. Place the new map provider class, ACTransitMapProvider.as, in the lib directory where Flash can find it. Modify one of the included Modest Maps sample clients to point to this new provider, and select an appropriate starting extent. Here, we modify part of SampleClient2.fla:

    import com.stamen.twisted.Reactor;
    import com.modestmaps.Map;
    import com.modestmaps.geo.Location;
    
    // Mandatory first step!
    Reactor.run(_root, undefined, 50);
    
    // Attach, initialize...
    container.attachMovie(Map.symbolName, 'map', 1);
    container.map.init(800, 600, true, new ACTransitMapProvider());
    
    // Start at Lake Merritt
    container.map.setExtent([new Location(37.810665, -122.262297),
                             new Location(37.808767, -122.248907),
                             new Location(37.798459, -122.260237)]);
                
  7. The end result is a draggy-zoomy map of AC Transit's route map: