Show an exploratory line of sight between two moving objects.

Use case
An exploratory line of sight between GeoElements (i.e. observer and target) will not remain constant whilst one or both are on the move.
An ExploratoryGeoElementLineOfSight is therefore useful in cases where visibility between two GeoElements requires monitoring over a period of time in a partially obstructed field of view
(such as buildings in a city).
Note: This analysis is a form of “exploratory analysis”, which means the results are calculated on the current scale of the data, and the results are generated very quickly but not persisted.
How to use the sample
An exploratory line of sight will display between a point on the Empire State Building (observer) and a taxi (target). The taxi will drive around a block and the exploratory line of sight should automatically update. The taxi will be highlighted when it is visible. You can change the observer height with the slider to see how it affects the target’s visibility.
How it works
- Instantiate an
AnalysisOverlayand add it to theSceneView’s analysis overlays collection. - Instantiate a
ExploratoryGeoElementLineOfSight, passing in observer and targetGeoElements (features or graphics). Add the exploratory line of sight to the analysis overlay’s analyses collection. - To get the target visibility when it changes, react to the target visibility changing on the
ExploratoryGeoElementLineOfSightinstance.
Relevant API
- AnalysisOverlay
- ExploratoryGeoElementLineOfSight
- ExploratoryLineOfSight.TargetVisibility
Offline data
| Link | Local Location |
|---|---|
| Taxi CAD | <userhome>/ArcGIS/Runtime/Data/3D/dolmus\_3ds/dolmus.zip |
Tags
3D, exploratory line of sight, visibility, visibility analysis
Sample Code
// [WriteFile Name=ShowExploratoryLineOfSightBetweenGeoElements, Category=Analysis]// [Legal]// Copyright 2019 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 "ShowExploratoryLineOfSightBetweenGeoElements.h"
// ArcGIS Maps SDK headers#include "AnalysisListModel.h"#include "AnalysisOverlay.h"#include "AnalysisOverlayListModel.h"#include "AngularUnit.h"#include "ArcGISSceneLayer.h"#include "ArcGISTiledElevationSource.h"#include "AttributeListModel.h"#include "Camera.h"#include "ElevationSourceListModel.h"#include "Error.h"#include "ExploratoryGeoElementLineOfSight.h"#include "GeodeticDistanceResult.h"#include "GeometryEngine.h"#include "Graphic.h"#include "GraphicListModel.h"#include "GraphicsOverlay.h"#include "GraphicsOverlayListModel.h"#include "LayerListModel.h"#include "LayerSceneProperties.h"#include "LinearUnit.h"#include "MapTypes.h"#include "ModelSceneSymbol.h"#include "Point.h"#include "PointBuilder.h"#include "RendererSceneProperties.h"#include "Scene.h"#include "SceneQuickView.h"#include "SceneViewTypes.h"#include "SimpleMarkerSymbol.h"#include "SimpleRenderer.h"#include "SpatialReference.h"#include "Surface.h"#include "SymbolTypes.h"
// Qt headers#include <QFuture>#include <QStandardPaths>#include <QtCore/qglobal.h>
// STL headers#include <array>
using namespace Esri::ArcGISRuntime;
// helper method to get cross platform data pathnamespace{ QString defaultDataPath() { QString dataPath;
#ifdef Q_OS_IOS dataPath = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);#else dataPath = QStandardPaths::writableLocation(QStandardPaths::HomeLocation);#endif
return dataPath; }} // namespace
namespace{
// Initial fixed point to observe taxi. const Point observationPoint(-73.9853, 40.7484, 200, SpatialReference::wgs84());
// Waypoints around the block for taxi to drive. const std::array<Point, 4> waypoints = {{{-73.984513, 40.748469, 2, SpatialReference::wgs84()}, {-73.985068, 40.747786, 2, SpatialReference::wgs84()}, {-73.983452, 40.747091, 2, SpatialReference::wgs84()}, {-73.982961, 40.747762, 2, SpatialReference::wgs84()}}};} // namespace
ShowExploratoryLineOfSightBetweenGeoElements::ShowExploratoryLineOfSightBetweenGeoElements(QObject* parent /* = nullptr */) : QObject(parent), m_scene(new Scene(BasemapStyle::ArcGISImageryStandard, this)){ // create a new elevation source from Terrain3D rest service ArcGISTiledElevationSource* elevationSource = new ArcGISTiledElevationSource(QUrl("https://elevation3d.arcgis.com/arcgis/rest/services/WorldElevation3D/Terrain3D/ImageServer"), this);
// add the elevation source to the scene to display elevation m_scene->baseSurface()->elevationSources()->append(elevationSource);
// Load up the buildings that will block the line of sight. ArcGISSceneLayer* buildings = new ArcGISSceneLayer( QUrl("https://tiles.arcgis.com/tiles/z2tnIkrLQ2BRzr6P/arcgis/rest/services/New_York_LoD2_3D_Buildings/SceneServer/layers/0"), this); m_scene->operationalLayers()->append(buildings);
// Trigger animation of taxi every 100ms. m_animation.setInterval(100); m_animation.callOnTimeout(this, &ShowExploratoryLineOfSightBetweenGeoElements::animate);}
ShowExploratoryLineOfSightBetweenGeoElements::~ShowExploratoryLineOfSightBetweenGeoElements() = default;
void ShowExploratoryLineOfSightBetweenGeoElements::init(){ // Register classes for QML qmlRegisterType<SceneQuickView>("Esri.Samples", 1, 0, "SceneView"); qmlRegisterType<ShowExploratoryLineOfSightBetweenGeoElements>("Esri.Samples", 1, 0, "ShowExploratoryLineOfSightBetweenGeoElementsSample");}
double ShowExploratoryLineOfSightBetweenGeoElements::heightZ() const{ if (m_observer) { const Point p = geometry_cast<Point>(m_observer->geometry()); return p.isValid() ? p.z() : 0.0; } else { return 0.0; }}
void ShowExploratoryLineOfSightBetweenGeoElements::setHeightZ(double z){ if (!m_observer) { return; }
const Point p = geometry_cast<Point>(m_observer->geometry()); if (p.isValid()) { PointBuilder builder(p); builder.setZ(z); m_observer->setGeometry(builder.toGeometry()); emit heightZChanged(); }}
SceneQuickView* ShowExploratoryLineOfSightBetweenGeoElements::sceneView() const{ return m_sceneView;}
// Set the view (created in QML)void ShowExploratoryLineOfSightBetweenGeoElements::setSceneView(SceneQuickView* sceneView){ if (!sceneView || sceneView == m_sceneView) { return; }
m_sceneView = sceneView; m_sceneView->setArcGISScene(m_scene); emit sceneViewChanged();
if (!defaultDataPath().isEmpty() && m_sceneView) { initialize(); }}
void ShowExploratoryLineOfSightBetweenGeoElements::initialize(){ // Setup the graphics overlay - ensure that all z-position are relative to the height of the surface. GraphicsOverlay* graphicsOverlay = new GraphicsOverlay(this); { LayerSceneProperties properties = graphicsOverlay->sceneProperties(); properties.setSurfacePlacement(SurfacePlacement::Relative); graphicsOverlay->setSceneProperties(properties); } m_sceneView->graphicsOverlays()->append(graphicsOverlay);
// Set up the renderer so that we can orient the taxi using the `HEADING` attribute. SimpleRenderer* renderer3D = new SimpleRenderer(this); { RendererSceneProperties properties = renderer3D->sceneProperties(); properties.setHeadingExpression("[HEADING]"); renderer3D->setSceneProperties(properties); } graphicsOverlay->setRenderer(renderer3D);
// Create our observation point as a red sphere. m_observer = new Graphic(observationPoint, new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Circle, Qt::red, 5.f, this), this); emit heightZChanged(); // We now have a point to extract the Z-height from. graphicsOverlay->graphics()->append(m_observer);
// Get our Taxi model. We will attempt to load it and continue our setup after it has loaded. const QUrl modelPath = defaultDataPath() + "/ArcGIS/Runtime/Data/3D/dolmus_3ds/dolmus.3ds"; ModelSceneSymbol* taxiSymbol = new ModelSceneSymbol(modelPath, 1.0, this); taxiSymbol->setAnchorPosition(SceneSymbolAnchorPosition::Bottom);
connect(taxiSymbol, &ModelSceneSymbol::doneLoading, this, [this, graphicsOverlay, taxiSymbol](const Error& error) { if (!error.isEmpty()) { return; }
// Create taxi from loaded taxi symbol, with an initial "HEADING" attribute for orientation. m_taxi = new Graphic(waypoints.front(), taxiSymbol, this); m_taxi->attributes()->insertAttribute("HEADING", 0.0); graphicsOverlay->graphics()->append(m_taxi);
// Set up our line of sight analysis. AnalysisOverlay* analysisOverlay = new AnalysisOverlay(this); m_sceneView->analysisOverlays()->append(analysisOverlay);
ExploratoryGeoElementLineOfSight* lineOfSight = new ExploratoryGeoElementLineOfSight(m_observer, m_taxi, this); analysisOverlay->analyses()->append(lineOfSight);
connect(lineOfSight, &ExploratoryGeoElementLineOfSight::targetVisibilityChanged, this, [this](ExploratoryLineOfSightTargetVisibility targetVisibility) { // Select taxi whenever there is a clear line of sight from observer position. m_taxi->setSelected(targetVisibility == ExploratoryLineOfSightTargetVisibility::Visible); });
Camera camera(observationPoint, 700, -30, 45, 0); m_sceneView->setViewpointCameraAsync(camera, 0);
m_animation.start(); });
taxiSymbol->load();}
void ShowExploratoryLineOfSightBetweenGeoElements::animate(){ // Goal point to travel to Point waypoint = waypoints.at(m_waypointIndex);
// Calculate azimuth between current location and goal location for taxi. Point location = geometry_cast<Point>(m_taxi->geometry());
if (!location.isValid()) { return; }
GeodeticDistanceResult distance = GeometryEngine::distanceGeodetic(location, waypoint, LinearUnit::meters(), AngularUnit::degrees(), GeodeticCurveType::Geodesic);
// Move taxi one metre along the line between its current position and the goal location. QList<Point> newPoints = GeometryEngine::moveGeodetic({location}, 1.0, LinearUnit::meters(), distance.azimuth1(), AngularUnit::degrees(), GeodeticCurveType::Geodesic); if (newPoints.size() > 0) { location = newPoints.last(); }
// Update taxi position and orientation. m_taxi->setGeometry(geometry_cast<Geometry>(location)); m_taxi->attributes()->replaceAttribute("HEADING", distance.azimuth1());
// When taxi is close enough to the waypoint then increment the index for a new goal next animation step. // Index is cyclic and an element of [0, 3]. if (distance.distance() <= 2) { m_waypointIndex = (m_waypointIndex + 1) % waypoints.size(); }}// [WriteFile Name=ShowExploratoryLineOfSightBetweenGeoElements, Category=Analysis]// [Legal]// Copyright 2019 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 ShowExploratoryLineOfSightBetweenGeoElements_H#define ShowExploratoryLineOfSightBetweenGeoElements_H
// Qt headers#include <QObject>#include <QTimer>
namespace Esri::ArcGISRuntime{ class Scene; class SceneQuickView; class Graphic;} // namespace Esri::ArcGISRuntime
Q_MOC_INCLUDE("SceneQuickView.h")
class ShowExploratoryLineOfSightBetweenGeoElements : public QObject{ Q_OBJECT
Q_PROPERTY(double heightZ READ heightZ WRITE setHeightZ NOTIFY heightZChanged) Q_PROPERTY(Esri::ArcGISRuntime::SceneQuickView* sceneView READ sceneView WRITE setSceneView NOTIFY sceneViewChanged)
public: explicit ShowExploratoryLineOfSightBetweenGeoElements(QObject* parent = nullptr); ~ShowExploratoryLineOfSightBetweenGeoElements() override;
static void init();
signals: void heightZChanged(); void sceneViewChanged();
private slots: void animate();
private: double heightZ() const; void setHeightZ(double z);
Esri::ArcGISRuntime::SceneQuickView* sceneView() const; void setSceneView(Esri::ArcGISRuntime::SceneQuickView* sceneView); void initialize();
Esri::ArcGISRuntime::Scene* m_scene = nullptr; Esri::ArcGISRuntime::SceneQuickView* m_sceneView = nullptr;
QTimer m_animation; std::size_t m_waypointIndex = 0; Esri::ArcGISRuntime::Graphic* m_taxi = nullptr; Esri::ArcGISRuntime::Graphic* m_observer = nullptr;};
#endif // ShowExploratoryLineOfSightBetweenGeoElements_H// [WriteFile Name=ShowExploratoryLineOfSightBetweenGeoElements, Category=Analysis]// [Legal]// Copyright 2019 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 Esri.Samples
Item { SceneView { id: view anchors.fill: parent
Component.onCompleted: { // Set the focus on SceneView to initially enable keyboard navigation forceActiveFocus(); }
Rectangle { anchors { margins: 5 right: parent.right verticalCenter: parent.verticalCenter } color: palette.base radius: 10 width: 80 height: parent.height * 0.5
Column { anchors { fill: parent margins: 10 } spacing: 10
Label { text: "Height: " + Math.round(heightSlider.value) + " m" anchors.horizontalCenter: parent.horizontalCenter font.pixelSize: 14 width: parent.width horizontalAlignment: Text.AlignHCenter wrapMode: Text.WordWrap }
Slider { id: heightSlider anchors.horizontalCenter: parent.horizontalCenter height: parent.height - parent.spacing - 40 width: 40 from: 150 to: 300 stepSize: 10 value: model.heightZ orientation: Qt.Vertical onMoved: { model.heightZ = value; } } } } }
// Declare the C++ instance which creates the scene etc. and supply the view ShowExploratoryLineOfSightBetweenGeoElementsSample { id: model sceneView: view }}// [WriteFile Name=ShowExploratoryLineOfSightBetweenGeoelements, Category=Analysis]// [Legal]// Copyright 2018 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 "ShowExploratoryLineOfSightBetweenGeoElements.h"
// ArcGIS Maps SDK headers#include "ArcGISRuntimeEnvironment.h"
// Qt headers#include <QDir>#include <QGuiApplication>#include <QQmlApplicationEngine>#include <QSurfaceFormat>
// 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[]){#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) // Linux requires 3.2 OpenGL Context // in order to instance 3D symbols QSurfaceFormat fmt = QSurfaceFormat::defaultFormat(); fmt.setVersion(3, 2); QSurfaceFormat::setDefaultFormat(fmt);#endif
QGuiApplication app(argc, argv); app.setApplicationName(QString("ShowExploratoryLineOfSightBetweenGeoElements"));
// 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 ShowExploratoryLineOfSightBetweenGeoElements::init();
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
// Initialize application view QQmlApplicationEngine engine; // Add the import Path engine.addImportPath(QDir(QCoreApplication::applicationDirPath()).filePath("qml")); // Add the Runtime and Extras path engine.addImportPath(arcGISRuntimeImportPath);
// Set the source engine.load(QUrl("qrc:/Samples/Analysis/ShowExploratoryLineOfSightBetweenGeoElements/main.qml"));
return app.exec();}// Copyright 2019 ESRI//// All rights reserved under the copyright laws of the United States// and applicable international laws, treaties, and conventions.//// You may freely redistribute and use this sample code, with or// without modification, provided you include the original copyright// notice and use restrictions.//// See the Sample code usage restrictions document for further information.//
import QtQuick.Controlsimport Esri.Samples
ApplicationWindow { visible: true width: 800 height: 600
ShowExploratoryLineOfSightBetweenGeoElements { anchors.fill: parent }}