How to use elevation data in a MapView?

2148
13
02-16-2018 01:43 AM
OliverBoesche
New Contributor II

Hi,

I currently try to load DTED files in an offline application using the MapView, but found no way to get values from the DTED files. The following example shows my current approach. Rendering the DTED files with a Hillshade-Renderer is successfull. Therefore, it is simple to see where elevation data should be available.

private async void Initialize()

{

var elevationFileList = new List<string>()

       {

@"C:\tmp\example.dt0",

@"C:\tmp\example.dt1",

@"C:\tmp\example.dt2"

      };

 

this.surface = new Surface();

var rasterElevation = new RasterElevationSource(elevationFileList);

await rasterElevation.LoadAsync();

this.surface.ElevationSources.Add(rasterElevation);

await this.surface.LoadAsync();

}

 

 

private async void MapView_GeoViewTapped(object sender, GeoViewInputEventArgs e)

{

var altitude = await this.surface.GetElevationAsync(e.Location);

System.Console.WriteLine("Altitude:{0}", altitude);

}

As far as I understand the documentation it is only possible to use the elevation data when it is bound to a surface on a SceneView. I would appreciate every hint to find a solution for this problem.

Best regards,
Oliver

0 Kudos
13 Replies
OliverBoesche
New Contributor II

Has nobody a hint, solution or comment on this?

0 Kudos
MichaelBranscomb
Esri Frequent Contributor

Hi,

Unfortunately the GetElevationAsync operation is currently only available on elevation sources being used in a Scene/SceneView. And MapView IdentifyLayersAsync currently does not support raster layers.

But you can use the ArcGIS Runtime Local Server component to run operations against raster datasets, one of which is `Get Cell Value`. You do this by creating a Geoprocessing Package in ArcMap and using that as the basis of a Local Geoprocessing Service in the API.

Info and samples:

Local Server—ArcGIS Runtime SDK for .NET (WPF) | ArcGIS for Developers 

Local Server geoprocessing tools support—ArcGIS Runtime SDK for .NET (WPF) | ArcGIS for Developers 

What is a geoprocessing package?—ArcMap | ArcGIS Desktop 

arcgis-runtime-samples-dotnet/src/WPF/ArcGISRuntime.WPF.Samples/Samples/LocalServer/LocalServerGeopr... 

Cheers

Mike

0 Kudos
OliverBoesche
New Contributor II

Hi Mike,

thank you for the answer. Is this feature planned for the next runtime release? In my special case, I don't have anything additional available.

Is it possible to use the GeoProcessing Package approach without using ArcMap or only to create it once?

Regards,

Oliver

0 Kudos
BjørnarSundsbø1
Occasional Contributor II

We have a similar issue where we want elevation data for the mouse cursor in a status bar for MapView. In SceneView, we use get the location of the cursor from the base surface, which includes altitude. Ideally, we would like the same for MapView. Based on Michael Branscomb answer, this doesn't seem very likely. My MapView contains a WebMap configured in a portal, so I'm looking for a solution that is not too complex and requires special configuration of the application to be able to do this.

While Keith Gemeinhart‌'s suggestion might be a way to go, where I might be able to configure a hidden elevationsource (I've noticed this layers shows up as ArcGisTiledLayer), and look for layers where the portal item is a Image Service, and do some magic there, but I fear for the complexity. Another option might to do an Identify of sorts to get the height, but then the trouble would be to know which layer to identify, and get the right information. This might also be a bit too expensive for the status bar. For the MapView, I might put the elevation info somewhere else, that requires specific user interaction to display this.

Any suggestions on how to go about this, or might ScreenToLocation support elevation information in the next version? 

0 Kudos
KeithGemeinhart1
New Contributor III

I think you can still probably make the detached elevation source work. Here is some of my basic setup code for a scene view that uses the world 3D map on the arcgis.com server.

private readonly Uri _elevationUri = new Uri("http://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer");

private void SetupSceneElevation()
{    
    // Add the elevation surface.    
    ArcGISTiledElevationSource tiledElevationSource = new ArcGISTiledElevationSource(_elevationUri);
    Surface baseSurface = new Surface    
    {        
        ElevationSources = { tiledElevationSource }    
    };    

    TheSceneView.Scene.BaseSurface = baseSurface;
}‍‍‍‍‍‍‍‍‍‍‍‍‍‍

You could do this same thing in your code, but then skip line 12 because you're not attaching it to a view.

Obviously you will need to change the scope of my baseSurface variable, but you could then simply query the elevation at a map point using that:

    var elevation = await baseSurface.GetElevationAsync(point);

You'll have to work out the specifics of the map view, etc. but here is some code that shows how you could work this into the mouse move event handler:

var e = param as MouseEventArgs;
if (e == null)
    return null;

var screenpoint = e.GetPosition(mapViewModel.MapView);
var mappoint = mapViewModel.MapView.ScreenToLocation(screenpoint);

if (mappoint == null)
    return null;

if (mapViewModel.MapView.WrapAroundMode == WrapAroundMode.EnabledWhenSupported)
    mappoint = GeometryEngine.NormalizeCentralMeridian(mappoint) as MapPoint;
     
     
var elevation = await baseSurface.GetElevationAsync(mappoint);‍     
     ‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍‍

 
0 Kudos
BjørnarSundsbø1
Occasional Contributor II

Thank you for your suggestion, Keith Gemeinhart‌. I'll see if I can use your suggestion to work this out  I have no internet access from my clients, so I need to detect the elevation source from my WebMap, but I should be able to make that work. I might even be able to get this from my portal configuration through HelperServices.ElevationService Property.

I won't be able to test this until Monday, but would you happen to know how expensive this operation would happen to be, in terms of querying every time the cursor is moved in terms of request being sent to the image server every time? There is some delay built in, to reduce CPU usage already. If the same thing happens behind my SceneView, I guess I can't discriminate between 2D and 3D However, I do expect that the elevation information for my SceneView will already have this information present when I navigate.

0 Kudos
KeithGemeinhart1
New Contributor III

I don't know about the performance issues in your specific case. For me, responsiveness is not an issue. There is no delay in tracking and displaying the mouse location in a status bar.

However, my elevation source is built from local DTED files. So there is no possibility of a round-trip server hit.

My educated guess is that you will not incur a round-trip to the server every time you query the elevation of a surface. That seems like a very inefficient implementation. I suspect that the surface could have incremental snapshot or cache of elevation data. My intuition says that if it operates this way, the cached/snapshot area of elevation data would match that of an associated SceneView. These are total guesses on my part.

Maybe Michael Branscomb‌ could weigh in?

If that is the case, then you could try attaching that baseSurface to a behind-the-scenes SceneView (as show in code line 12, previous reply) and then syncing the SceneView's viewpoint to match that of your map view. There is some sample code for syncing map & scene displays in ESRI samples & docs, but here is what I have in my code as an example:

private void OnViewpointChanged(object sender, EventArgs e)
{
    if (!TheSceneView.IsVisible)
        return; // don't sync when it's hidden; to improve performance

    // Get the MapView or SceneView that sent the event
    GeoView sendingView = sender as GeoView;

    // Only take action if this geoview is the one that the user is navigating.
    // Viewpoint changed events are fired when SetViewpoint is called; This check prevents a feedback loop
    if (sendingView.IsNavigating)
    {
        // If the MapView sent the event, update the SceneView's viewpoint
        if (sender is MapView)
        {
            // Get the viewpoint
            Viewpoint updateViewpoint = TheMapView.GetCurrentViewpoint(ViewpointType.CenterAndScale);

            // Set the viewpoint
            TheSceneView.SetViewpoint(updateViewpoint);
        }
        else // Else, update the MapView's viewpoint
        {
            // Get the viewpoint
            Viewpoint updateViewpoint = TheSceneView.GetCurrentViewpoint(ViewpointType.CenterAndScale);

            // Set the viewpoint
            TheMapView.SetViewpoint(updateViewpoint);
        }
    }
}
BjørnarSundsbø1
Occasional Contributor II

Quick summary from Keith Gemeinhart‌'s suggestion, put into practice:

- Approach with creating a Scene with the layers works

- Involves a few additional loadAsync on the various elements; just on the Scene is not enough

- Not lightning fast, and does not work fast enough for mouse move and status bar (click and display works just fine)

- Cached Tiled Elevation Service from Organization settings is unfortunately not available from HelperServices Class, which is just silly.

0 Kudos
KeithGemeinhart1
New Contributor III

I realize this is an old question, but thought I would reply in case anyone is still looking for a solution or additional examples. You noted that, "... it is only possible to use the elevation data when it is bound to a surface on a SceneView ..." However, SceneView is just a class. It's not required to be bound to a map. So think of it as a tool that you can use to manage data. It's only visible to the user when bound to a map, but still valid and fully-functional behind the scenes! (Pun intended).

Mike provided a solution that uses a gpk, but that adds some complexity to the solution. Here are some code snippets that are consistent with your original ideas:

public Surface Dted1Surface { get; private set; }
public SceneView SceneView1 { get; private set; }
Dted1Surface = new Surface();
SceneView1 = new SceneView();

          
// Create a raster elevation source based on all files in dted1Files
RasterElevationSource dted1ElevSource = new RasterElevationSource(Dted1Files);
await dted1ElevSource.LoadAsync();
dted1ElevSource.Name = "DTED1";

// Create a Surface based on the elevation source
Dted1Surface.ElevationSources.Add(dted1ElevSource);
Dted1Surface.Name = "DTED1 Surface";
await Dted1Surface.LoadAsync();

// Create a Scene and assign the created surface to its BaseSurface
Scene dted1Scene = new Scene();
dted1Scene.BaseSurface = Dted1Surface;
await dted1Scene.LoadAsync();

// Create a SceneView and assign created scene to its Scene
SceneView1.Scene = dted1Scene;

MapPoint point;
// point = GetDesiredElevationLocation();
double result = await Dted1Surface.GetElevationAsync(point);
0 Kudos