Highlighting a Circular Area on a Map

PDF for offline use
Sample Code:
Related Articles:
Related APIs:

Let us know how you feel about this

Translation Quality


0/250

last updated: 2017-08

This article explains how to add a circular overlay to a map in order to highlight a circular area of the map.

Overview

An overlay is a layered graphic on a map. Overlays support drawing graphical content that scales with the map as it is zoomed. The following screenshots show the result of adding a circular overlay to a map:

When a Map control is rendered by a Xamarin.Forms application, in iOS the MapRenderer class is instantiated, which in turn instantiates a native MKMapView control. On the Android platform, the MapRenderer class instantiates a native MapView control. On the Universal Windows Platform (UWP), the MapRenderer class instantiates a native MapControl. The rendering process can be taken advantage of to implement platform-specific map customizations by creating a custom renderer for a Map on each platform. The process for doing this is as follows:

  1. Create a Xamarin.Forms custom map.
  2. Consume the custom map from Xamarin.Forms.
  3. Customize the map by creating a custom renderer for the map on each platform.

Xamarin.Forms.Maps must be initialized and configured before use. For more information, see Maps Control.

For information about customizing a map using a custom renderer, see Customizing a Map Pin.

Creating the Custom Map

Create a CustomCircle class that has Position and Radius properties:

public class CustomCircle
{
  public Position Position { get; set; }
  public double Radius { get; set; }
}

Then, create a subclass of the Map class, that adds a property of type CustomCircle:

public class CustomMap : Map
{
  public CustomCircle Circle { get; set; }
}

Consuming the Custom Map

Consume the CustomMap control by declaring an instance of it in the XAML page instance:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:MapOverlay;assembly=MapOverlay"
             x:Class="MapOverlay.MapPage">
    <ContentPage.Content>
        <local:CustomMap x:Name="customMap" MapType="Street" WidthRequest="{x:Static local:App.ScreenWidth}" HeightRequest="{x:Static local:App.ScreenHeight}" />
    </ContentPage.Content>
</ContentPage>

Alternatively, consume the CustomMap control by declaring an instance of it in the C# page instance:

public class MapPageCS : ContentPage
{
    public MapPageCS ()
    {
        var customMap = new CustomMap {
            MapType = MapType.Street,
            WidthRequest = App.ScreenWidth,
            HeightRequest = App.ScreenHeight
        };
        ...
        Content = customMap;
    }
}

Initialize the CustomMap control as required:

public partial class MapPage : ContentPage
{
  public MapPage ()
  {
    ...
    var pin = new Pin {
      Type = PinType.Place,
      Position = new Position (37.79752, -122.40183),
      Label = "Xamarin San Francisco Office",
      Address = "394 Pacific Ave, San Francisco CA"
    };

    var position = new Position (37.79752, -122.40183);
    customMap.Circle = new CustomCircle {
      Position = position,
      Radius = 1000
    };

    customMap.Pins.Add (pin);
    customMap.MoveToRegion (MapSpan.FromCenterAndRadius (position, Distance.FromMiles (1.0)));
  }
}

This initialization adds Pin and CustomCircle instances to the custom map, and positions the map's view with the MoveToRegion method, which changes the position and zoom level of the map by creating a MapSpan from a Position and a Distance.

Customizing the Map

A custom renderer must now be added to each application project in order to add the circular overlay to the map.

Creating the Custom Renderer on iOS

Create a subclass of the MapRenderer class and override its OnElementChanged method in order to add the circular overlay:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MapOverlay.iOS
{
    public class CustomMapRenderer : MapRenderer
    {
        MKCircleRenderer circleRenderer;

        protected override void OnElementChanged(ElementChangedEventArgs<View> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null) {
                var nativeMap = Control as MKMapView;
                if (nativeMap != null) {
                    nativeMap.RemoveOverlays(nativeMap.Overlays);
                    nativeMap.OverlayRenderer = null;
                    circleRenderer = null;
                }
            }

            if (e.NewElement != null) {
                var formsMap = (CustomMap)e.NewElement;
                var nativeMap = Control as MKMapView;
                var circle = formsMap.Circle;

                nativeMap.OverlayRenderer = GetOverlayRenderer;

                var circleOverlay = MKCircle.Circle(new CoreLocation.CLLocationCoordinate2D(circle.Position.Latitude, circle.Position.Longitude), circle.Radius);
                nativeMap.AddOverlay(circleOverlay);
            }
        }
        ...
    }
}

This method performs the following configuration, provided that the custom renderer is attached to a new Xamarin.Forms element:

  • The MKMapView.OverlayRenderer property is set to a corresponding delegate.
  • The circle is created by setting a static MKCircle object that specifies the center of the circle, and the radius of the circle in meters.
  • The circle is added to the map by calling the MKMapView.AddOverlay method.

Then, implement the GetOverlayRenderer method in order to customize the rendering of the overlay:

public class CustomMapRenderer : MapRenderer
{
  MKCircleRenderer circleRenderer;
  ...

  MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper)
  {
      if (circleRenderer == null && !Equals(overlayWrapper, null)) {
          var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay;
          circleRenderer = new MKCircleRenderer(overlay as MKCircle) {
              FillColor = UIColor.Red,
              Alpha = 0.4f
          };
      }
      return circleRenderer;
  }
}

Creating the Custom Renderer on Android

Create a subclass of the MapRenderer class and override its OnElementChanged and OnElementPropertyChanged methods in order to add the circular overlay:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MapOverlay.Droid
{
    public class CustomMapRenderer : MapRenderer
    {
        CustomCircle circle;
        bool isDrawn;

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

            if (e.OldElement != null) {
                // Unsubscribe
            }

            if (e.NewElement != null) {
                var formsMap = (CustomMap)e.NewElement;
                circle = formsMap.Circle;
                Control.GetMapAsync(this);
            }
        }

        protected override void OnElementPropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            base.OnElementPropertyChanged(sender, e);

            if (e.PropertyName.Equals("VisibleRegion") && !isDrawn) {
                var circleOptions = new CircleOptions();
                circleOptions.InvokeCenter(new LatLng(circle.Position.Latitude, circle.Position.Longitude));
                circleOptions.InvokeRadius(circle.Radius);
                circleOptions.InvokeFillColor(0X66FF0000);
                circleOptions.InvokeStrokeColor(0X66FF0000);
                circleOptions.InvokeStrokeWidth(0);

                NativeMap.AddCircle(circleOptions);
                isDrawn = true;
            }
        }
    }
}

This method calls the MapView.GetMapAsync method, which gets the underlying GoogleMap that is tied to the view, provided that the custom renderer is attached to a new Xamarin.Forms element.

The circle is created in the OnElementPropertyChanged method, which is called whenever any bindable properties in the custom map control change. The Xamarin.Forms.Maps.Map.VisibleRegion property contains coordinates for the currently visible region of the map, and will change prior to the map being displayed. When this occurs, a CircleOptions object is instantiated that specifies the center of the circle, and the radius of the circle in meters. The circle is then added to the map by calling the NativeMap.AddCircle method.

Creating the Custom Renderer on the Universal Windows Platform

Create a subclass of the MapRenderer class and override its OnElementChanged method in order to add the circular overlay:

[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))]
namespace MapOverlay.UWP
{
    public class CustomMapRenderer : MapRenderer
    {
        const int EarthRadiusInMeteres = 6371000;

        protected override void OnElementChanged(ElementChangedEventArgs<Map> e)
        {
            base.OnElementChanged(e);

            if (e.OldElement != null)
            {
                // Unsubscribe
            }
            if (e.NewElement != null)
            {
                var formsMap = (CustomMap)e.NewElement;
                var nativeMap = Control as MapControl;
                var circle = formsMap.Circle;

                var coordinates = new List<BasicGeoposition>();
                var positions = GenerateCircleCoordinates(circle.Position, circle.Radius);
                foreach (var position in positions)
                {
                    coordinates.Add(new BasicGeoposition { Latitude = position.Latitude, Longitude = position.Longitude });
                }

                var polygon = new MapPolygon();
                polygon.FillColor = Windows.UI.Color.FromArgb(128, 255, 0, 0);
                polygon.StrokeColor = Windows.UI.Color.FromArgb(128, 255, 0, 0);
                polygon.StrokeThickness = 5;
                polygon.Path = new Geopath(coordinates);
                nativeMap.MapElements.Add(polygon);
            }
        }
        ...
    }
}

This method performs the following operations, provided that the custom renderer is attached to a new Xamarin.Forms element:

  • The circle position and radius are retrieved from the CustomMap.Circle property and passed to the GenerateCircleCoordinates method, which generates latitude and longitude coordinates for the circle perimeter.
  • The circle perimeter coordinates are converted into a List of BasicGeoposition coordinates.
  • The circle is created by instantiating a MapPolygon object. The MapPolygon class is used to display a multi-point shape on the map by setting its Path property to a Geopath object that contains the shape coordinates.
  • The polygon is rendered on the map by adding it to the MapControl.MapElements collection.

Summary

This article explained how to add a circular overlay to a map in order to highlight a circular area of the map.

Xamarin Workbook

If it's not already installed, install the Xamarin Workbooks app first. The workbook file should download automatically, but if it doesn't, just click to start the workbook download manually.