Windows Phone 8.1 for Developers–Maps
This blog post is part of a series about how Windows Phone 8.1 affects developers. This blog post talks about maps and is written by Andreas Ekberg at Jayway and was originally posted here.
Learning how to use Maps in the .Net world is a never ending business. There are major API-differences between WPF, Silverlight, Windows 8 Store apps and Windows Phone and even between WP7 and WP8. Windows Phone 8.1 makes no differ and there are many breaking changes here. In the end of the article I will try to cover most of the namespace, class- and method-renames but first we talk about things that are more fun: the new stuff including Custom MapTiles and support for TrafficFlow.
Better support for custom map tiles
Most digital maps used today on the web and in mobile devices use the same concept of map tiles. The map is just a grid of images with the same sizes and where each image, called a map tile has an X/Y-coordinate and belongs to exactly one zoom level. For every new greater zoom level a tile is replaced by four new ones and the map gets more detailed. For more details of the Bing Maps Tile System look here. Many have complained about the poor support for custom map tiles in windows phone. In 8.1 things look much better. Finally you can with little effort support your own tiles and you have more control over the different map layers. You add/change a tiles layer with the TileSources collection where you add objects of type MapTileSource. Here you can set things like Zindex, tile pixel size and visibility. You can even allow or forbid stretching of the tiles while a higher-resolution tiles are being downloaded. The Layer property can be used to set the layer type of the tile source like BackgroundReplacement, BackgroundOverlay and RoadOverlay. The DataSource is used to specify the actual map tile data source and can be set to any object that inherits MapTileDataSource. There are currently three derived data source classes out of the box:
- HttpMapTileDataSource – The tiles are fetched by using HTTP or HTTPS
- LocalMapTileDataSource - The tiles are fetched by using a local protocol such as ms-appdata
- CustomMapTileDataSource - custom implementation where you have full control and feed the tile requests with raw pixels using IRandomAccessStreamReference
To replace the Bing map tiles with tiles from OpenStreetMap:
var httpsource = new HttpMapTileDataSource("https://a.tile.openstreetmap.org/{zoomlevel}/{x}/{y}.png");
var ts = new MapTileSource(httpsource);
mapControl.TileSources.Add(ts);
Unfortunately there seem to be no way to customize the behaviours of the {subdomain} replacement string in the UriFormat (set in the constructor above) anymore. It always gives values between 0-7 and OpenStreetMap uses the letters a-c for its subdomains. If you want to add load balancing you have to use the UriRequested event:
var next = "a";
var httpsource = new HttpMapTileDataSource();
httpsource.UriRequested += (source, args) =>
{
var deferral = args.Request.GetDeferral();
next = GetNext(next);
args.Request.Uri = new Uri("https://" + next + ".tile.openstreetmap.org/" +
args.ZoomLevel + "/" + args.X + "/" + args.Y + ".png");
deferral.Complete();
};
var ts = new MapTileSource(htds);
GetNext() is just a help function that iterates through the letters a,b and c. If you want full control you can implement your own custom tile data source:
var customSource = new CustomMapTileDataSource();
customSource.BitmapRequested += async (o, args) =>
{
var deferral = args.Request.GetDeferral();
args.Request.PixelData = await CreateBitmapAsStreamAsync();
deferral.Complete();
};
var ts = new MapTileSource(customSource);
mapControl.TileSources.Add(ts);
where CreateBitmapAsStreamAsync() is your own method where you create/download the tile and return it as a stream.
Traffic flow
Both the map and routing now support traffic flow. To display traffic conditions on the map is as simple as:
myMapControl.TrafficFlowVisible = true;
If you want to calculate the fastest driving route with respect to traffic flow from your current location to the Jayway office in Malmö you can write:
var gl = new Geolocator() { DesiredAccuracy = PositionAccuracy.High };
var locationGeoPos = await gl.GetGeopositionAsync(TimeSpan.FromMinutes(5),
TimeSpan.FromSeconds(5));
var location = locationGeoPos.Coordinate.Point;
var endPoint = new Geopoint(
new BasicGeoposition()
{
Latitude = 55.6127809826285,
Longitude = 13.0031764693558
});
var mapRouteFinderResult =
await MapRouteFinder.GetDrivingRouteAsync(location, endPoint,
MapRouteOptimization.TimeWithTraffic);
Routing news
In addition to the support for traffic info you can also specify some restrictions to your route query like avoid highways, toll roads, ferries, tunnels and/or dirt roads. Here is an example with a modified query to the code above that avoids dirt roads and ferries and takes into account that we start heading south .The route is then displayed on the map and a dialogue with the navigation step by step instructions is shown.
var finderResult = await MapRouteFinder.GetDrivingRouteAsync(location, kvarnby,
MapRouteOptimization.Distance,
MapRouteRestrictions.DirtRoads | MapRouteRestrictions.Ferries, 180);
if (finderResult.Status != MapRouteFinderStatus.Success) return;
var route = finderResult.Route;
var mapRouteView = new MapRouteView(route);
mapRouteView.RouteColor = Colors.Black;
mapControl.Routes.Add(mapRouteView);
var firstRouteLeg = route.Legs[0];
var desc = "";
foreach (var routeManeuver in firstRouteLeg.Maneuvers)
{
desc += routeManeuver.LengthInMeters.ToString() + "m : " +
routeManeuver.InstructionText + "\n";
}
new MessageDialog(desc).ShowAsync();
Other MapControl news
- The map now have touch support for rotation. It seems to be no way to turn of this rotation support if you want to, but I hope that will be added in the future.
- You can limit the Zoomlevels with MinZoomLevel and MaxZoomLevel.
- You can listen to touch events and get both GeoPoint and local UI coordinates related to the touch point with the MapTapped, MapDoubleTapped and MapHolding events
- You can check if a location is visible on current map window with IsLocationInView()
API changes
One of the biggest changes in the API is that the GeoPoint class is now used everywhere the GeoCoordinate was used before. This makes the API more consistent, at the cost of more characters to write.
var endPoint = new Geopoint(
new BasicGeoposition()
{
Latitude = 55.5896818824112,
Longitude = 13.1075582373887
});
Another thing that have changed is how objects like pushpins are added. The old MapLayer and MapOverlay is gone. Now you just add the controls to the MapControl.Children collection. The coordinate and position origin are set by the two attached properties Location and NormalizedAnchorPoint. Here is a code example where you add a pin to the current location of the user and zoom in around the same location. There is no built-in pushpin control, so you have to create your own control that inherits DependencyObject or just copy the one from the old Phone Toolkit.
var location = await gl.GetGeopositionAsync(TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(5));
map1.TrySetViewAsync(location.Coordinate.Point, 15, 0, 0, MapAnimationKind.Bow);
map1.Children.Add(myPushPin);
MapControl.SetLocation(myPushPin, location.Coordinate.Point);
MapControl.SetNormalizedAnchorPoint(myPushPin, new Point(0.5, 0.5));
Some other changes worth mentioning:
- No more need for a map capability (ID_CAP_MAP)
- The query classes (RouteQuery, GeocodeQuery, ReverseGeocodeQuery) has been replayed with the two finder classes MapRouteFinder and MapLocationFinder.
- The MapControl.Routes property replaces the Add/RemoveRoute methods.
- The SetView methods is made async with TrySetViewAsync.
- The specific EventHandler<* ChangedEventArgs> are replaced with TypedEventHandler<MapControl, Object>.
Finally here is an unstructured, probably incomplete table that tries to map old namespaces, classes and methods to their new names: Have fun mapping around!
Microsoft.Phone.Maps.Controls | Windows.UI.Xaml.Controls.Maps |
Microsoft.Phone.Maps.Services | Windows.Services.Maps |
Map | MapControl |
Map.Center(GeoCoordinate) | MapControl.Center(Geopoint) |
Map.ColorMode(MapColorMode) | Map.ColorScheme(MapColorScheme) |
Map.LandmarksEnabled | MapControl.LandmarksVisible |
Map.PedestrianFeaturesEnabled | MapControl.PedestrianFeaturesVisible |
Map.Pitch | MapControl.DesiredPitch |
Map.ActualPitch | MapControl.Pitch |
Map.Add/RemoveRoute(MapRoute) | MapControl.Routes(MapRouteView) |
Map.CartographicMode | MapControl.Style |
MapCartographicMode | MapStyle |
Map.TileSource(TileSource) | MapControl.TileSource(MapTileSource) |
Map.TransformCenter | MapControl.TransformOrigin |
Map.GetMapElementsAt() | MapControl.FindMapElementsAtOffset() |
Map.SetView | MapControl.TrySetViewAsync() |
Map.SetView | MapControl.TrySetViewBoundsAsync() |
Map.ConvertViewportPointToGeoCoordinate | MapControl.GetLocationFromOffset() |
Map.ConvertGeoCoordinateToViewportPoint | MapControl.GetOffsetFromLocation() |
Map.ResolveCompleted(e) | MapControl.LoadingStatusChanged(e) |
Map.Layers | MapControl.Children |
Map.MapRoute | MapControl.MapRouteView |
Comments
Anonymous
April 29, 2014
The comment has been removedAnonymous
May 01, 2014
Trying to implement CreateBitmapAsStreamAsync: Following code doesn't work: var imageUri = new Uri("ms-appx:///Assets/backgroundimage.png"); return RandomAccessStreamReference.CreateFromUri(imageRef); Image exists, is being opened and loaded correctly into BitmapImage (tested just in case something wrong is with image). Tried with jpg/bmp/png. Thanks. @AlexSorokoletovAnonymous
May 04, 2014
Herman: Hi, I saw your own reply on the original post on the Jayway blog and posted a reply thereAnonymous
May 04, 2014
Alex: Sorry for being unclear, the function should have been called CreatePixelDataAsStreamAsync because the stream should be a raw pixel stream with four bytes (RGBA) for every pixel, from the top left pixel and on. CustomMapTileDataSource is to be used when you creates or make changes to the tiles at pixel level. If you already have the tiles on disk its easier to use LocalMapTileDataSource. If you for some reason still want to use CustomMapTileDataSource you could use GetPixelDataAsync() from BitmapDecoder to get the pixels from an image.Anonymous
May 12, 2014
Is there any link in which I could find some guidance to create a custom push pin for Windows Phone 8.1 without using the windows phone toolkit?