Custom Map Tiles in Xamarin.Forms

Over the past few weeks i have been working on a project which required not only a mapping element but also required custom map tiles to be overlaid on the map. This is actually a really simple process using Xamarin.Forms (I haven’t added Windows Phone here but i will edit this later) which requires only a basic knowledge of custom renderers.

We are going to create a map like this:

https://a.tiles.mapbox.com/v4/blounty.l7lok06j/page.html?access_token=pk.eyJ1IjoiYmxvdW50eSIsImEiOiJYanJxSGxJIn0.LV0GHzqOvtGSyV6CjHqfuw#5/12.812/4.614

Project Setup

Create a new Forms project and add the Xamarin.Forms.Maps nuget package to the core PCL, iOS and Android projects. In the Android project you also need to set the following settings in the AndroidManifest.xml:

  • Access to the Network State – Maps API v2 must be able to check if it can download the map tiles.
  • Internet Access – Internet access is necessary to download the map tiles and communicate with the Google Play Servers for API access.
  • OpenGL ES v2 – The application must declare the requirement for OpenGL ES v2.
  • Google Maps API Key – The API key is used to confirm that the application is registered and authorized to use Google Play Services.
  • Write to External Storage – Google Maps Android API v2 will cache downloaded tiles to external storage.
  • Access to the Google Web-based Services – The application needs permissions to access Google’s web services that back the Android Maps API v2.
  • Access to Location Providers – These are optional permissions. They will allow the  GoogleMap class to display the location of the device on the map.

Call global::Xamarin.FormsMaps.Init() in both your iOS and Android projects at the the same point global::Xamarin.Forms.Forms.Init() is called.

PCL Custom Map Control

Now we have the basic requirements in place lets move on to writing a little code. Create a new class called CustomMapTiles like so:

 using System;
 using Xamarin.Forms.Maps;
 
 namespace CustomMapTiles
 {
     public class CustomMap 
         : Map
     {
         public string MapTileTemplate
         {
             get;
             set;
         }
     }
 }

We can now use this new control in a view, we also need to set the MapTileTemplate property.We can obtain this  by creating a new new project at MapBox.com.

This will give you a url like this:

http://api.tiles.mapbox.com/v4/blounty.l7lok06j/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYmxvdW50eSIsImEiOiJYanJxSGxJIn0.LV0GHzqOvtGSyV6CjHqfuw

In your new views Xaml add the new control as follows:


<?xml version="1.0" encoding="UTF-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="CustomMapTiles.Views.MapPage"
xmlns:local="clr-namespace:CustomMapTiles;assembly=CustomMapTiles">
<local:CustomMap x:Name="MyMap"
MapTileTemplate="http://api.tiles.mapbox.com/v4/blounty.l7lok06j/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoiYmxvdW50eSIsImEiOiJYanJxSGxJIn0.LV0GHzqOvtGSyV6CjHqfuw"/>
</ContentPage>

iOS Custom Map Renderer

Create a new class called CustomMapRenderer in your iOS project with the below code:


using System;
using Xamarin.Forms.Maps.iOS;
using Xamarin.Forms;
using CustomMapTiles;
using CustomMapTiles.iOS.Renderers;
using MapKit;

[assembly: ExportRenderer (typeof (CustomMap), typeof (CustomMapRenderer))]

namespace CustomMapTiles.iOS.Renderers
{
public class CustomMapRenderer
: MapRenderer
{

MKMapView mapView;
CustomMap customMap;

protected override void OnElementChanged(Xamarin.Forms.Platform.iOS.ElementChangedEventArgs<View> e)
{
base.OnElementChanged(e);

if (e.OldElement == null)
{
mapView = Control as MKMapView;
customMap = e.NewElement as CustomMap;

var overlay = new MKTileOverlay (customMap.MapTileTemplate);
overlay.CanReplaceMapContent = false;
overlay.GeometryFlipped = false;
mapView.AddOverlay (overlay, MKOverlayLevel.AboveLabels);

mapView.OverlayRenderer = (mv, o) =>
new MKTileOverlayRenderer((MKTileOverlay)o);
}
}
}
}

Here we are using the url we added earlier to our control earlier and instantiating a new MKTileOverlay. We add this to the map and we are done with iOS..

Android Custom Map Renderer

Finally lets create a new class in the Android project called CustomTileProvider:


namespace CustomMapTiles.Droid.Renderers
{
public class CustomTileProvider : UrlTileProvider
{
string urlTemplate;

public CustomTileProvider (int x, int y, string urlTemplate)
: base(x,y)
{
this.urlTemplate = urlTemplate;
}

public override Java.Net.URL GetTileUrl (int x, int y, int z)
{
var url = urlTemplate.Replace("{z}", z.ToString()).Replace("{x}", x.ToString()).Replace("{y}", y.ToString());
Console.WriteLine (url);
return new Java.Net.URL (url);
}
}
}

Followed by a class called CustomMapRenderer again in your Android project:


using System;
using Xamarin.Forms.Maps.Android;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Xamarin.Forms;
using CustomMapTiles;
using CustomMapTiles.Droid.Renderers;

[assembly: ExportRenderer (typeof (CustomMap), typeof (CustomMapRenderer))]
namespace CustomMapTiles.Droid.Renderers
{
public class CustomMapRenderer
: MapRenderer
{
MapView map;
CustomTileProvider tileProvider;
CustomMap customMap;

protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);

if (e.OldElement == null) {

map = Control as MapView;
customMap = e.NewElement as CustomMap;

var tileProvider = new CustomTileProvider (512, 512, customMap.MapTileTemplate);
var options = new TileOverlayOptions().InvokeTileProvider(tileProvider);

map.Map.AddTileOverlay (options);
}
}
}
}

Here we have the final running apps with custom map tiles:

iOS

Screen Shot 2015-02-16 at 11.18.15 pm

Android

Screen Shot 2015-02-16 at 11.16.27 pm

You can find the code here:

https://github.com/blounty/CustomMapTiles

Advertisements