Perform a line of sight analysis in a map view between fixed observer and target positions.

Use case
Line of sight analysis determines whether a target can be seen from one or more observer locations based on elevation data. This can support planning workflows such as siting communication equipment, assessing observation coverage, or evaluating potential obstructions between known locations. In this sample, several predefined observer points are evaluated against a single fixed target to compare visibility outcomes side by side.
Note: This analysis is a form of “data-driven analysis”, which means the analysis is calculated at the resolution of the data rather than the resolution of the display.
How to use the sample
The sample loads with a map centered on the Isle of Arran, Scotland, and runs a line of sight analysis from multiple observer points (triangles) to a fixed target point (beacon icon) located at the highest point of the island. Solid green line segments represent visible portions of each line of sight result, and dashed gray segments represent not visible portions. Click an observer to see a callout that reports whether the target is visible and over what distance the line remains unobstructed. Use the switch to show only results where the target is visible from the observer.
How it works
- Create a
Mapand set it on aMapQuickView. - Create
GraphicsOverlayinstances and add the target and observer graphics with appropriate symbols, along with a separate results overlay for the line of sight output. - Create a
ContinuousFieldfrom a raster file containing elevation data. - Create
LineOfSightPositionobjects from the target and observerPoints usingHeightOrigin::Relative. - Configure
LineOfSightParameterswithObserverTargetPairsusing the observer and target line of sight positions. - Create a
LineOfSightFunctionfrom the continuous field and line of sight parameters. - Evaluate the function to get
LineOfSightresults. - Create
Graphicobjects from each result using the geometry ofvisibleLineornotVisibleLineand an appropriate line symbol. - Use
LineOfSight::targetVisibilityto determine whether an observer position has a direct line of sight to the target. - Get the length of the visible line result with
GeometryEngine::lengthGeodeticto report the callout result.
Relevant API
- ContinuousField
- GeometryEngine
- GraphicsOverlay
- LineOfSight
- LineOfSightFunction
- LineOfSightParameters
- LineOfSightPosition
- ObserverTargetPairs
Offline data
To set up the sample’s offline data, see the Use offline data in the samples section of the Qt Samples repository overview.
| Link | Local Location |
|---|---|
| Arran elevation raster | <userhome>/ArcGIS/Runtime/Data/raster/arran.tif |
About the data
The sample uses a 10m resolution digital terrain elevation raster of the Isle of Arran, Scotland (Raster data Copyright Scottish Government and SEPA (2014)).
Tags
analysis, elevation, line of sight, map view, spatial analysis, terrain, visibility
Sample code
// [WriteFile Name=ShowLineOfSightAnalysisInMap, Category=Analysis]// [Legal]// Copyright 2026 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
#include "ShowLineOfSightAnalysisInMap.h"
#include "AnalysisTypes.h"#include "AttributeListModel.h"#include "CalloutData.h"#include "ContinuousField.h"#include "Error.h"#include "GeometryEngine.h"#include "Graphic.h"#include "GraphicListModel.h"#include "GraphicsOverlay.h"#include "GraphicsOverlayListModel.h"#include "IdentifyGraphicsOverlayResult.h"#include "LineOfSight.h"#include "LineOfSightFunction.h"#include "LineOfSightParameters.h"#include "LineOfSightPosition.h"#include "LinearUnit.h"#include "Map.h"#include "MapQuickView.h"#include "MapTypes.h"#include "ObserverTargetPairs.h"#include "PictureMarkerSymbol.h"#include "Point.h"#include "Polyline.h"#include "SimpleLineSymbol.h"#include "SimpleMarkerSymbol.h"#include "SpatialReference.h"#include "SymbolTypes.h"#include "Viewpoint.h"
#include <QFileInfo>#include <QFuture>#include <QMouseEvent>#include <QStandardPaths>
using namespace Esri::ArcGISRuntime;
namespace{ QString defaultDataPath() {#ifdef Q_OS_IOS return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);#else return QStandardPaths::writableLocation(QStandardPaths::HomeLocation);#endif }
QList<ObserverDefinition> createObservers() { const SpatialReference webMercator = SpatialReference::webMercator(); return { {QColor(Qt::green), Point(-580893.546, 7489102.890, 5.0, webMercator)}, {QColor(Qt::white), Point(-583446.004, 7483567.462, 5.0, webMercator)}, {QColor(255, 165, 0), Point(-577665.236, 7490792.908, 5.0, webMercator)}, {QColor(Qt::yellow), Point(-576452.981, 7487071.388, 5.0, webMercator)}, {QColor(228, 168, 239), Point(-576650.067, 7481479.772, 5.0, webMercator)}, {QColor(Qt::blue), Point(-571683.896, 7492017.864, 5.0, webMercator)} }; }} // namespace
ShowLineOfSightAnalysisInMap::ShowLineOfSightAnalysisInMap(QObject* parent /* = nullptr */) : QObject(parent), m_elevationFilePath(defaultDataPath() + "/ArcGIS/Runtime/Data/raster/arran.tif"), m_map(new Map(BasemapStyle::ArcGISHillshadeDark, this)){}
ShowLineOfSightAnalysisInMap::~ShowLineOfSightAnalysisInMap() = default;
void ShowLineOfSightAnalysisInMap::init(){ // Register the map view for QML qmlRegisterType<MapQuickView>("Esri.Samples", 1, 0, "MapView"); qmlRegisterType<ShowLineOfSightAnalysisInMap>("Esri.Samples", 1, 0, "ShowLineOfSightAnalysisInMapSample");}
MapQuickView* ShowLineOfSightAnalysisInMap::mapView() const{ return m_mapView;}
// Set the view (created in QML)void ShowLineOfSightAnalysisInMap::setMapView(MapQuickView* mapView){ if (!mapView || mapView == m_mapView) { return; }
m_mapView = mapView;
connect(m_mapView, &MapQuickView::mouseClicked, this, &ShowLineOfSightAnalysisInMap::identifyObserverAt);
initialize();
emit mapViewChanged();}
void ShowLineOfSightAnalysisInMap::setVisibilityFilter(bool visibilityFilter){ if (m_visibilityFilter == visibilityFilter) { return; }
m_visibilityFilter = visibilityFilter; applyVisibilityFilter(); emit visibilityFilterChanged();}
void ShowLineOfSightAnalysisInMap::initialize(){ if (!m_mapView || m_initialized) { return; }
m_initialized = true; m_isObserverIdentifyEnabled = false;
m_mapView->setMap(m_map);
m_resultsGraphicsOverlay = new GraphicsOverlay(this); m_observersGraphicsOverlay = new GraphicsOverlay(this); m_targetGraphicsOverlay = new GraphicsOverlay(this);
m_mapView->graphicsOverlays()->append(m_resultsGraphicsOverlay); m_mapView->graphicsOverlays()->append(m_observersGraphicsOverlay); m_mapView->graphicsOverlays()->append(m_targetGraphicsOverlay);
PictureMarkerSymbol* beaconSymbol = new PictureMarkerSymbol(QUrl(QStringLiteral("qrc:/Samples/Analysis/ShowLineOfSightAnalysisInMap/iconAssets/beacon.png")), this);
beaconSymbol->setWidth(24.0); beaconSymbol->setHeight(24.0);
m_targetPoint = Point(-577955.365, 7484288.220, 5.0, SpatialReference::webMercator()); Graphic* targetGraphic = new Graphic(m_targetPoint, beaconSymbol, this); m_targetGraphicsOverlay->graphics()->append(targetGraphic);
m_observers = createObservers();
for (int i = 0; i < static_cast<int>(m_observers.size()); ++i) { const ObserverDefinition& observer = m_observers.at(i); SimpleMarkerSymbol* symbol = new SimpleMarkerSymbol(SimpleMarkerSymbolStyle::Triangle, observer.color, 15.0, this); Graphic* observerGraphic = new Graphic(observer.position, symbol, this); observerGraphic->attributes()->insertAttribute(QStringLiteral("observerIndex"), i); m_observersGraphicsOverlay->graphics()->append(observerGraphic); }
m_map->setInitialViewpoint(Viewpoint(m_targetPoint, 150000.0)); createLineOfSightAnalysis();}
void ShowLineOfSightAnalysisInMap::createLineOfSightAnalysis(){ if (!QFileInfo::exists(m_elevationFilePath)) { m_isObserverIdentifyEnabled = true; return; }
ContinuousField::createFromFilesAsync({m_elevationFilePath}, 0, this).then(this, [this](ContinuousField* elevation) { onElevationFieldCreated(elevation); });}
void ShowLineOfSightAnalysisInMap::onElevationFieldCreated(ContinuousField* elevation){ if (!elevation) { m_isObserverIdentifyEnabled = true; return; }
LineOfSightPosition* targetPosition = new LineOfSightPosition(m_targetPoint, HeightOrigin::Relative, this); QList<LineOfSightPosition*> targetPositions{targetPosition};
QList<LineOfSightPosition*> observerPositions; observerPositions.reserve(m_observers.size()); const QList<ObserverDefinition>& observers = m_observers; for (const ObserverDefinition& observer : observers) { observerPositions.append(new LineOfSightPosition(observer.position, HeightOrigin::Relative, this)); }
LineOfSightParameters* parameters = new LineOfSightParameters(this); parameters->setObserverTargetPairs(ObserverTargetPairs::create(observerPositions, targetPositions, this));
LineOfSightFunction* lineOfSightFunction = LineOfSightFunction::create(elevation, parameters, this);
lineOfSightFunction->evaluateAsync(this).then(this, [this](const QList<LineOfSight*>& results) { onLineOfSightEvaluated(results); });}
void ShowLineOfSightAnalysisInMap::onLineOfSightEvaluated(const QList<LineOfSight*>& results){ if (results.isEmpty()) { return; }
SimpleLineSymbol* visibleLineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle::Solid, Qt::green, 4.0, this); SimpleLineSymbol* notVisibleLineSymbol = new SimpleLineSymbol(SimpleLineSymbolStyle::LongDash, Qt::gray, 2.0, this);
m_lineOfSightResults.clear();
for (LineOfSight* result : results) { const float targetVisibility = result->targetVisibility(); m_lineOfSightResults.append(result);
const Polyline visibleLine = result->visibleLine(); if (!visibleLine.isEmpty()) { Graphic* visibleLineGraphic = new Graphic(visibleLine, visibleLineSymbol, this); visibleLineGraphic->attributes()->insertAttribute(QStringLiteral("targetVisibility"), targetVisibility); m_resultsGraphicsOverlay->graphics()->append(visibleLineGraphic); }
const Polyline notVisibleLine = result->notVisibleLine(); if (!notVisibleLine.isEmpty()) { Graphic* notVisibleLineGraphic = new Graphic(notVisibleLine, notVisibleLineSymbol, this); notVisibleLineGraphic->attributes()->insertAttribute(QStringLiteral("targetVisibility"), targetVisibility); m_resultsGraphicsOverlay->graphics()->append(notVisibleLineGraphic); } }
applyVisibilityFilter(); m_isObserverIdentifyEnabled = true;}
void ShowLineOfSightAnalysisInMap::identifyObserverAt(QMouseEvent& mouseEvent){ if (!m_isObserverIdentifyEnabled || !m_mapView || !m_observersGraphicsOverlay) { return; }
m_mapView->calloutData()->setVisible(false);
m_mapView->identifyGraphicsOverlayAsync(m_observersGraphicsOverlay, mouseEvent.position(), 10.0, false, this) .then(this, [this](IdentifyGraphicsOverlayResult* identifyResult) { onIdentifyObserverCompleted(identifyResult); });}
void ShowLineOfSightAnalysisInMap::onIdentifyObserverCompleted(IdentifyGraphicsOverlayResult* identifyResult){ if (!identifyResult) { return; }
const QList<Graphic*> graphics = identifyResult->graphics(); if (graphics.isEmpty()) { return; }
Graphic* observerGraphic = graphics.constFirst(); const int observerIndex = observerGraphic->attributes()->attributeValue(QStringLiteral("observerIndex")).toInt(); const QString detailText = lineOfSightDetail(m_lineOfSightResults.at(observerIndex));
CalloutData* calloutData = m_mapView->calloutData(); const Point observerPoint(observerGraphic->geometry()); calloutData->setLocation(observerPoint); calloutData->setDetail(detailText); calloutData->setGeoElement(observerGraphic); calloutData->setVisible(true);}
QString ShowLineOfSightAnalysisInMap::lineOfSightDetail(LineOfSight* result) const{ if (!result) { return QString(); }
const Error error = result->error(); if (!error.isEmpty()) { return error.additionalMessage().isEmpty() ? error.message() : error.additionalMessage(); }
const Polyline visibleLine = result->visibleLine(); const Polyline notVisibleLine = result->notVisibleLine(); if (visibleLine.isEmpty() && notVisibleLine.isEmpty()) { return QString(); }
const double visibleLength = visibleLine.isEmpty() ? 0.0 : GeometryEngine::lengthGeodetic(visibleLine, LinearUnit::meters(), GeodeticCurveType::Geodesic);
if (notVisibleLine.isEmpty()) { return QStringLiteral("Target visible from observer over %1 meters.").arg(visibleLength, 0, 'f', 1); }
return QStringLiteral("Target not visible from observer. Obstructed after %1 meters.").arg(visibleLength, 0, 'f', 1);}
void ShowLineOfSightAnalysisInMap::applyVisibilityFilter(){ if (!m_resultsGraphicsOverlay) { return; }
GraphicListModel* lineGraphics = m_resultsGraphicsOverlay->graphics(); for (Graphic* lineGraphic : *lineGraphics) { const float targetVisibility = lineGraphic->attributes()->attributeValue(QStringLiteral("targetVisibility")).toFloat(); lineGraphic->setVisible(!m_visibilityFilter || targetVisibility == 1.0); }}// [WriteFile Name=ShowLineOfSightAnalysisInMap, Category=Analysis]// [Legal]// Copyright 2026 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 SHOWLINEOFSIGHTANALYSISINMAP_H#define SHOWLINEOFSIGHTANALYSISINMAP_H
#include <QObject>#include <QColor>#include <QList>
#include "Point.h"
class QString;class QMouseEvent;
namespace Esri::ArcGISRuntime{ class ContinuousField; class GraphicsOverlay; class IdentifyGraphicsOverlayResult; class LineOfSight; class Map; class MapQuickView;} // namespace Esri::ArcGISRuntime
Q_MOC_INCLUDE("MapQuickView.h");
struct ObserverDefinition{ QColor color; Esri::ArcGISRuntime::Point position;};
class ShowLineOfSightAnalysisInMap : public QObject{ Q_OBJECT
Q_PROPERTY(Esri::ArcGISRuntime::MapQuickView* mapView READ mapView WRITE setMapView NOTIFY mapViewChanged) Q_PROPERTY(bool visibilityFilter MEMBER m_visibilityFilter WRITE setVisibilityFilter NOTIFY visibilityFilterChanged)
public: explicit ShowLineOfSightAnalysisInMap(QObject* parent = nullptr); ~ShowLineOfSightAnalysisInMap() override;
static void init();
Q_INVOKABLE void setVisibilityFilter(bool visibilityFilter);
signals: void mapViewChanged(); void visibilityFilterChanged();
private: Esri::ArcGISRuntime::MapQuickView* mapView() const; void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView);
void initialize(); void createLineOfSightAnalysis(); void onElevationFieldCreated(Esri::ArcGISRuntime::ContinuousField* elevation); void onLineOfSightEvaluated(const QList<Esri::ArcGISRuntime::LineOfSight*>& results); void onIdentifyObserverCompleted(Esri::ArcGISRuntime::IdentifyGraphicsOverlayResult* identifyResult); void applyVisibilityFilter(); void identifyObserverAt(QMouseEvent& mouseEvent); QString lineOfSightDetail(Esri::ArcGISRuntime::LineOfSight* result) const;
const QString m_elevationFilePath; QList<ObserverDefinition> m_observers; bool m_isObserverIdentifyEnabled = false; bool m_visibilityFilter = false; bool m_initialized = false;
Esri::ArcGISRuntime::Map* m_map = nullptr; Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_targetGraphicsOverlay = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_observersGraphicsOverlay = nullptr; Esri::ArcGISRuntime::GraphicsOverlay* m_resultsGraphicsOverlay = nullptr;
Esri::ArcGISRuntime::Point m_targetPoint;
QList<Esri::ArcGISRuntime::LineOfSight*> m_lineOfSightResults;};
#endif // SHOWLINEOFSIGHTANALYSISINMAP_H// [WriteFile Name=ShowLineOfSightAnalysisInMap, Category=Analysis]// [Legal]// Copyright 2026 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.Samplesimport Esri.ArcGISRuntime.Toolkit
Item { id: root width: 800 height: 600 clip: true
ShowLineOfSightAnalysisInMapSample { id: model mapView: view }
ColumnLayout { anchors.fill: parent spacing: 0
MapView { id: view objectName: "mapView" Layout.fillWidth: true Layout.fillHeight: true
Component.onCompleted: { forceActiveFocus(); }
Callout { id: callout calloutData: view.calloutData maxWidth: Math.min(root.width * 0.6, 320) implicitHeight: 120 screenOffsetY: -19 accessoryButtonVisible: false leaderPosition: Callout.LeaderPosition.Automatic }
Label { anchors.top: parent.top anchors.left: parent.left anchors.margins: 12 color: palette.text font.pixelSize: 12 font.italic: true font.bold: true text: qsTr("Raster data Copyright Scottish Government and SEPA (2014)") }
Rectangle { anchors.top: parent.top anchors.right: parent.right anchors.margins: 12 width: filterRow.implicitWidth + 16 height: filterRow.implicitHeight + 16 radius: 6 color: palette.base opacity: 0.85 border.color: palette.mid
MouseArea { anchors.fill: parent acceptedButtons: Qt.LeftButton | Qt.RightButton onClicked: mouse => mouse.accepted = true onDoubleClicked: mouse => mouse.accepted = true onWheel: wheel => wheel.accepted = true }
RowLayout { id: filterRow anchors.fill: parent anchors.margins: 8 spacing: 8
Label { color: palette.text text: qsTr("Only observers with line of sight") }
Switch { checked: model.visibilityFilter onToggled: model.setVisibilityFilter(checked) } } } }
}}