Home

Awesome

Xamarin.Forms.SKMaps

Map pins and overlays for Xamarin.Forms.Maps, drawn with SkiaSharp

Xamarin.Forms.SKMaps allows developers an easier integration of custom map features from a shared code base. Built over Xamarin.Forms.Maps and adding the strength of SkiaSharp rendering, you get easy and highly performant map drawing features from a single code base.

Build status

NugetiOSAndroid
NuGetBuild statusBuild status
<img alt="Xamarin.Forms.SKMaps Demo" src="./088E2535-1902-47EA-932D-DF9F23A4B8E5.png" width="272" />

Documentation

Using Xamarin.Forms.SKMaps is as easy as using a regular Xamarin Forms Maps. You can use the map control in Xaml or code, following your preference:

<table> <tr> <th>Xaml</th><th>Code</th> </tr> <tr> <td><pre lang=xml>&lt;skmap:SKMap /&gt;</pre></td> <td><pre lang="csharp">SKMap map = new SKMap();</pre></td> </tr> </table>

if using Xaml, make sure to include the proper namespace in your root element :

<ContentPage xmlns:skmap="clr-namespace:Xamarin.Forms.SKMaps;assembly=Xamarin.Forms.SKMaps">
  ...
</ContentPage>

Pins

Adding a SKPin could not be easier. It uses the map's regular Pins property that you are used to.

<table> <tr> <th>Xaml</th><th>Code</th> </tr> <tr> <td><pre lang=xml>&lt;skmap:SKMap&gt; &lt;skmap:SKMap.Pins&gt; &lt;CustomPin Width="32" Height="32" /&gt; &lt;/skmap:SKMap.Pins&gt; &lt;/skmap:SKMap&gt;</pre></td> <td><pre lang="csharp">map.Pins.Add(new CustomPin());</pre></td> </tr> </table>

Drawing your pin

The CustomPin class in the above example is the class responsible for rendering the pin marker. To do so, subclass SKPin and override the DrawPin method. You will receive a SKSurface to draw on. This surface will be sized according to its Width, Height and the device's screen density.

public override void DrawPin(SKSurface surface)
{
    SKCanvas canvas = surface.Canvas;

    if (_svgIcon != null)
    {
        SKMatrix fillMatrix = GetFillMatrix(canvas, _svgIcon.Picture.CullRect);

        canvas.DrawPicture(_svgIcon.Picture, ref fillMatrix);
    }
}

Other properties

SKPin provides other properties to modify it's behavior. As it subclasses the Pin class, you also have access to the regular pin properties.

PropertyPurposeDefault
WidthThe width of the pin, in device independent pixels.32 dip
HeightThe height of the pin, in device independent pixels.32 dip
AnchorXSet the pin's anchor X position. The anchor will be over the pin's position on the map. This value is in the range { 0...1 } with a value of 0 being at the left.0.5 (Center)
AnchorYSet the pin's anchor Y position. The anchor will be over the pin's position on the map. This value is in the range { 0...1 } with a value of 0 being at the top.0.5 (Center)
IsVisibleSet the pin's visibility on the map.true (Visible)
ClickableSet the pin's clickability on the map.true (Clickable)

Redrawing the pin

To force the pin to be redrawn, you can call the Invalidate method. Changing the Width or Height will also force a refresh of the pin graphics to accomodate the new size.

Overlays

The SKMapOverlay can be used to add custom "map tile" style overlays. To do so, add your overlays to the SKMap MapOverlaysproperty.

<table> <tr> <th>Xaml</th><th>Code</th> </tr> <tr> <td><pre lang=xml>&lt;skmap:SKMap&gt; &lt;skmap:SKMap.MapOverlays&gt; &lt;CustomOverlay /&gt; &lt;/skmap:SKMap.MapOverlays&gt; &lt;/skmap:SKMap&gt;</pre></td> <td><pre lang="csharp">map.MapOverlays.Add(new CustomOverlay());</pre></td> </tr> </table>

Drawing your overlay

Similar to the SKPin, the SKMapOverlay should be subclassed and the overlay is drawn within an override named DrawOnMap. To ease the drawing calculations, all drawing occurs in GPS coordinates through the SKMapCanvas class and other utility classes like SKMapPath. The SKPaint properties remain in pixels, for instance StrokeWidth.

public override void DrawOnMap(SKMapCanvas canvas, SKMapSpan canvasMapRect, double zoomScale)
{
    if (Route != null && Route.Points.Count() > 1)
    {
        Position firstPoint = Route.Points.FirstOrDefault();
        List<Position> currentRoute = new List<Position>(Route.Points.Skip(1));
        SKMapPath path = canvas.CreateEmptyMapPath();

        path.MoveTo(firstPoint);
        foreach (Position nextPoint in currentRoute)
        {
            path.LineTo(nextPoint);
        }

        using (SKPaint paint = new SKPaint())
        {
            paint.Color = LineColor.ToSKColor();
            paint.StrokeWidth = LineWidth;
            paint.Style = SKPaintStyle.Stroke;
            paint.IsAntialias = true;

            canvas.DrawPath(path, paint);
        }
    }
}

Notes

Other properties

SKMapOverlay provides other properties to modify it's behavior.

PropertyPurposeDefault
GpsBoundsGps bounds that fully emcompass the overlay. Tiles outside this area will not be rendered.N/A
IsVisibleSet the overlay's visibility on the map.true (Visible)

Redrawing the overlay

To force the overlay to be redrawn, you can call the Invalidate method. Alternatively, changing the GPSBoundsproperty from within the child class will also force a full refresh of the overlay.

Converting coordinates and pixel sizes

Since the SKMapOverlay does all it's drawing operations in pixels but in a GPS coordinate space, you are likely to need to convert from pixel sizes to GPS spans. A common use-case is wanting to draw a line of a given GPS size (i.e. 100 meters wide) regardless of the zoom level and position. Xamarin.Forms.SKMaps provides utility methods to do those conversions. Those methods are available on the SKMapCanvas class.

MethodPurpose
PixelsToMaximumMapSpanAtScaleReturns the maximum span that the provided pixels size requires for a given zoom scale. Because of the mercator projection, the maximum span will always be at the equator since the projection stretches the map at the poles. This method is useful to add padding to a shape when we don't know where it will be rendered. Using a zoom scale of SKMapCanvas.MaxZoomScale will provide the maximum padding required when fully zoomed out.
PixelsToMapSizeAtScaleReturns the span that the provided pixels size requires at the provided location for a given zoom scale. This method is useful to add padding to a shape when we know where it will be rendered.

Sample

The provided sample is abnormally large, it is more than a sample. This is due to the fact that this Nuget started as a Toptal certification project for Xamarin. I meant to use the project as a starting point for the Nuget but as I never had the time to fit the certification within my schedule, I forked the project into the current sample. The idea behind the project was to build a fully cross platform app, meaning I had the objective of doing 100% of the features, including the UI, in shared code. I still have the objective of writing a Toptal blog post on this as it was requested in my previous blog post.

I'm aware this might create a fair bit of confusion as the sample is not straight to the point, given the requirements of the certification on which I built the foundation. However, this is also a great opportunty to see different cross platform techniques in Xamarin Forms. The sample therefore introduces different Forms concepts like markup extensions, using MvvmCross value converters in Forms, Forms effects and behaviors, MvvmCross localization, shared project embedded resources accessed through a resource locator, rendering custom fonts in SkiaSharp, etc.

For those solely interested in the map overlay and pin samples, they can be found here and they are referenced in the ActivityPage.