At some point in the 100.x lifespan of ArcGIS Runtime SDK for .NET, the old tried-and-true method of treating a MapView as just another WPF Visual went sailing out the window. Granted, the ExportImageAsync method should have been a simple workaround, but for one drawback: overlay items are not included!
Now I don't know about you, but I find the OverlayItemsControl to be a great way to add interactive text to a map. You can have it respond to a mouse-over:
Bring up a context menu:
Modify properties:
And so on. In the old days, when you created an image of the MapView, the overlays would just come right along:
private RenderTargetBitmap GetMapImage(MapView mv)
{
// Save map transform
System.Windows.Media.Transform t = mv.LayoutTransform;
Rect r = System.Windows.Controls.Primitives.LayoutInformation.GetLayoutSlot(mv);
mv.LayoutTransform = null;
Size sz = new Size(mv.ActualWidth, mv.ActualHeight);
mv.Measure(sz);
mv.Arrange(new Rect(sz));
// Output map
RenderTargetBitmap rtBitmap = new RenderTargetBitmap(
(int)sz.Width, (int)sz.Height, 96d, 96d,
System.Windows.Media.PixelFormats.Pbgra32);
rtBitmap.Render(mv);
// Restore map transform
mv.Arrange(r);
mv.LayoutTransform = t;
return rtBitmap;
}
Not so today! Try that approach in 100.6 and you just get a black box.
My workaround:
Step 3 is trickier than you would think, however, because of two issues: 1) relating the anchor point to the overlay, and 2) taking any RenderTransform into account.
As far as I can tell, this is the rule for determining the relationship between the overlay and the anchor point:
HorizontalAlignment: Center or Stretch, anchor point is at the center; Left, anchor point is at the right; Right, anchor point is at the left.
VerticalAlignment: Center or Stretch, anchor point is at the center; Top, anchor point is at the bottom; Bottom, anchor point is at the top.
For a Canvas element, the anchor point is at 0,0 -- however, I have not found a good way to create an Image from a Canvas [if the actual width and height are unknown].
To create an Image from the element, any RenderTransform must be removed before generating the RenderTargetBitmap. Then, the Transform must be reapplied to the Image. Also, you need to preserve HorizontalAlignment and VerticalAlignment if you're creating a page layout using a copy of the MapView, so that the anchor point placement is correct.
So here it is, the code for my workaround:
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.UI;
using Esri.ArcGISRuntime.UI.Controls;
namespace Workarounds
{
public struct MapOverlayExport
{
public Image OverlayImage;
public MapPoint Anchor;
public MapPoint TopLeft;
}
public static class MapExportHelper
{
// Export bitmap from map with XAML graphics overlays
public static async Task<ImageSource> GetMapImage(MapView mv)
{
RuntimeImage ri = await mv.ExportImageAsync();
ImageSource src=await ri.ToImageSourceAsync();
if (mv.Overlays.Items.Count == 0)
return src; // No XAML overlays
// Create canvas
double dWidth = mv.ActualWidth;
double dHeight = mv.ActualHeight;
Rect rMap = new Rect(0, 0, dWidth, dHeight);
Size szMap = new Size(dWidth, dHeight);
Canvas c = new Canvas();
// Add map image
Image imgMap = new Image()
{
Height = dHeight,
Width = dWidth,
Source = src
};
imgMap.Measure(szMap);
imgMap.Arrange(rMap);
imgMap.UpdateLayout();
Canvas.SetTop(imgMap, 0);
Canvas.SetLeft(imgMap, 0);
c.Children.Add(imgMap);
// Add map overlays
List<MapOverlayExport> Overlays = GetMapOverlays(mv);
foreach (MapOverlayExport overlay in Overlays)
{
// Get Image and location
Image img = overlay.OverlayImage;
MapPoint ptMap = overlay.TopLeft;
Point ptScreen = mv.LocationToScreen(ptMap);
// Create and place image of element
Canvas.SetTop(img, ptScreen.Y);
Canvas.SetLeft(img, ptScreen.X);
c.Children.Add(img);
img.UpdateLayout();
}
c.Measure(szMap);
c.Arrange(rMap);
c.UpdateLayout();
// Create RenderTargetBitmap
RenderTargetBitmap rtBitmap = new RenderTargetBitmap(
(int)dWidth, (int)dHeight, 96d, 96d, PixelFormats.Pbgra32);
rtBitmap.Render(c);
return rtBitmap;
}
public static List<MapOverlayExport> GetMapOverlays(MapView mv)
{
List<MapOverlayExport> Overlays = new List<MapOverlayExport>();
foreach (object obj in mv.Overlays.Items)
{
// Get element and location
if (!(obj is FrameworkElement elem))
{
Debug.Print("MapExportHelper: Non-FrameworkElement encountered.");
continue;
}
double dW = elem.ActualWidth;
double dH = elem.ActualHeight;
if ((dH == 0) || (dW == 0))
{
Debug.Print("MapExportHelper: Unsupported FrameworkElement encountered.");
continue;
}
// Remove RenderTransform and RenderTransformOrigin
Transform tRender = elem.RenderTransform;
Point ptOrigin = elem.RenderTransformOrigin;
elem.RenderTransform = null;
elem.RenderTransformOrigin = new Point(0,0);
elem.Measure(new Size(dW, dH));
elem.Arrange(new Rect(0, 0, dW, dH));
elem.UpdateLayout();
// Create image of element
ImageSource src=null;
if (elem is Image imgSrc)
src=imgSrc.Source;
else
{
RenderTargetBitmap bmp = new RenderTargetBitmap(
(int)dW, (int)dH, 96d, 96d, PixelFormats.Pbgra32);
bmp.Render(elem);
src=bmp;
}
Image img = new Image()
{
Height = dH,
Width = dW,
Source = src,
HorizontalAlignment = elem.HorizontalAlignment,
VerticalAlignment = elem.VerticalAlignment,
RenderTransform = tRender,
RenderTransformOrigin = ptOrigin
};
// Restore RenderTransform and RenderTransformOrigin
elem.RenderTransform = tRender;
elem.RenderTransformOrigin = ptOrigin;
// Find top left location in map coordinates
MapPoint ptMap = MapView.GetViewOverlayAnchor(elem);
Point ptScreen = mv.LocationToScreen(ptMap);
double dY = 0;
double dX = 0;
switch (elem.VerticalAlignment)
{
case VerticalAlignment.Center:
case VerticalAlignment.Stretch:
dY = -dH / 2;
break;
case VerticalAlignment.Top:
dY = -dH;
break;
}
switch (elem.HorizontalAlignment)
{
case HorizontalAlignment.Center:
case HorizontalAlignment.Stretch:
dX = -dW / 2;
break;
case HorizontalAlignment.Left:
dX = -dW;
break;
}
Point ptTopLeftScreen = new Point(ptScreen.X + dX, ptScreen.Y + dY);
MapPoint ptTopLeftMap = mv.ScreenToLocation(ptTopLeftScreen);
// Add exported overlay to list
Overlays.Add(new MapOverlayExport()
{
OverlayImage = img,
Anchor = ptMap,
TopLeft = ptTopLeftMap
});
}
return Overlays;
}
}
}
P.S. -- If you want ExportImageAsync to include overlays, vote up this idea: https://community.esri.com/ideas/17558
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.