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.
Note: This analysis requires a geoprocessing service endpoint in a connected environment and processing times may vary depending on connection speeds. If fast, offline analysis is required, consider using a ViewshedFunction to perform a viewshed instead.
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
GeoprocessingTaskobject with the URL set to a geoprocessing service endpoint. - Create a
FeatureCollectionTableobject and add a newFeatureobject whose geometry is the viewshed’s observerPoint. - Make a
GeoprocessingParametersobject passing in the observer point. - Use the geoprocessing task to create a
GeoprocessingJobobject with the parameters. - Start the job and wait for it to complete and return a
GeoprocessingResultobject. - Get the resulting
GeoprocessingFeaturesobject. - Iterate through the viewshed features to use their geometry or display the geometry in a new
Graphicobject.
Relevant API
- FeatureCollectionTable
- GeoprocessingFeatures
- GeoprocessingJob
- GeoprocessingParameters
- GeoprocessingResult
- GeoprocessingTask
Tags
geoprocessing, heat map, heatmap, viewshed
Sample Code
// [WriteFile Name=ShowViewshedCalculatedFromGeoprocessingTask, 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 "ShowViewshedCalculatedFromGeoprocessingTask.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;
ShowViewshedCalculatedFromGeoprocessingTask::ShowViewshedCalculatedFromGeoprocessingTask(QQuickItem* parent /* = nullptr */) : QQuickItem(parent){}
ShowViewshedCalculatedFromGeoprocessingTask::~ShowViewshedCalculatedFromGeoprocessingTask() = default;
void ShowViewshedCalculatedFromGeoprocessingTask::init(){ qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<ShowViewshedCalculatedFromGeoprocessingTask>("Esri.Samples", 1, 0, "ShowViewshedCalculatedFromGeoprocessingTaskSample");}
void ShowViewshedCalculatedFromGeoprocessingTask::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 ShowViewshedCalculatedFromGeoprocessingTask::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 ShowViewshedCalculatedFromGeoprocessingTask::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 ShowViewshedCalculatedFromGeoprocessingTask::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 ShowViewshedCalculatedFromGeoprocessingTask::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 ShowViewshedCalculatedFromGeoprocessingTask::processResults(GeoprocessingResult* results){ // Get the results from the outputs as GeoprocessingFeatures const QMap<QString, GeoprocessingParameter*> 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); }}// [WriteFile Name=ShowViewshedCalculatedFromGeoprocessingTask, 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]
#ifndef ShowViewshedCalculatedFromGeoprocessingTask_H#define ShowViewshedCalculatedFromGeoprocessingTask_H
// Qt headers#include <QQuickItem>
namespace Esri::ArcGISRuntime{ class FeatureCollectionTable; class GraphicsOverlay; class GeoprocessingTask; class GeoprocessingResult; class Graphic; class Map; class MapQuickView;} // namespace Esri::ArcGISRuntime
class ShowViewshedCalculatedFromGeoprocessingTask : public QQuickItem{ Q_OBJECT
Q_PROPERTY(bool viewshedInProgress MEMBER m_viewshedInProgress NOTIFY viewshedInProgressChanged) Q_PROPERTY(QString statusText READ jobStatus NOTIFY statusChanged)
public: explicit ShowViewshedCalculatedFromGeoprocessingTask(QQuickItem* parent = nullptr); ~ShowViewshedCalculatedFromGeoprocessingTask() override;
void componentComplete() override; static void init();
signals: void viewshedInProgressChanged(); void displayErrorDialog(const QString& titleText, const QString& detailedText); void statusChanged();
private: void onAddFeatureCompleted_(Esri::ArcGISRuntime::FeatureCollectionTable* inputFeatures);
Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_inputOverlay = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_resultsOverlay = nullptr; Esri::ArcGISRuntime::GeoprocessingTask* m_viewshedTask = nullptr; Esri::ArcGISRuntime::Graphic* m_inputGraphic = nullptr; bool m_viewshedInProgress = false; QString m_jobStatus; QObject* m_graphicParent = nullptr;
private: void connectSignals(); void createOverlays(); void calculateViewshed(); void processResults(Esri::ArcGISRuntime::GeoprocessingResult* results);
QString jobStatus() const { return m_jobStatus; }};
#endif // ShowViewshedCalculatedFromGeoprocessingTask_H// [WriteFile Name=ShowViewshedCalculatedFromGeoprocessingTask, 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]
import QtQuickimport QtQuick.Controlsimport QtQuick.Layoutsimport Esri.Samples
ShowViewshedCalculatedFromGeoprocessingTaskSample { id: rootRectangle clip: true width: 800 height: 600
MapView { anchors.fill: parent objectName: "mapView"
Component.onCompleted: { // Set the focus on MapView to initially enable keyboard navigation forceActiveFocus(); } }
onDisplayErrorDialog: { messageDialog.title = qsTr("Error"); messageDialog.text = titleText; messageDialog.detailedText = detailedText; messageDialog.open(); }
// Create rectangle to display the status Rectangle { anchors { margins: -10 fill: statusColumn } color: palette.base radius: 5 border.color: "black" opacity: 0.85 }
Column { id: statusColumn anchors { right: parent.right top: parent.top margins: 20 }
Label { anchors.margins: 5 visible: !viewshedInProgress text: qsTr("Click map to execute viewshed analysis") font.pixelSize: 12 }
Row { anchors.margins: 5 visible: viewshedInProgress spacing: 10
BusyIndicator { anchors.verticalCenter: parent.verticalCenter width: 32 height: width }
Label { anchors.verticalCenter: parent.verticalCenter text: statusText font.pixelSize: 12 } } }
MouseArea { anchors.fill: statusColumn acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => mouse.accepted = true onDoubleClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true }
Dialog { id: messageDialog modal: true x: Math.round(parent.width - width) / 2 y: Math.round(parent.height - height) / 2 standardButtons: Dialog.Ok title: qsTr("Error") property alias text : textLabel.text property alias detailedText : detailsLabel.text ColumnLayout { Label { id: textLabel text: qsTr("Executing geoprocessing failed.") } Label { id: detailsLabel } } }}// [WriteFile Name=ShowViewshedCalculatedFromGeoprocessingTask, Category=Analysis]// [Legal]// Copyright 2015 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]
// sample headers#include "ShowViewshedCalculatedFromGeoprocessingTask.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QCommandLineParser>#include <QDir>#include <QGuiApplication>#include <QQmlEngine>#include <QQuickView>
// Platform specific headers#ifdef Q_OS_WIN#include <Windows.h>#endif
#define STRINGIZE(x) #x#define QUOTE(x) STRINGIZE(x)
int main(int argc, char* argv[]){ QGuiApplication app(argc, argv); app.setApplicationName(QString("ShowViewshedCalculatedFromGeoprocessingTask"));
// Use of ArcGIS location services, such as basemap styles, geocoding, and routing services, // requires an access token. For more information see // https://links.esri.com/arcgis-runtime-security-auth.
// The following methods grant an access token:
// 1. User authentication: Grants a temporary access token associated with a user's ArcGIS account. // To generate a token, a user logs in to the app with an ArcGIS account that is part of an // organization in ArcGIS Online or ArcGIS Enterprise.
// 2. API key authentication: Get a long-lived access token that gives your application access to // ArcGIS location services. Go to the tutorial at https://links.esri.com/create-an-api-key. // Copy the API Key access token.
const QString accessToken = QString("");
if (accessToken.isEmpty()) { qWarning() << "Use of ArcGIS location services, such as the basemap styles service, requires" << "you to authenticate with an ArcGIS account or set the API Key property."; } else { Esri::ArcGISRuntime::ArcGISRuntimeEnvironment::setApiKey(accessToken); } // Initialize the sample ShowViewshedCalculatedFromGeoprocessingTask::init();
// Initialize application view QQuickView view; view.setResizeMode(QQuickView::SizeRootObjectToView);
// Add the import Path view.engine()->addImportPath(QDir(QCoreApplication::applicationDirPath()).filePath("qml"));
QString arcGISRuntimeImportPath = QUOTE(ARCGIS_RUNTIME_IMPORT_PATH);
#if defined(LINUX_PLATFORM_REPLACEMENT) // on some linux platforms the string 'linux' is replaced with 1 // fix the replacement paths which were created QString replaceString = QUOTE(LINUX_PLATFORM_REPLACEMENT); arcGISRuntimeImportPath = arcGISRuntimeImportPath.replace(replaceString, "linux", Qt::CaseSensitive);#endif
// Add the Runtime and Extras path view.engine()->addImportPath(arcGISRuntimeImportPath);
// Set the source view.setSource(QUrl("qrc:/Samples/Analysis/ShowViewshedCalculatedFromGeoprocessingTask/ShowViewshedCalculatedFromGeoprocessingTask.qml"));
view.show();
return app.exec();}