Select to view content in your preferred language

How to get elevation from the MapView

409
7
09-27-2024 09:58 AM
IgnisDev
Emerging Contributor

Hello,

I have a question regarding elevation using the MapView in the ArcGIS SDK for Swift.

My use case is: when long pressing on the map, I would like to display a sheet with details about that location, including elevation data.

To accomplish this, I am currently adding a hidden SceneView behind the MapView. I then configure the scene with a base surface using a world elevation source. To synchronize the scene's viewpoint with the map, I listen for viewpoint changes on the map and continuously apply them to the scene. Then when the user long presses on the MapView, I get the elevation from the scene using:

let elevation = try? await scene.baseSurface.elevation(at: point)

This works, but has performance and memory implications.

Is there a way to get elevation information for a point only using the MapView?

For reference, I am using SwiftUI in Xcode 16, with arcgis-maps-sdk-swift version is 200.5.1.

Thank you!

Tags (2)
0 Kudos
7 Replies
KoushikHajra
Esri Contributor

Hello @IgnisDev:

Thank you for reaching out with your query. There are a couple of possible approaches you could try out. I'm laying out both of those for you to make the best choice for your needs. 

Option 1: 

You could query the world elevation service for every point that you get from your map to get the elevation for the point. Here is a post that shows how to achieve that. As the post describes, there is a possibility that you could get some inconsistent results based on the context. 

Option 2:

We have a new service, which is in Beta at the moment. Here are the details of the service and here is a blogpost showing the usage for it. This should work for the case you mentioned but as I mentioned, it is in  Beta and we would like you to join the Early Adopter Program and provide us some feedback on it. 

We hope either of these approaches would help with your performance problems of your app as long as you are querying in an async fashion. I hope this helps. Please keep us posted and let us know if these don't work. 

Regards,

Koushik 

imbachb
Regular Contributor

In our case our app also has to run offline so we could not rely on ESRI's APIs.

Instead, we created a raster layer with elevation data (for example based on DTED or tif) and then called `identify` to receive the stored raster cell value at a specific location, similar to how it is done in this example:Identify raster cell | ArcGIS Maps SDK for Swift | Esri Developer

To make the raster layer "invisible" (it must be visible otherwise identify won't work) we set its opacity to 0.00001.

Nicholas-Furness
Esri Regular Contributor

Hi @imbachb (this might also be of interested to @IgnisDev).

What you're doing is reasonable, but we do have APIs that can make it much easier for you.

What you need to do is create a standalone Surface. You can add 1 or more ElevationSources to it, where an ElevationSource can be an ArcGISTiledElevationSource (using a service or a local TPK/TPKX file), or it can be a RasterElevationSource (which can use one or more local DTED files).

Please note: when using a standalone Surface like this, you do currently need to explicitly load each of its ElevationSources.

Here's some sample Swift code (though you would use a RasterElevationSource instead)…

let surface: Surface = {
    let surface = Surface()
    
    let worldElevationServiceURL = URL(string: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer")!
    let elevationSource = ArcGISTiledElevationSource(url: worldElevationServiceURL)
    surface.addElevationSource(elevationSource)

    Task {
        await surface.elevationSources.load()
        let failedSources = surface.elevationSources.filter({ $0.loadStatus == .failed })
        for source in failedSources {
            print("Elevation source \(source) failed to load: \(String(describing: source.loadError))")
        }
    }
    
    return surface
}()

and you can then call it with something like this…

let elevationAtPoint = try await surface.elevation(at: mapPoint)

Hope that helps.

imbachb
Regular Contributor

Hi @Nicholas-Furness, many thanks for your insight. We did in fact not realise that we can use the Surface object as is, without adding it to a SceneView. Your approach is definitely more straight forward than our workaround.

One thing we noticed: The RasterElevationSource Class | ArcGIS Maps SDK for Qt load() function seems to be a blocking call. We have around 3k *.dt2 files that we'd like to use for our elevation data. The initial load causes the application to freeze for ~10 seconds while the debugger stays on the same line of code. We assume it's because ArcGIS indexes all these files first.

We'd like to be able to switch between maps with different elevation sources within our application but the freezing impacts the UX negatively. In our workaround with the elevation layer we used a MosaicDatasetRaster Class | ArcGIS Maps SDK for Qt which didn't have this issue.
We use ArcGIS SDK for Qt though, sorry if it is off topic here.

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Hi @imbachb. No problem! Not off topic yet 🙂

The load() method should not be blocking (though it might take some time to open and load all 3,000 files, but that should happen in the background). If you're stepping through in the debugger, that might have something to do with it. Can you set a breakpoint after the load rather than stepping through it? Or try the app without the debugger.

If that doesn't show things working as expected, then we are starting to get off topic here so could I ask you to pose the question about RasterElevationSource load() blocking over in the Qt forum please?

0 Kudos
imbachb
Regular Contributor

@Nicholas-Furness Thank you for the reply.
To us it does seem like the load function is at least partly blocking (maybe blocking is not the right terminology here):
I wrapped the load function between two `std::chrono::high_resolution_clock::now()` which results in a more than 10 seconds time difference.
I've created a new thread about this in the qt section: RasterElevationSource load() function seems to be ... - Esri Community

0 Kudos
Nicholas-Furness
Esri Regular Contributor

Yeah, the app should remain responsive while those 3000 files are opened (except perhaps when you're stepping through in the debugger). Thanks for opening that other issue. That's the best way to get feedback on this issue.

0 Kudos