Just a quick update to this earlier post;
Windows 10, UWP and Experimenting with Inking onto a Map Control
I got some feedback on that post from a few different places and people pointed out that I could have made more of ‘option 3’ in that post where I’d tried to overlay an InkCanvas on top of a MapControl.
Specifically;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Maps:MapControl x:Name="map" MapTapped="OnMapTapped" xmlns:Maps="using:Windows.UI.Xaml.Controls.Maps" /> <InkCanvas x:Name="inkCanvas" Visibility="Collapsed"> </InkCanvas> </Grid>
Now, I got some feedback on Twitter and elsewhere that I could make that InkCanvas non hit-testable and it’s true that this does work for me (thanks Dave!);
but, like Dave, I notice two things if (on OS 10586) I put a non-hit-testable InkCanvas over the top of my MapControl;
- I don’t get a MapTapped event as Dave says above.
- I notice a reasonable amount of lag when I do a pinch gesture which is going to the map control ‘through’ the InkCanvas control. It feels a little like I’m driving the map control through treacle and it’s not nearly as responsive as it would usually be.
so I veered away from having the InkCanvas initially Visible and using IsHitTestVisible=”False” and moved more towards having the InkCanvas be initially Collapsed as you can see in the XAML above and then dynamically making it visible as/when I wanted it.
That then ties up with this code behind where you’ll notice that I’ve now got 2 different ways of trying to turn an ink stroke into a set of waypoints and I suspect that neither of them are ‘perfect’ by any means and maybe you have a better one;
#define INK_POINTS namespace Update { using System; using System.Collections.Generic; using System.Linq; using Windows.Devices.Geolocation; using Windows.Foundation; using Windows.Services.Maps; using Windows.UI; using Windows.UI.Input.Inking; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Maps; public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.tapPositions = new MapInputEventArgs[POI_COUNT]; this.Loaded += OnLoaded; } void OnLoaded(object sender, RoutedEventArgs e) { this.inkCanvas.InkPresenter.StrokesCollected += OnStrokesCollected; var drawingAttr = this.inkCanvas.InkPresenter.CopyDefaultDrawingAttributes(); drawingAttr.PenTip = PenTipShape.Rectangle; drawingAttr.Size = new Size(4, 4); drawingAttr.IgnorePressure = true; drawingAttr.Color = Colors.Orange; this.inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttr); } Geopoint GeopointFromPoint(Point point) { Geopoint geoPoint = null; this.map.GetLocationFromOffset(point, out geoPoint); return (geoPoint); } async void OnStrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args) { // We're going to take the first stroke, we're not going to try and // somehow stitch together many strokes :-S var firstStroke = args.Strokes.FirstOrDefault(); if (firstStroke != null) { // How to split this down into some waypoints? var geoPoints = new List<Geopoint>(); // add the initial point. geoPoints.Add(GeopointFromPoint(this.tapPositions[0].Position)); // Now try to add some intermediate waypoints to try and 'guide' // the route so that it follows the line that's been drawn. // I'm not sure how much this next section of code is going to cause // duplication by (e.g.) adding the same or similar points along the // route due to the ink points potentially being very close together // etc. #if INK_POINTS // Add some sprinkling of the ink points...I've chosen 20,40, etc. var inkPoints = firstStroke.GetInkPoints(); const int SAMPLE_RATE = 20; for (int i = SAMPLE_RATE; i < inkPoints.Count; i += SAMPLE_RATE) { geoPoints.Add(GeopointFromPoint(inkPoints[i].Position)); } #else // Add the positions of the segments that make up the ink stroke. foreach (var segment in firstStroke.GetRenderingSegments()) { geoPoints.Add(GeopointFromPoint(segment.Position)); } #endif // add the follow on point. geoPoints.Add(GeopointFromPoint(this.tapPositions[1].Position)); var routeResult = await MapRouteFinder.GetDrivingRouteFromWaypointsAsync(geoPoints); // We should do something about failures too if (routeResult.Status == MapRouteFinderStatus.Success) { var mapPolyline = new MapPolyline(); mapPolyline.Path = routeResult.Route.Path; mapPolyline.StrokeThickness = 4; mapPolyline.StrokeColor = Colors.Orange; mapPolyline.Visible = true; this.map.MapElements.Add(mapPolyline); } } this.inkCanvas.Visibility = Visibility.Collapsed; this.inkCanvas.InkPresenter.StrokeContainer.Clear(); } void OnMapTapped(MapControl sender, MapInputEventArgs args) { var mapElementCount = this.map.MapElements.Count; if (mapElementCount < POI_COUNT) { this.tapPositions[mapElementCount] = args; var mapIcon = new MapIcon() { Location = args.Location, NormalizedAnchorPoint = new Point(0.5, 0.5) }; this.map.MapElements.Add(mapIcon); // When you add a 2nd point of interest, we put an InkCanvas over the entire map // waiting to see what you do next. If your next move is to use a pen then // we'll wait for an ink stroke. If your next move is to use something // other than a pen then we'll get rid of the InkCanvas and get out of your // way. mapElementCount = this.map.MapElements.Count; if (mapElementCount == POI_COUNT) { // Switch the ink canvas on and leave it on until the user completes // a stroke. this.inkCanvas.Visibility = Visibility.Visible; } } } static readonly int POI_COUNT = 2; MapInputEventArgs[] tapPositions; } }
And that works pretty well for me compared to my original post and it’s quite a bit less code (especially if you removed the conditional compilation).
If I take away the requirement to handle MapTapped event then I could make the InkCanvas initially visible and non hit-testable as in;
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Maps:MapControl x:Name="map" xmlns:Maps="using:Windows.UI.Xaml.Controls.Maps" /> <InkCanvas x:Name="inkCanvas" IsHitTestVisible="False"> </InkCanvas> </Grid>
and then my code behind does a little less as it does not wait for 2 taps to add 2 points of interest but, instead, just waits for a single line to be drawn and uses it to try and sketch out a route;
using System; using System.Collections.Generic; using System.Linq; using Windows.Devices.Geolocation; using Windows.Foundation; using Windows.Services.Maps; using Windows.UI; using Windows.UI.Input.Inking; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Maps; public sealed partial class MainPage : Page { public MainPage() { this.InitializeComponent(); this.Loaded += OnLoaded; } void OnLoaded(object sender, RoutedEventArgs e) { this.inkCanvas.InkPresenter.StrokesCollected += OnStrokesCollected; var drawingAttr = this.inkCanvas.InkPresenter.CopyDefaultDrawingAttributes(); drawingAttr.PenTip = PenTipShape.Rectangle; drawingAttr.Size = new Size(4, 4); drawingAttr.IgnorePressure = true; drawingAttr.Color = Colors.Orange; this.inkCanvas.InkPresenter.UpdateDefaultDrawingAttributes(drawingAttr); } Geopoint GeopointFromPoint(Point point) { Geopoint geoPoint = null; this.map.GetLocationFromOffset(point, out geoPoint); return (geoPoint); } async void OnStrokesCollected(InkPresenter sender, InkStrokesCollectedEventArgs args) { // We're going to take the first stroke, we're not going to try and // somehow stitch together many strokes :-S var firstStroke = args.Strokes.FirstOrDefault(); if (firstStroke != null) { // How to split this down into some waypoints? var geoPoints = new List<Geopoint>(); // Now try to add some intermediate waypoints to try and 'guide' // the route so that it follows the line that's been drawn. // I'm not sure how much this next section of code is going to cause // duplication by (e.g.) adding the same or similar points along the // route due to the ink points potentially being very close together // etc. #if INK_POINTS // Add some sprinkling of the ink points...I've chosen 20,40, etc. var inkPoints = firstStroke.GetInkPoints(); const int SAMPLE_RATE = 20; for (int i = SAMPLE_RATE; i < inkPoints.Count; i += SAMPLE_RATE) { geoPoints.Add(GeopointFromPoint(inkPoints[i].Position)); } #else // Add the positions of the segments that make up the ink stroke. foreach (var segment in firstStroke.GetRenderingSegments()) { geoPoints.Add(GeopointFromPoint(segment.Position)); } #endif var routeResult = await MapRouteFinder.GetDrivingRouteFromWaypointsAsync(geoPoints); // We should do something about failures too if (routeResult.Status == MapRouteFinderStatus.Success) { var mapPolyline = new MapPolyline(); mapPolyline.Path = routeResult.Route.Path; mapPolyline.StrokeThickness = 4; mapPolyline.StrokeColor = Colors.Orange; mapPolyline.Visible = true; this.map.MapElements.Add(mapPolyline); } } this.inkCanvas.InkPresenter.StrokeContainer.Clear(); } }
and that works fine albeit with that slight lag that gets introduced (on OS 10586 at least) by having the InkCanvas over the top of the MapControl.