Offline Mapping with C1Maps

The C1Maps control adds data binding and vector graphics support to Microsoft® Bing Maps, as well as support for rendering geographic KML files. C1Maps can also be easily customized to display maps from other sources such as OpenStreetMap. In the latest version of C1Maps for WinRT, we’ve made it possible to display offline maps for connection-less apps and on-the-go users. In this article I will walk through building a Universal Windows app using OpenStreetMap tiles offline with the C1Maps control. There are 2 steps to building offline maps with C1Maps:

  1. Download and obtain map tile images. In my sample I will use a custom tool to help me download the tiles from OpenStreetMap.
  2. Create a custom tile source (C1MultiScaleTileSource) that handles loading the tile images from some offline location. In my sample I will load images from my application’s resources.

After I have my map configured I will then plot some location-based data on the map to show it still works like an online map. So let’s get started.

Downloading Map Tile Images

When working with offline maps we generally still need to first obtain the tiles from some online source. In my scenario it’s a one-time process of downloading. I don’t want the app to require any online activity, so I will download the tiles once myself and distribute them with my application. You have to be careful where you get your tiles because not all services legally allow this. Bing Maps, for instance, does not allow offline storage of map tiles even if you find a way to make it happen. Fortunately, OpenStreetMap does allow offline tile storage. You just have to give copyright notice to OpenStreetMap contributors in your app. You can read the full legalese from the link below. http://www.openstreetmap.org/copyright So how do you download tiles from OpenStreetMap? It’s a great question and one that’s not easy to answer because OpenStreetMap does not provide a free tool to do this. There are some you will find on the Internet, but the ones I tried didn’t work or were way too complicated for me. You could take screen captures but it’s not recommended because the images need to be organized and labeled in such a way that the map viewer knows exactly where in the world (lat and long) these tiles belong. So we at ComponentOne built a tool, just like any other free tool out there, which simply takes all of the tiles currently viewed in C1Maps from OpenStreetMap and saves them to your local machine. It saves and names the images precisely in order so they can be easily imported into a custom tile source, which I will cover in the next step. Download the ComponentOne OpenStreetMap Tile Downloader To begin downloading tiles, download and unzip the Tile Downloader Tool and run the EXE. It will look like this below: DownloadTileImages1 Position the map and click the Download Tile Images button. The Download Settings window appears. All of the tiles that are currently in view will be saved to the output folder you select. For example, if you select Zoom Level = 3 and download the images, you will see a folder named “4” since the zoom level is zero-based. Inside the 4 folder will be more sub-folders each representing an indexed column of tiles. At each zoom level these indexes represent the entire world from the International Date Line moving east. The folder structure and naming may not make a lot of sense to us, but it will be understood by the C1Maps control. TileFolders The tool has some advanced capabilities as well. You can explicitly define the latitude and longitude rather than using the map viewer. You can also supply network credentials if you are limited by proxy. DownloadSettings For my sample I will download the tiles for Zoom Levels 2 and 3 of the United States. In my output folder I will have two folders named 3 and 4 that represent the images for each zoom level. Next, I need to add these folders of images to my project in the exact same structure as they downloaded. This is so they will be included in my output at resources. And then I'm ready to create the custom tile source that will display these images in my C1Maps control. Solution_Tiles

Creating a Custom Tile Source

You can create your own custom tile source to be used with C1Maps using the C1MultiScaleTileSource base class. It has a single GetTileLayers method to override. Within GetTileLayers we can point to where to load tile images from. By default, C1Maps uses a web source, but in a custom source you can point to some offline location. In WPF you could point to images anywhere on the users’ machine. In WinRT our options are a bit more limited, so for my sample I’m going to point to my app’s resources. You can place the following class in the shared module of your app.


public class OfflineMapsSource : C1MultiScaleTileSource  
{  
    private const string uriFormat = @"ms-appx:/Tiles/{Z}/{X}/{Y}.png";  

    public OfflineMapsSource()  
        : base(0x8000000, 0x8000000, 0x100, 0x100, 0)  
    { }  

    protected override void GetTileLayers(int tileLevel, int tilePositionX, int tilePositionY, IList<object> source)  
    {  
        if (tileLevel > 8)  
        {  
            var zoom = tileLevel - 8;  
            var uri = uriFormat;  

            uri = uri.Replace("{X}", tilePositionX.ToString());  
            uri = uri.Replace("{Y}", tilePositionY.ToString());  
            uri = uri.Replace("{Z}", zoom.ToString());  
            source.Add(new Uri(uri));  
        }  
    }  
}  

GetTileLayers works quite simply by fetching the tile at the X, Y and Zoom level requested by the viewer. So the real key to working offline here is building the URI that points to offline. If you were working in WPF, an example URI could look like:


string uri = string.Format(@"{0}\\MyTiles\\{Z}\\{X}\\{Y}.png", Directory.GetCurrentDirectory()));  

In WinRT, we use the “ms-appx:/” prefix to access a local resource. In GetTileLayers we have to subtract 8 from tileLevel to calculate the requested zoom level. The tileLevel is in relation to pixel coordinates, where as, the zoom is based on tile image coordinates. Add a C1Maps control to your page or in a shared UserControl. Initialize your custom tile source by setting it to the Source property.


// initialize offline map source  
c1Maps.Source = new OfflineMapsSource();  

Now we have a working offline map. Before running I will also add a marker to a real geographic location for testing. Here is a quick snippet that creates a vector layer with a single placemark.


// add a mark @ Pittsburgh, Pennsylvania  
C1VectorLayer layer = new C1VectorLayer();  
C1VectorPlacemark mark = new C1VectorPlacemark();  
mark.GeoPoint = new Point(-80, 40.4417);  
mark.Geometry = new EllipseGeometry() { RadiusX = 5, RadiusY = 5 };  
mark.Fill = new SolidColorBrush(Colors.Red);  
layer.Children.Add(mark);  
c1Maps.Layers.Add(layer);  

And that's it! OfflineMappingApp1 You can download the complete, working Universal Windows App sample below. The sample has shared 100% of the code, XAML and tile images between the Windows and Windows Phone apps. Download Maps_Offline_WinRT Sample

ComponentOne Product Manager Greg Lutz

Greg Lutz

comments powered by Disqus