Calculate a viewshed using a geoprocessing service, in this case showing which parts of a landscape are visible from points on mountainous terrain.
Use case
A viewshed is used to highlight what is visible from a given point. A viewshed could be created to show what a hiker might be able to see from a given point at the top of a mountain. Equally, a viewshed could also be created from a point representing the maximum height of a proposed wind turbine to see from what areas the turbine would be visible.
How to use the sample
Click the map to see all areas visible from that point within a 15km radius. Clicking on an elevated area will highlight a larger part of the surrounding landscape. It may take a few seconds for the task to run and send back the results.
How it works
- Create a
GeoprocessingTask
object with the URL set to a geoprocessing service endpoint. - Create a
FeatureCollectionTable
object and add a newFeature
object whose geometry is the viewshed's observerPoint
. - Make a
GeoprocessingParameters
object passing in the observer point. - Use the geoprocessing task to create a
GeoprocessingJob
object with the parameters. - Start the job and wait for it to complete and return a
GeoprocessingResult
object. - Get the resulting
GeoprocessingFeatures
object. - Iterate through the viewshed features to use their geometry or display the geometry in a new
Graphic
object.
Relevant API
- FeatureCollectionTable
- GeoprocessingFeatures
- GeoprocessingJob
- GeoprocessingParameters
- GeoprocessingResult
- GeoprocessingTask
Tags
geoprocessing, heat map, heatmap, viewshed
Sample Code
// [WriteFile Name=AnalyzeViewshed, Category=Analysis] // [Legal] // Copyright 2016 Esri. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // [Legal] #ifdef PCH_BUILD #include "pch.hpp" #endif // PCH_BUILD // sample headers #include "AnalyzeViewshed.h" // ArcGIS Maps SDK headers #include "Error.h" #include "Feature.h" #include "FeatureCollectionTable.h" #include "FeatureIterator.h" #include "Field.h" #include "GeoprocessingFeatures.h" #include "GeoprocessingJob.h" #include "GeoprocessingParameter.h" #include "GeoprocessingParameters.h" #include "GeoprocessingResult.h" #include "GeoprocessingTask.h" #include "GeoprocessingTypes.h" #include "Graphic.h" #include "GraphicListModel.h" #include "GraphicsOverlay.h" #include "GraphicsOverlayListModel.h" #include "Map.h" #include "MapQuickView.h" #include "MapTypes.h" #include "MapViewTypes.h" #include "Point.h" #include "SimpleFillSymbol.h" #include "SimpleMarkerSymbol.h" #include "SimpleRenderer.h" #include "SpatialReference.h" #include "SymbolTypes.h" #include "TaskTypes.h" #include "Viewpoint.h" // Qt headers #include <QFuture> #include <QMouseEvent> #include <QUuid> using namespace Esri::ArcGISRuntime; AnalyzeViewshed::AnalyzeViewshed(QQuickItem* parent /* = nullptr */): QQuickItem(parent) { } AnalyzeViewshed::~AnalyzeViewshed() = default; void AnalyzeViewshed::init() { qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<AnalyzeViewshed>("Esri.Samples", 1, 0, "AnalyzeViewshedSample"); } void AnalyzeViewshed::componentComplete() { QQuickItem::componentComplete(); // find QML MapView component m_mapView = findChild<MapQuickView*>("mapView"); m_mapView->setWrapAroundMode(WrapAroundMode::Disabled); // Create a map using the topographic basemap m_map = new Map(BasemapStyle::ArcGISTopographic, this); m_map->setInitialViewpoint(Viewpoint(Point(6.84905317262762, 45.3790902612337, SpatialReference(4326)), 100000)); // Set map to map view m_mapView->setMap(m_map); // Create the GeoprocessingTask m_viewshedTask = new GeoprocessingTask(QUrl("https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/ESRI_Elevation_World/GPServer/Viewshed"), this); // Create the Graphics Overlays createOverlays(); // Connect signals connectSignals(); } void AnalyzeViewshed::createOverlays() { // Create the graphics overlays for the input and output m_inputOverlay = new GraphicsOverlay(this); m_inputGraphic = new Graphic(this); m_inputOverlay->graphics()->append(m_inputGraphic); SimpleMarkerSymbol* sms = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Circle, QColor("red"), 12.0, this); SimpleRenderer* inputRenderer = new SimpleRenderer(sms, this); m_inputOverlay->setRenderer(inputRenderer); m_mapView->graphicsOverlays()->append(m_inputOverlay); m_resultsOverlay = new GraphicsOverlay(this); SimpleFillSymbol* sfs = new SimpleFillSymbol(SimpleFillSymbolStyle::Solid, QColor(226, 119, 40, 100), this); SimpleRenderer* outputRenderer = new SimpleRenderer(sfs, this); m_resultsOverlay->setRenderer(outputRenderer); m_mapView->graphicsOverlays()->append(m_resultsOverlay); } void AnalyzeViewshed::connectSignals() { // Set up signal handler for the mouse clicked signal connect(m_mapView, &MapQuickView::mouseClicked, this, [this](QMouseEvent& mouse) { // The geoprocessing task is still executing, don't do anything else (i.e. respond to // more user taps) until the processing is complete. if (m_viewshedInProgress) return; // Indicate that the geoprocessing is running m_viewshedInProgress = true; // Clear previous viewshed geoprocessing task results m_resultsOverlay->graphics()->clear(); if (m_graphicParent) { delete m_graphicParent; m_graphicParent = nullptr; } // Create a marker graphic where the user clicked on the map and add it to the existing graphics overlay Point mapPoint = m_mapView->screenToLocation(mouse.position().x(), mouse.position().y()); if (m_inputGraphic) m_inputGraphic->setGeometry(mapPoint); // Setup the geoprocessing task calculateViewshed(); }); // Connect to the GP Task's errorOccurred signal connect(m_viewshedTask, &GeoprocessingTask::errorOccurred, this, [this](const Error& error) { emit displayErrorDialog("Geoprocessing Task failed", error.message()); }); } void AnalyzeViewshed::calculateViewshed() { // Create a new feature collection table based upon point geometries using the current map view spatial reference FeatureCollectionTable* inputFeatures = new FeatureCollectionTable(QList<Field>(), GeometryType::Point, SpatialReference::webMercator(), this); // Create a new feature from the feature collection table. It will not have a coordinate location (x,y) yet Feature* inputFeature = inputFeatures->createFeature(this); // Assign a physical location to the new point feature based upon where the user clicked on the map view inputFeature->setGeometry(m_inputOverlay->graphics()->at(0)->geometry()); // Add the new feature with (x,y) location to the feature collection table inputFeatures->addFeatureAsync(inputFeature).then(this, [this, inputFeatures]() { onAddFeatureCompleted_(inputFeatures); }); } void AnalyzeViewshed::onAddFeatureCompleted_(FeatureCollectionTable* inputFeatures) { // Create the parameters that are passed to the used geoprocessing task GeoprocessingParameters viewshedParameters = GeoprocessingParameters(GeoprocessingExecutionType::SynchronousExecute); // Request the output features to use the same SpatialReference as the map view viewshedParameters.setOutputSpatialReference(SpatialReference::webMercator()); // Add an input location to the geoprocessing parameters QMap<QString, GeoprocessingParameter*> inputs; inputs["Input_Observation_Point"] = new GeoprocessingFeatures(inputFeatures, this); viewshedParameters.setInputs(inputs); // Create the job that handles the communication between the application and the geoprocessing task GeoprocessingJob* viewshedJob = m_viewshedTask->createJob(viewshedParameters); // Create signal handler for the job connect(viewshedJob, &GeoprocessingJob::statusChanged, this, [this, viewshedJob](JobStatus jobStatus) { switch (jobStatus) { case JobStatus::Failed: emit displayErrorDialog("Geoprocessing Task failed", !viewshedJob->error().isEmpty() ? viewshedJob->error().message() : "Unknown error."); m_viewshedInProgress = false; m_jobStatus = "Job failed"; break; case JobStatus::Started: m_viewshedInProgress = true; m_jobStatus = "Job in progress..."; break; case JobStatus::Paused: m_viewshedInProgress = false; m_jobStatus = "Job paused..."; break; case JobStatus::Succeeded: m_viewshedInProgress = false; m_jobStatus = "Job succeeded"; // handle the results processResults(viewshedJob->result()); break; default: break; } // emit signals emit viewshedInProgressChanged(); emit statusChanged(); }); // start the job viewshedJob->start(); } void AnalyzeViewshed::processResults(GeoprocessingResult *results) { // Get the results from the outputs as GeoprocessingFeatures const auto outputs = results->outputs(); GeoprocessingFeatures* viewshedResultFeatures = static_cast<GeoprocessingFeatures*>(outputs["Viewshed_Result"]); // Create the parent for the graphic if (!m_graphicParent) m_graphicParent = new QObject(this); // Add all the features from the result feature set as a graphics to the map FeatureIterator features = viewshedResultFeatures->features()->iterator(); while (features.hasNext()) { Feature* feat = features.next(this); Graphic* graphic = new Graphic(feat->geometry(), m_graphicParent); m_resultsOverlay->graphics()->append(graphic); } }