How to overlay tiled images on a map

Overlay third-party or custom tiled images on the map displayed in the MapControl by using tile sources. Use tile sources to overlay specialized information - for example, weather data, population data, or seismic data - or even to replace the default map entirely.

You can overlay tiled images from the following sources:

The MapControl and the classes described in this topic belong to the Windows.UI.Xaml.Controls.Maps namespace.

Important  

Your app has to be authenticated before it can use many features of the Map control and map services. For more info, see How to authenticate your Maps app.

 

About tile sources

Map services such as Nokia Maps and Bing Maps cut maps into square tiles for quick retrieval and display. These tiles are 256 pixels by 256 pixels, and are pre-rendered at multiple levels of detail. Many third-party services also provide map-based data that's cut into tiles. Use tile sources to retrieve third-party tiles, or to create your own custom tiles, and overlay them on the map displayed in the MapControl.

Important  

When you use tile sources, you don't have to write code to request or to position individual tiles. The MapControl requests tiles as it needs them. Each request specifies the X and Y coordinates and the zoom level for the individual tile. You simply specify the format of the Uri or filename to use to retrieve the tiles in the UriFormatString property. That is, you insert replaceable parameters in the base Uri or filename to indicate where to pass the X and Y coordinates and the zoom level for each tile.

Here's an example of the UriFormatString property for an HttpMapTileDataSource that shows the replaceable parameters for the X and Y coordinates and the zoom level.

    http://www.<web service name>.com/z={zoomlevel}&x={x}&y={y}

 

(The X and Y coordinates represent the location of the individual tile within the map of the world at the specified level of detail. The tile numbering system starts from {0, 0} in the upper left corner of the map. For example, the tile at {1, 2} is in the second column of the third row of the grid of tiles.)

For more info about the tile system used by mapping services, see Bing Maps Tile System.

Overlaying tiled images from a tile source on a map

To overlay tiled images on a map, do the following things.

  1. Instantiate one of the three tile data source classes that inherit from MapTileDataSource.

    Configure the UriFormatString to use to request the tiles by inserting replaceable parameters in the base Uri or filename.

    The following example instantiates an HttpMapTileDataSource. This example specifies the value of the UriFormatString in the constructor of the HttpMapTileDataSource.

                HttpMapTileDataSource dataSource = new HttpMapTileDataSource(
                    "http://www.<web service name>.com/z={zoomlevel}&x={x}&y={y}");
    
  2. Instantiate and configure a MapTileSource. Specify the MapTileDataSource that you configured in the previous step as the DataSource of the MapTileSource.

    The following example specifies the DataSource in the constructor of the MapTileSource.

                MapTileSource tileSource = new MapTileSource(dataSource);
    

    You can restrict the conditions in which the tiles are displayed by using properties of the MapTileSource.

    • Display tiles only within a specific geographic area by providing a value for the Bounds property.
    • Display tiles only at certain levels of detail by providing a value for the ZoomLevelRange property.

    Optionally, configure other properties of the MapTileSource that affect the loading or the display of the tiles, such as Layer, AllowOverstretch, IsRetryEnabled, and IsTransparencyEnabled.

  3. Add the MapTileSource to the TileSources collection of the MapControl.

                MapControl1.TileSources.Add(tileSource);
    

Overlaying tiles retrieved from a web service

Overlay tiled images retrieved from from a web service by using the HttpMapTileDataSource.

  1. Instantiate an HttpMapTileDataSource.

  2. Specify the format of the Uri that the web service expects as the value of the UriFormatString property. To create this value, insert replaceable parameters in the base Uri. For example, in the following code sample, the value of the UriFormatString is:

        http://www.<web service name>.com/z={zoomlevel}&x={x}&y={y}
    

    The web service has to support a Uri that contains the replaceable parameters {x}, {y}, and {zoomlevel}. Most web services (for example, Nokia, Bing, and Google) support Uris in this format. If the web service requires additional arguments that aren't available with the UriFormatString property, then you have to create a custom Uri. Create and return a custom Uri by handling the UriRequested event. For more info, see the section Providing a custom Uri later in this topic.

  3. Then follow the remaining steps described previously in the section Overlaying tiled images on a map.

The following example overlays tiles from a fictitious web service on a map of North America. The value of the UriFormatString is specified in the constructor of the HttpMapTileDataSource. In this example, tiles are only displayed within the geographic boundaries specified by the optional Bounds property.

        private void AddHttpMapTileSource()
        {
            // Create the bounding box in which tiles are displayed.
            // This example represents North America.
            BasicGeoposition northWestCorner =
                new BasicGeoposition() { Latitude = 48.38544, Longitude = -124.667360 };
            BasicGeoposition southEastCorner =
                new BasicGeoposition() { Latitude = 25.26954, Longitude = -80.30182 };
            GeoboundingBox boundingBox = new GeoboundingBox(northWestCorner, southEastCorner);

            // Create an HTTP data source.
            // This example retrieves tiles from a fictitious web service.
            HttpMapTileDataSource dataSource = new HttpMapTileDataSource(
                "http://www.<web service name>.com/z={zoomlevel}&x={x}&y={y}");

            // Optionally, add custom HTTP headers if the web service requires them.
            dataSource.AdditionalRequestHeaders.Add("header name", "header value");

            // Create a tile source and add it to the Map control.
            MapTileSource tileSource = new MapTileSource(dataSource);
            tileSource.Bounds = boundingBox;
            MapControl1.TileSources.Add(tileSource);
        }
void MainPage::AddHttpMapTileSource()
{
       BasicGeoposition northWest = { 48.38544, -124.667360 };
       BasicGeoposition southEast = { 25.26954, -80.30182 };
       GeoboundingBox^ boundingBox = ref new GeoboundingBox(northWest, southEast);

       auto dataSource = ref new Windows::UI::Xaml::Controls::Maps::HttpMapTileDataSource(
             "http://www.<web service name>.com/z={zoomlevel}&x={x}&y={y}");

       dataSource->AdditionalRequestHeaders->Insert("header name", "header value");

       auto tileSource = ref new Windows::UI::Xaml::Controls::Maps::MapTileSource(dataSource);
       tileSource->Bounds = boundingBox;

       this->MapControl1->TileSources->Append(tileSource);
}

Overlaying tiles stored as files in local storage

Overlay tiled images stored as files in local storage by using the LocalMapTileDataSource. Typically you package and distribute these files with your app.

  1. Instantiate a LocalMapTileDataSource.

  2. Specify the format of the file names as the value of the UriFormatString property. To create this value, insert replaceable parameters in the base filename. For example, in the following code sample, the value of the UriFormatString is:

        Tile_{zoomlevel}_{x}_{y}.png
    

    If the format of the file names requires additional arguments that aren't available with the UriFormatString property, then you have to create a custom Uri. Create and return a custom Uri by handling the UriRequested event. For more info, see the section Providing a custom Uri later in this topic.

  3. Then follow the remaining steps described previously in the section Displaying tiled images on a map.

You can use the following protocols and locations to load tiles from local storage:

Uri More info
ms-appx:///

Points to the root of the app's installation folder.

This is the location referenced by the Package.InstalledLocation property.

ms-appdata:///local

Points to the root of the app's local storage.

This is the location referenced by the ApplicationData.LocalFolder property.

ms-appdata:///temp

Points to the app's temp folder.

This is the location referenced by the ApplicationData.TemporaryFolder property.

 

The following example loads tiles stored as files in the app's installation folder by using the ms-appx:/// protocol. The value for the UriFormatString is specified in the constructor of the LocalMapTileDataSource. In this example, tiles are only displayed when the zoom level of the map is within the range specified by the optional ZoomLevelRange property.

        void AddLocalMapTileSource()
        {
            // Specify the range of zoom levels
            // at which the overlaid tiles are displayed.
            MapZoomLevelRange range;
            range.Min = 11;
            range.Max = 20;

            // Create a local data source.
            LocalMapTileDataSource dataSource = new LocalMapTileDataSource(
                "ms-appx:///TileSourceAssets/Tile_{zoomlevel}_{x}_{y}.png");

            // Create a tile source and add it to the Map control.
            MapTileSource tileSource = new MapTileSource(dataSource);
            tileSource.ZoomLevelRange = range;
            MapControl1.TileSources.Add(tileSource);
        }

Providing a custom Uri

If the replaceable parameters available with the UriFormatString property of the HttpMapTileDataSource or the UriFormatString property of the LocalMapTileDataSource aren't sufficient to retrieve your tiles, then you have to create a custom Uri. Create and return a custom Uri by providing a custom handler for the UriRequested event. The UriRequested event is raised for each individual tile.

  1. In your custom handler for the UriRequested event, combine the required custom arguments with the X, Y, and ZoomLevel properties of the MapTileUriRequestedEventArgs to create the custom Uri.
  2. Return the custom Uri in the Uri property of the MapTileUriRequest, which is contained in the Request property of the MapTileUriRequestedEventArgs.

The following example shows how to provide a custom Uri by creating a custom handler for the UriRequested event. It also shows how to implement the deferral pattern if you have to do something asynchronously to create the custom Uri.

using Windows.UI.Xaml.Controls.Maps;
using System.Threading.Tasks;
...
            var httpTileDataSource = new HttpMapTileDataSource();
            // Attach a handler for the UriRequested event.
            httpTileDataSource.UriRequested += HandleUriRequestAsync;
            MapTileSource httpTileSource = new MapTileSource(httpTileDataSource);
            MapControl1.TileSources.Add(httpTileSource);
...
        // Handle the UriRequested event.
        private async void HandleUriRequestAsync(HttpMapTileDataSource sender,
            MapTileUriRequestedEventArgs args)
        {
            // Get a deferral to do something asynchronously.
            // Omit this line if you don't have to do something asynchronously.
            var deferral = args.Request.GetDeferral();

            // Get the custom Uri.
            var uri = await GetCustomUriAsync(args.X, args.Y, args.ZoomLevel);

            // Specify the Uri in the Uri property of the MapTileUriRequest.
            args.Request.Uri = uri;

            // Notify the app that the custom Uri is ready.
            // Omit this line also if you don't have to do something asynchronously.
            deferral.Complete();
        }

        // Create the custom Uri.
        private async Task<Uri> GetCustomUriAsync(int x, int y, int zoomLevel)
        {
            // Do something asynchronously to create and return the custom Uri.        }
        }

Overlaying custom tiles

Overlay custom tiles by using the CustomMapTileDataSource. Create tiles programmatically in memory on the fly, or write your own code to load existing tiles from another source.

To create or load custom tiles, provide a custom handler for the BitmapRequested event. The BitmapRequested event is raised for each individual tile.

  1. In your custom handler for the BitmapRequested event, combine the required custom arguments with the X, Y, and ZoomLevel properties of the MapTileBitmapRequestedEventArgs to create or retrieve a custom tile.
  2. Return the custom tile in the PixelData property of the MapTileBitmapRequest, which is contained in the Request property of the MapTileBitmapRequestedEventArgs. The PixelData property is of type IRandomAccessStreamReference.

The following example shows how to provide custom tiles by creating a custom handler for the BitmapRequested event. This example creates identical red tiles that are partially opaque. The example ignores the X, Y, and ZoomLevel properties of the MapTileBitmapRequestedEventArgs. Although this is not a real world example, the example demonstrates how you can create in-memory custom tiles on the fly. The example also shows how to implement the deferral pattern if you have to do something asynchronously to create the custom tiles.

using Windows.UI.Xaml.Controls.Maps;
using Windows.Storage.Streams;
using System.Threading.Tasks;
...
        CustomMapTileDataSource customDataSource = new CustomMapTileDataSource();
        // Attach a handler for the BitmapRequested event.
        customDataSource.BitmapRequested += customDataSource_BitmapRequestedAsync;
        customTileSource = new MapTileSource(customDataSource);
        MapControl1.TileSources.Add(customTileSource);
...
        // Handle the BitmapRequested event.
        private async void customDataSource_BitmapRequestedAsync(
            CustomMapTileDataSource sender,
            MapTileBitmapRequestedEventArgs args)
        {
            var deferral = args.Request.GetDeferral();
            args.Request.PixelData = await CreateBitmapAsStreamAsync();
            deferral.Complete();
        }

        // Create the custom tiles.
        // This example creates red tiles that are partially opaque.
        private async Task<RandomAccessStreamReference> CreateBitmapAsStreamAsync()
        {
            int pixelHeight = 256;
            int pixelWidth = 256;
            int bpp = 4;

            byte[] bytes = new byte[pixelHeight * pixelWidth * bpp];

            for (int y = 0; y < pixelHeight; y++)
            {
                for (int x = 0; x < pixelWidth; x++)
                {
                    int pixelIndex = y * pixelWidth + x;
                    int byteIndex = pixelIndex * bpp;

                    // Set current pixel bytes
                    bytes[byteIndex] = 0xff;        // Red
                    bytes[byteIndex + 1] = 0x00;    // Green
                    bytes[byteIndex + 2] = 0x00;    // Blue
                    bytes[byteIndex + 3] = 0x80;    // Alpha (0xff = fully opaque)
                }
            }

            // Create RandomAccessStream from byte array
            InMemoryRandomAccessStream randomAccessStream =
                new InMemoryRandomAccessStream();
            IOutputStream outputStream = randomAccessStream.GetOutputStreamAt(0);
            DataWriter writer = new DataWriter(outputStream);
            writer.WriteBytes(bytes);
            await writer.StoreAsync();
            await writer.FlushAsync();
            return RandomAccessStreamReference.CreateFromStream(randomAccessStream);
        }
InMemoryRandomAccessStream^ TileSources::CustomRandomAccessSteram::get()
{
       int pixelHeight = 256;
       int pixelWidth = 256;
       int bpp = 4;
 
       Array<byte>^ bytes = ref new Array<byte>(pixelHeight * pixelWidth * bpp);
       
       for (int y = 0; y < pixelHeight; y++)
       {
              for (int x = 0; x < pixelWidth; x++)
              {
                     int pixelIndex = y * pixelWidth + x;
                     int byteIndex = pixelIndex * bpp;
 
                     // Set current pixel bytes
                     bytes[byteIndex] = (byte)(std::rand() % 256);        // Red
                     bytes[byteIndex + 1] = (byte)(std::rand() % 256);    // Green
                     bytes[byteIndex + 2] = (byte)(std::rand() % 256);    // Blue
                     bytes[byteIndex + 3] = (byte)((std::rand() % 56) + 200);    // Alpha (0xff = fully opaque)
              }
       }
 
       // Create RandomAccessStream from byte array
       InMemoryRandomAccessStream^ randomAccessStream = ref new InMemoryRandomAccessStream();
       IOutputStream^ outputStream = randomAccessStream->GetOutputStreamAt(0);
       DataWriter^ writer = ref new DataWriter(outputStream);
       writer->WriteBytes(bytes);
 
       create_task(writer->StoreAsync()).then([writer](unsigned int)
       {
              create_task(writer->FlushAsync());
       });
 
       return randomAccessStream;
}

Replacing the default map

To replace the default map entirely with third-party or custom tiles: