This sample demonstrates how to calculate a Viewshed from a GeoElement.
Use case
A viewshed analysis is a type of visual analysis you can perform on a scene. The viewshed aims to answer the question 'What can I see from a given location?'. The output is an overlay with two different colors - one representing the visible areas (green) and the other representing the obstructed areas (red).
How to use the sample
Tap to set a destination for the vehicle (a GeoElement). The vehicle will 'drive' towards the tapped location. The viewshed analysis will update as the vehicle moves.
How it works
- Create and show the scene, with an elevation source and a buildings layer.
- Add a model (the
GeoElement
) to represent the observer (in this case, a tank).- Use a
SimpleRenderer
which has a heading expression set in theGraphicsOverlay
. This way you can relate the viewshed's heading to theGeoElement
object's heading.
- Use a
- Create a
GeoElementViewshed
with configuration for the viewshed analysis. - Add the viewshed to an
AnalysisOverlay
and add the overlay to the scene. - Configure the SceneView
CameraController
to orbit the vehicle.
Relevant API
- AnalysisOverlay
- GeodeticDistanceResult
- GeoElementViewshed
- GeometryEngine.distanceGeodetic (used to animate the vehicle)
- ModelSceneSymbol
- OrbitGeoElementCameraController
Offline data
Offline sample data will be downloaded by the sample viewer automatically.
Link | Local Location |
---|---|
Model Marker Symbol Data | <userhome> /ArcGIS/Runtime/Data/3D/bradley_low_3ds/bradle.3ds |
About the data
This sample shows a Johannesburg, South Africa Scene from ArcGIS Online. The sample uses a Tank model scene symbol hosted as an item on ArcGIS Online.
Tags
3D, analysis, buildings, model, scene, viewshed, visibility analysis
Sample Code
// [WriteFile Name=ViewshedGeoElement, Category=Analysis]
// [Legal]
// Copyright 2017 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 QtQuick 2.6
import Esri.ArcGISRuntime 100.15
import Esri.ArcGISExtras 1.1
Rectangle {
id: rootRectangle
clip: true
width: 800
height: 600
readonly property string dataPath: System.userHomePath + "/ArcGIS/Runtime/Data"
readonly property string headingAttr: "HEADING"
readonly property var linearUnit: Enums.LinearUnitIdMeters
readonly property var angularUnit: Enums.AngularUnitIdDegrees
readonly property var geodeticCurveType: Enums.GeodeticCurveTypeGeodesic
property Point waypoint: null
SceneView {
id: sceneView
anchors.fill: parent
Component.onCompleted: {
// Set the focus on SceneView to initially enable keyboard navigation
forceActiveFocus();
}
Scene {
id: scene
Basemap {
initStyle: Enums.BasemapStyleArcGISImageryStandard
}
// Set the Scene's Surface
Surface {
ArcGISTiledElevationSource {
url: "https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"
}
}
// Add a Scene Layer
ArcGISSceneLayer {
url: "https://tiles.arcgis.com/tiles/P3ePLMYs2RVChkJx/arcgis/rest/services/Buildings_Brest/SceneServer/layers/0"
}
onLoadStatusChanged: {
if (loadStatus !== Enums.LoadStatusLoaded)
return;
// Set the Camera Controller
const controller = ArcGISRuntimeEnvironment.createObject("OrbitGeoElementCameraController", {
targetGeoElement: tank, cameraDistance: 200,
cameraPitchOffset: 45
});
sceneView.cameraController = controller;
}
}
// Declare a GraphicsOverlay
GraphicsOverlay {
SimpleRenderer {
// set up heading expression for the tank
RendererSceneProperties {
headingExpression: "[%1]".arg(headingAttr)
}
}
LayerSceneProperties {
surfacePlacement: Enums.SurfacePlacementRelative
}
// Create the Tank Graphic
Graphic {
id: tank
Point {
x: -4.508708007847015
y: 48.38823243446344
z: 0
spatialReference: Factory.SpatialReference.createWgs84()
}
ModelSceneSymbol {
id: sceneSymbol
url: dataPath + "/3D/bradley_low_3ds/bradle.3ds"
anchorPosition: Enums.SceneSymbolAnchorPositionBottom
scale: 10
heading: 90.0
}
Component.onCompleted: {
tank.attributes.insertAttribute(headingAttr, 150.0)
}
}
}
// Declare an Analysis Overlay
AnalysisOverlay {
id: analysisOverlay
// Create the Location Viewshed
GeoElementViewshed {
id: geoelementViewshed
horizontalAngle: 90.0
verticalAngle: 25.0
minDistance: 5.0
maxDistance: 250.0
headingOffset: 0
pitchOffset: 0
offsetY: 0.5
offsetZ: 0.5
geoElement: tank
}
}
onMouseClicked: {
waypoint = sceneView.screenToBaseSurface(mouse.x, mouse.y);
timer.start();
}
}
function animate() {
if (waypoint === null)
return;
// get current location and distance from waypoint
let location = tank.geometry;
const distance = GeometryEngine.distanceGeodetic(location, waypoint, linearUnit, angularUnit, geodeticCurveType);
// move toward waypoint based on speed and update orientation
location = GeometryEngine.moveGeodetic([location], 1.0, linearUnit, distance.azimuth1, angularUnit, geodeticCurveType);
tank.geometry = location[0];
// update the heading
const heading = tank.attributes.attributeValue(headingAttr);
tank.attributes.replaceAttribute(headingAttr, heading + (distance.azimuth1 - heading) / 10);
// reached waypoint
if (distance.distance <= 5) {
waypoint = null;
timer.stop();
}
}
Timer {
id: timer
interval: 100
repeat: true
onTriggered: animate()
}
}