Skip to content

SkiaSharp drawn map pins and overlays for Xamarin Forms

License

Notifications You must be signed in to change notification settings

LRP-sgravel/Xamarin.Forms.SKMaps

Repository files navigation

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

Nuget iOS Android
NuGet Build status Build status

Xamarin.Forms.SKMaps Demo

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:

XamlCode
<skmap:SKMap />
SKMap map = new SKMap();

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.

XamlCode
<skmap:SKMap>
  <skmap:SKMap.Pins>
    <CustomPin Width="32"
               Height="32" />
  </skmap:SKMap.Pins>
</skmap:SKMap>
map.Pins.Add(new CustomPin());

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.

Property Purpose Default
Width The width of the pin, in device independent pixels. 32 dip
Height The height of the pin, in device independent pixels. 32 dip
AnchorX Set 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)
AnchorY Set 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)
IsVisible Set the pin's visibility on the map. true (Visible)
Clickable Set 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.

XamlCode
<skmap:SKMap>
  <skmap:SKMap.MapOverlays>
    <CustomOverlay />
  </skmap:SKMap.MapOverlays>
</skmap:SKMap>
map.MapOverlays.Add(new CustomOverlay());

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

  • You should always consider the canvasMapRect parameter when doing the render calls to avoid drawing outside of the current rendering area. Failure to do so could result in slower performance.
  • This method might be called concurently for many tiles, your implementation therefore needs to be thread-safe.
  • DrawOnMap will not be called for tiles outside of your overlay's GpsBounds.
  • Drawing shapes across the 180th meridian will automatically handle the necessary wrap-around. To cross the 180th meridian, shapes require passing a MapSpan that goes across the 180th meridian, meaning that it would reach longitudes under -180 degrees or above 180 degrees (i.e. A rectangle centered at 179 degrees longitude with a span of 5 degrees).
  • In the case of lines, the shortest route between the two provided coordinates is rendered by default. You can opt out and force the long route to be rendered by setting the shortLine parameter to false in the DrawLine calls.

Other properties

SKMapOverlay provides other properties to modify it's behavior.

Property Purpose Default
GpsBounds Gps bounds that fully emcompass the overlay. Tiles outside this area will not be rendered. N/A
IsVisible Set 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.

Method Purpose
PixelsToMaximumMapSpanAtScale Returns 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.
PixelsToMapSizeAtScale Returns 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.

About

SkiaSharp drawn map pins and overlays for Xamarin Forms

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages