This article is part of the Xamarin UI July initiative by Steven Thewissen.
While thinking about an article topic for Xamarin UI July, I reached for an article idea which I had in my backlog a while now - highlighting countries in the Xamarin.Forms map control. This functionality can be useful when your app is showing some data filtered by country or when the user provides their country during registration. Providing visual feedback to the user's selection makes the app come alive.
Getting the data
Each country on the world map is a set of polygonal areas (a single polygon is enough for most, but countries like Japan definitely need more than one). We need to find a data source with the approximate boundary of these areas so that we can display them on the map. Also - we need this in a format which is easily parseable in a Xamarin app. Chances are you have already encountered the JSON format, which is today the de-facto default for lightweight data transfer. It turns out, there is a variant of the JSON format specifically suited for geographical data - GeoJSON. While it is fully JSON compatible, so we could just read it with JSON.NET, there is also a wonderful open-source library which makes working with this format a breeze - GeoJSON.Net. With the format chosen, it's time to find a data source. Luckily, open-source is here to help us once more! This GitHub repository contains a lightweight (251 KB) GeoJSON file with rough geometries of all countries!
Perfect, we are ready to dive into code!
User interface
First, we will build a simple user interface on which we can demonstrate the country highlighting:
Nothing too fancy, just a simple ListView
of countries and a map control below. Xamarin.Forms Map can be included in your project by following a few simple steps detailed in the documentation. Make sure to note that Map
control is no longer part of the default Xamarin.Forms NuGet package and it is provided separately as a Xamarin.Forms.Maps NuGet package. The data-binding portions of the UI will be provided by the code-behind later in this article.
Map highlighting support
The built-in Xamarin.Forms map control does not have support for polygon drawing on top of the map. This is a functionality we definitely require and we will have to implement it ourselves. First off, we create a set of support classes to describe a polygon, and highlight on the map:
Note the MapHighlight
class supports multiple polygons, in line with the requirements of some countries as mentioned above. To introduce highlighting functionality to the map, we create a new HighlightableMap
control by deriving from Xamarin.Forms.Maps.Map
:
Map renderers
UWP
We start our platform-specific renderers with UWP. When the Highlight
property is updated, we clear the existing MapElements
from the native map control and add polygons from the current MapHighlight
instance. UWP MapPolygon
includes support for fill and stroke color and stroke thickness. We use the ToWindowsColor()
extension method to convert Xamarin.Forms color to UWP's Windows.UI.Color
.
Android
The Android map renderer is pretty straightforward and similar to UWP. The key difference here is that map initialization is finished asynchronously so we need to wait for it to complete to update the highlight in the first place.
iOS
iOS API for map polygons is probably the least pleasant of the three platforms. Displaying geometry on top of the map is done using a concept of "overlays", which however do not have their visual properties embedded. Those have to be provided in OverlayRenderer
delegate. We handle this in the OverlayRendererHandler
method. As we need a reference to the instance of MapHighlight
his is currently being drawn, we store it in a field.
App logic
Now with the map control ready to use, we can write the main logic of our application. For the sake of simplicity, I will put my code into the code-behind of our page, but it would be probably better suited in a view model. To represent a country, we create a simple POCO class. Note the code uses the cool new tuple-initialization in its constructor.
When the page loads, we need to initialize the list of countries first. The ReadCountryGeoJson()
method returns the contents of the countries.geo.json
file. In my case, I added the file as an embedded resource to the project.
The country data are parsed to a FeatureCollection
, which is a specialized type provided with GeoJSON.Net. This gives us easy access to the Id
field and name
property of the features. The SelectedItem
property of our ListView
is two-way bound to SelectedCountry
property. Let's implement that now:
Finally, we need to implement the HighlightCountry
method. Based on the country selection we find the GeoJSON.Net feature for it and check if it is a single- or multi-polygon area. We create appropriate MapPolygon
instances based on this and then instantiate the MapHighlight
.
To make sure the user actually sees the highlighted area, we are also calculating the minimum and maximum latitude and longitude of the area, and its center. The call of the MoveToRegion
method will bring the area into view. Note I am multiplying the area size by 1.2, just to show a bit of margin around the highlight as well.
Sample app
Here is our sample app running on UWP, Android and iOS. [gallery columns="2" size="large" type="slideshow" ids="1876,1877,1878"]
Source code
Full source code for this article along with the sample application is available on my GitHub.
Summary
We have shown how to add custom behavior to the map control in Xamarin.Forms and implemented a reusable highlighting feature with adjustable visuals. While we demonstrated this on country highlighting, it could be utilized for many other purposes as well.