|
| 1 | +--- |
| 2 | +title: "Highlighting a Region on a Map" |
| 3 | +description: "This article explains how to add a polygon overlay to a map, to highlight a region on the map. Polygons are a closed shape and have their interiors filled in." |
| 4 | +ms.prod: xamarin |
| 5 | +ms.assetid: E79EB2CF-8DD6-44A8-B47D-5F0A94FB0A63 |
| 6 | +ms.technology: xamarin-forms |
| 7 | +author: davidbritch |
| 8 | +ms.author: dabritch |
| 9 | +ms.date: 11/29/2017 |
| 10 | +--- |
| 11 | + |
| 12 | +# Highlighting a Region on a Map |
| 13 | + |
| 14 | +[ Download the sample](https://docs.microsoft.com/samples/xamarin/xamarin-forms-samples/customrenderers-map-polygon) |
| 15 | + |
| 16 | +_This article explained how to add a polygon overlay to a map, to highlight a region on the map. Polygons are a closed shape and have their interiors filled in._ |
| 17 | + |
| 18 | +## Overview |
| 19 | + |
| 20 | +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 polygon overlay to a map: |
| 21 | + |
| 22 | + |
| 23 | + |
| 24 | +When a [`Map`](xref:Xamarin.Forms.Maps.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: |
| 25 | + |
| 26 | +1. [Create](#Creating_the_Custom_Map) a Xamarin.Forms custom map. |
| 27 | +1. [Consume](#Consuming_the_Custom_Map) the custom map from Xamarin.Forms. |
| 28 | +1. [Customize](#Customizing_the_Map) the map by creating a custom renderer for the map on each platform. |
| 29 | + |
| 30 | +> [!NOTE] |
| 31 | +> [`Xamarin.Forms.Maps`](xref:Xamarin.Forms.Maps) must be initialized and configured before use. For more information, see [`Maps Control`](~/xamarin-forms/user-interface/map/index.md). |
| 32 | +
|
| 33 | +For information about customizing a map using a custom renderer, see [Customizing a Map Pin](~/xamarin-forms/app-fundamentals/custom-renderer/map/customized-pin.md). |
| 34 | + |
| 35 | +<a name="Creating_the_Custom_Map" /> |
| 36 | + |
| 37 | +### Creating the Custom Map |
| 38 | + |
| 39 | +Create a subclass of the [`Map`](xref:Xamarin.Forms.Maps.Map) class, that adds a `ShapeCoordinates` property: |
| 40 | + |
| 41 | +```csharp |
| 42 | +public class CustomMap : Map |
| 43 | +{ |
| 44 | + public List<Position> ShapeCoordinates { get; set; } |
| 45 | + |
| 46 | + public CustomMap () |
| 47 | + { |
| 48 | + ShapeCoordinates = new List<Position> (); |
| 49 | + } |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +The `ShapeCoordinates` property will store a collection of coordinates that define the region to be highlighted. |
| 54 | + |
| 55 | +<a name="Consuming_the_Custom_Map" /> |
| 56 | + |
| 57 | +### Consuming the Custom Map |
| 58 | + |
| 59 | +Consume the `CustomMap` control by declaring an instance of it in the XAML page instance: |
| 60 | + |
| 61 | +```xaml |
| 62 | +<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" |
| 63 | + xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" |
| 64 | + xmlns:local="clr-namespace:MapOverlay;assembly=MapOverlay" |
| 65 | + x:Class="MapOverlay.MapPage"> |
| 66 | + <ContentPage.Content> |
| 67 | + <local:CustomMap x:Name="customMap" MapType="Street" WidthRequest="{x:Static local:App.ScreenWidth}" HeightRequest="{x:Static local:App.ScreenHeight}" /> |
| 68 | + </ContentPage.Content> |
| 69 | +</ContentPage> |
| 70 | +``` |
| 71 | + |
| 72 | +Alternatively, consume the `CustomMap` control by declaring an instance of it in the C# page instance: |
| 73 | + |
| 74 | +```csharp |
| 75 | +public class MapPageCS : ContentPage |
| 76 | +{ |
| 77 | + public MapPageCS () |
| 78 | + { |
| 79 | + var customMap = new CustomMap { |
| 80 | + MapType = MapType.Street, |
| 81 | + WidthRequest = App.ScreenWidth, |
| 82 | + HeightRequest = App.ScreenHeight |
| 83 | + }; |
| 84 | + ... |
| 85 | + Content = customMap; |
| 86 | + } |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +Initialize the `CustomMap` control as required: |
| 91 | + |
| 92 | +```csharp |
| 93 | +public partial class MapPage : ContentPage |
| 94 | +{ |
| 95 | + public MapPage () |
| 96 | + { |
| 97 | + ... |
| 98 | + customMap.ShapeCoordinates.Add (new Position (37.797513, -122.402058)); |
| 99 | + customMap.ShapeCoordinates.Add (new Position (37.798433, -122.402256)); |
| 100 | + customMap.ShapeCoordinates.Add (new Position (37.798582, -122.401071)); |
| 101 | + customMap.ShapeCoordinates.Add (new Position (37.797658, -122.400888)); |
| 102 | + |
| 103 | + customMap.MoveToRegion (MapSpan.FromCenterAndRadius (new Position (37.79752, -122.40183), Distance.FromMiles (0.1))); |
| 104 | + } |
| 105 | +} |
| 106 | +``` |
| 107 | + |
| 108 | +This initialization specifies a series of latitude and longitude coordinates, to define the region of the map to be highlighted. It then positions the map's view with the [`MoveToRegion`](xref:Xamarin.Forms.Maps.Map.MoveToRegion*) method, which changes the position and zoom level of the map by creating a [`MapSpan`](xref:Xamarin.Forms.Maps.MapSpan) from a [`Position`](xref:Xamarin.Forms.Maps.Position) and a [`Distance`](xref:Xamarin.Forms.Maps.Distance). |
| 109 | + |
| 110 | +<a name="Customizing_the_Map" /> |
| 111 | + |
| 112 | +### Customizing the Map |
| 113 | + |
| 114 | +A custom renderer must now be added to each application project to add the polygon overlay to the map. |
| 115 | + |
| 116 | +#### Creating the Custom Renderer on iOS |
| 117 | + |
| 118 | +Create a subclass of the `MapRenderer` class and override its `OnElementChanged` method to add the polygon overlay: |
| 119 | + |
| 120 | +```csharp |
| 121 | +[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))] |
| 122 | +namespace MapOverlay.iOS |
| 123 | +{ |
| 124 | + public class CustomMapRenderer : MapRenderer |
| 125 | + { |
| 126 | + MKPolygonRenderer polygonRenderer; |
| 127 | + |
| 128 | + protected override void OnElementChanged(ElementChangedEventArgs<View> e) |
| 129 | + { |
| 130 | + base.OnElementChanged(e); |
| 131 | + |
| 132 | + if (e.OldElement != null) { |
| 133 | + var nativeMap = Control as MKMapView; |
| 134 | + if (nativeMap != null) { |
| 135 | + nativeMap.RemoveOverlays(nativeMap.Overlays); |
| 136 | + nativeMap.OverlayRenderer = null; |
| 137 | + polygonRenderer = null; |
| 138 | + } |
| 139 | + } |
| 140 | + |
| 141 | + if (e.NewElement != null) { |
| 142 | + var formsMap = (CustomMap)e.NewElement; |
| 143 | + var nativeMap = Control as MKMapView; |
| 144 | + |
| 145 | + nativeMap.OverlayRenderer = GetOverlayRenderer; |
| 146 | + |
| 147 | + CLLocationCoordinate2D[] coords = new CLLocationCoordinate2D[formsMap.ShapeCoordinates.Count]; |
| 148 | + |
| 149 | + int index = 0; |
| 150 | + foreach (var position in formsMap.ShapeCoordinates) |
| 151 | + { |
| 152 | + coords[index] = new CLLocationCoordinate2D(position.Latitude, position.Longitude); |
| 153 | + index++; |
| 154 | + } |
| 155 | + |
| 156 | + var blockOverlay = MKPolygon.FromCoordinates(coords); |
| 157 | + nativeMap.AddOverlay(blockOverlay); |
| 158 | + } |
| 159 | + } |
| 160 | + ... |
| 161 | + } |
| 162 | +} |
| 163 | + |
| 164 | +``` |
| 165 | + |
| 166 | +This method performs the following configuration, provided that the custom renderer is attached to a new Xamarin.Forms element: |
| 167 | + |
| 168 | +- The `MKMapView.OverlayRenderer` property is set to a corresponding delegate. |
| 169 | +- The collection of latitude and longitude coordinates are retrieved from the `CustomMap.ShapeCoordinates` property and stored as an array of `CLLocationCoordinate2D` instances. |
| 170 | +- The polygon is created by calling the static `MKPolygon.FromCoordinates` method, which specifies the latitude and longitude of each point. |
| 171 | +- The polygon is added to the map by calling the `MKMapView.AddOverlay` method. This method automatically closes the polygon by drawing a line that connects the first and last points. |
| 172 | + |
| 173 | +Then, implement the `GetOverlayRenderer` method to customize the rendering of the overlay: |
| 174 | + |
| 175 | +```csharp |
| 176 | +public class CustomMapRenderer : MapRenderer |
| 177 | +{ |
| 178 | + MKPolygonRenderer polygonRenderer; |
| 179 | + ... |
| 180 | + |
| 181 | + MKOverlayRenderer GetOverlayRenderer(MKMapView mapView, IMKOverlay overlayWrapper) |
| 182 | + { |
| 183 | + if (polygonRenderer == null && !Equals(overlayWrapper, null)) { |
| 184 | + var overlay = Runtime.GetNSObject(overlayWrapper.Handle) as IMKOverlay; |
| 185 | + polygonRenderer = new MKPolygonRenderer(overlay as MKPolygon) { |
| 186 | + FillColor = UIColor.Red, |
| 187 | + StrokeColor = UIColor.Blue, |
| 188 | + Alpha = 0.4f, |
| 189 | + LineWidth = 9 |
| 190 | + }; |
| 191 | + } |
| 192 | + return polygonRenderer; |
| 193 | + } |
| 194 | +} |
| 195 | +``` |
| 196 | + |
| 197 | +#### Creating the Custom Renderer on Android |
| 198 | + |
| 199 | +Create a subclass of the `MapRenderer` class and override its `OnElementChanged` and `OnMapReady` methods to add the polygon overlay: |
| 200 | + |
| 201 | +```csharp |
| 202 | +[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))] |
| 203 | +namespace MapOverlay.Droid |
| 204 | +{ |
| 205 | + public class CustomMapRenderer : MapRenderer |
| 206 | + { |
| 207 | + List<Position> shapeCoordinates; |
| 208 | + |
| 209 | + public CustomMapRenderer(Context context) : base(context) |
| 210 | + { |
| 211 | + } |
| 212 | + |
| 213 | + protected override void OnElementChanged(Xamarin.Forms.Platform.Android.ElementChangedEventArgs<Map> e) |
| 214 | + { |
| 215 | + base.OnElementChanged(e); |
| 216 | + |
| 217 | + if (e.OldElement != null) |
| 218 | + { |
| 219 | + // Unsubscribe |
| 220 | + } |
| 221 | + |
| 222 | + if (e.NewElement != null) |
| 223 | + { |
| 224 | + var formsMap = (CustomMap)e.NewElement; |
| 225 | + shapeCoordinates = formsMap.ShapeCoordinates; |
| 226 | + Control.GetMapAsync(this); |
| 227 | + } |
| 228 | + } |
| 229 | + |
| 230 | + protected override void OnMapReady(Android.Gms.Maps.GoogleMap map) |
| 231 | + { |
| 232 | + base.OnMapReady(map); |
| 233 | + |
| 234 | + var polygonOptions = new PolygonOptions(); |
| 235 | + polygonOptions.InvokeFillColor(0x66FF0000); |
| 236 | + polygonOptions.InvokeStrokeColor(0x660000FF); |
| 237 | + polygonOptions.InvokeStrokeWidth(30.0f); |
| 238 | + |
| 239 | + foreach (var position in shapeCoordinates) |
| 240 | + { |
| 241 | + polygonOptions.Add(new LatLng(position.Latitude, position.Longitude)); |
| 242 | + } |
| 243 | + NativeMap.AddPolygon(polygonOptions); |
| 244 | + } |
| 245 | + } |
| 246 | +} |
| 247 | +``` |
| 248 | + |
| 249 | +The `OnElementChanged` method retrieves the collection of latitude and longitude coordinates from the `CustomMap.ShapeCoordinates` property and stores them in a member variable. It then 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. Once the `GoogleMap` instance is available, the `OnMapReady` method will be invoked, where the polygon is created by instantiating a `PolygonOptions` object that specifies the latitude and longitude of each point. The polygon is then added to the map by calling the `NativeMap.AddPolygon` method. This method automatically closes the polygon by drawing a line that connects the first and last points. |
| 250 | + |
| 251 | +#### Creating the Custom Renderer on the Universal Windows Platform |
| 252 | + |
| 253 | +Create a subclass of the `MapRenderer` class and override its `OnElementChanged` method to add the polygon overlay: |
| 254 | + |
| 255 | +```csharp |
| 256 | +[assembly: ExportRenderer(typeof(CustomMap), typeof(CustomMapRenderer))] |
| 257 | +namespace MapOverlay.UWP |
| 258 | +{ |
| 259 | + public class CustomMapRenderer : MapRenderer |
| 260 | + { |
| 261 | + protected override void OnElementChanged(ElementChangedEventArgs<Map> e) |
| 262 | + { |
| 263 | + base.OnElementChanged(e); |
| 264 | + |
| 265 | + if (e.OldElement != null) |
| 266 | + { |
| 267 | + // Unsubscribe |
| 268 | + } |
| 269 | + |
| 270 | + if (e.NewElement != null) |
| 271 | + { |
| 272 | + var formsMap = (CustomMap)e.NewElement; |
| 273 | + var nativeMap = Control as MapControl; |
| 274 | + |
| 275 | + var coordinates = new List<BasicGeoposition>(); |
| 276 | + foreach (var position in formsMap.ShapeCoordinates) |
| 277 | + { |
| 278 | + coordinates.Add(new BasicGeoposition() { Latitude = position.Latitude, Longitude = position.Longitude }); |
| 279 | + } |
| 280 | + |
| 281 | + var polygon = new MapPolygon(); |
| 282 | + polygon.FillColor = Windows.UI.Color.FromArgb(128, 255, 0, 0); |
| 283 | + polygon.StrokeColor = Windows.UI.Color.FromArgb(128, 0, 0, 255); |
| 284 | + polygon.StrokeThickness = 5; |
| 285 | + polygon.Path = new Geopath(coordinates); |
| 286 | + nativeMap.MapElements.Add(polygon); |
| 287 | + } |
| 288 | + } |
| 289 | + } |
| 290 | +} |
| 291 | +``` |
| 292 | + |
| 293 | +This method performs the following operations, provided that the custom renderer is attached to a new Xamarin.Forms element: |
| 294 | + |
| 295 | +- The collection of latitude and longitude coordinates are retrieved from the `CustomMap.ShapeCoordinates` property and converted into a `List` of `BasicGeoposition` coordinates. |
| 296 | +- The polygon 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. |
| 297 | +- The polygon is rendered on the map by adding it to the `MapControl.MapElements` collection. Note that the polygon will be automatically closed by drawing a line that connects the first and last points. |
| 298 | + |
| 299 | +## Summary |
| 300 | + |
| 301 | +This article explained how to add a polygon overlay to a map, to highlight a region of the map. Polygons are a closed shape and have their interiors filled in. |
| 302 | + |
| 303 | +## Related Links |
| 304 | + |
| 305 | +- [Polygon Map Overlay (sample)](https://docs.microsoft.com/samples/xamarin/xamarin-forms-samples/customrenderers-map-polygon) |
| 306 | +- [Customizing a Map Pin](~/xamarin-forms/app-fundamentals/custom-renderer/map/customized-pin.md) |
| 307 | +- [Xamarin.Forms.Maps](xref:Xamarin.Forms.Maps) |
0 commit comments