Learn how to perform and display a line of sight analysis in a 3D scene.
A line of sight analysis is a type of visibility analysis that calculates visibility (visible or obstructed) between a designated observer and target. It calculates whether the target is visible to the observer based on the environment and renders a line indicating visibility on the map.
In this tutorial, you will perform and display a line of sight analysis in a web scene. Your line of sight analysis will show which targets (hot spots) are visible, based on the terrain, from specified observation points in the Yosemite Valley.
Prerequisites
Before starting this tutorial:
-
You need an ArcGIS Location Platform or ArcGIS Online account.
-
Your system meets the system requirements.
Steps
Open the Xcode project
-
To start this tutorial, first complete the Display a web scene tutorial or download and unzip the solution.
-
Open the
.xcodeproj
file in Xcode. -
If you downloaded the solution, get an access token and set the API key.
An API Key gives your app access to secure resources used in this tutorial.
-
Go to the Create an API key tutorial to obtain a new API key access token using your ArcGIS Location Platform or ArcGIS Online account. Ensure that the following privilege is enabled: Location services > Basemaps > Basemap styles service. Copy the access token as it will be used in the next step.
-
In Xcode, in the Project Navigator, click AppDelegate.swift.
-
In the editor, set the
API
property on theKey AGS
with your access token.ArcGIS Runtime Environment AppDelegate.swiftUse dark colors for code blocks func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { AGSArcGISRuntimeEnvironment.apiKey = "YOUR_ACCESS_TOKEN" return true }
-
Get the web scene item ID
You can use ArcGIS tools to create and view web scenes. Use the Scene Viewer to identify the web scene item ID. This item ID will be used later in the tutorial.
- Go to the Yosemite Valley Hotspots web scene in the Scene Viewer in ArcGIS Online. This web scene displays terrain and hotspots in the Yosemite Valley.
- Make a note of the item ID at the end of the browser's URL. The item ID should be 7558ee942b2547019f66885c44d4f0b1.
Update the scene
-
In Xcode, in the Project Navigator, click ViewController.swift.
-
In the editor, modify the
setup
method to create anScene() AGS
for the web scene. To do this, create a portal item providing the web scene's item ID and anScene AGS
referencing ArcGIS Online.Portal ViewController.swiftUse dark colors for code blocks 28 29 30 31 33 34 35 36 37 38Change line private func setupScene() { let scene: AGSScene = { let portal = AGSPortal.arcGISOnline(withLoginRequired: false) let item = AGSPortalItem(portal: portal, itemID: "7558ee942b2547019f66885c44d4f0b1") return AGSScene(item: item) }() sceneView.scene = scene }
Add a UI to control the line of sight analyses
To control the line of sight analyses, some UI is required.
-
Define a private method named
setup
. Create aU I() "
button and assign it to the navigation bar. The clear button will remove the line of sight analyses from the scene. You set an action on the clear button using a selector for a method that will be added in a later step.Clear" ViewController.swiftUse dark colors for code blocks 46Add line. Add line. Add line. Add line. private func setupUI() { let clear = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(clearScene)) navigationItem.rightBarButtonItem = clear }
-
Define a method named
clear
and assign it theScene( _:) @objc
keyword. This method removes the line of sight analyses from the scene view. The@objc
method keyword exposes the method to Objective-C, a necessary step for using theUI
API.Bar Button Item ViewController.swiftUse dark colors for code blocks 42 43 44 45 46Add line. Add line. Add line. Add line. private func setupUI() { let clear = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(clearScene)) navigationItem.rightBarButtonItem = clear } @objc func clearScene(_ sender: UIBarButtonItem) { clearAnalyses() }
-
In
view
, callDid Load() setup
.U I() ViewController.swiftUse dark colors for code blocks 20 21 22 23 25 26Add line. override func viewDidLoad() { super.viewDidLoad() setupScene() setupUI() }
-
In Xcode, in the Project Navigator, click Main.storyboard.
-
In the editor, select
View
. In the menu bar, click Editor > Embed In > Navigation Controller.Controller Embedding
View
within a Navigation Controller places a navigation bar at the top ofController View
. The navigation bar contains the UI elements.Controller -
To verify that your UI is set up properly, press <Command+R> to run the app. You should see a "Clear" button contained by the navigation bar and the app should load the Yosemite Valley Hotspots web scene.
Add observer and target graphics
Your app will use graphics to depict the locations of the analyses observer and targets.
-
In Xcode, in the Project navigator, click ViewController.swift.
-
Create a private
AGS
namedGraphics Overlay graphics
to display the observer and targets graphics.Overlay A graphics overlay is a container for graphics. It is used with a scene view to display graphics on a scene. You can add more than one graphics overlay to a scene view. Graphics overlays are displayed on top of all the other layers.
ViewController.swiftUse dark colors for code blocks 57Add line. private let graphicsOverlay = AGSGraphicsOverlay()
-
Create a private
AGS
namedGraphic observer
with a black fill color and white outline. This graphic is used to depict the location of the line of sight analyses observer and is initialized with aGraphic nil
geometry because the observer's location is not yet determined.A graphic with
nil
geometry will not be rendered in the scene view, even if it has been added to a graphics overlay.ViewController.swiftUse dark colors for code blocks 56 57Add line. Add line. Add line. Add line. Add line. private let graphicsOverlay = AGSGraphicsOverlay() private let observerGraphic: AGSGraphic = { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .black, size: 16) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .white, width: 2) return AGSGraphic(geometry: nil, symbol: symbol) }()
-
Create a private method named
add
that accepts an AGSPoint as a parameter. This method is used to create graphics with a white fill color and black outline to depict the locations of line of sight analyses targets.Target Graphic(point :) ViewController.swiftUse dark colors for code blocks 58 59 60 61 62 63Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. private let observerGraphic: AGSGraphic = { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .black, size: 16) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .white, width: 2) return AGSGraphic(geometry: nil, symbol: symbol) }() @discardableResult private func addTargetGraphic(point: AGSPoint) -> AGSGraphic { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .white, size: 16) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .black, width: 2) let targetGraphic = AGSGraphic(geometry: point, symbol: symbol, attributes: nil) graphicsOverlay.graphics.add(targetGraphic) return targetGraphic }
-
Create a private computed variable named
target
to filter and return all graphics contained byGraphics graphics
that depict target locations. The graphics overlay contains oneOverlay observer
and zero or several target graphics so any graphic that is not the observer is a target.Graphic ViewController.swiftUse dark colors for code blocks 64 65 66 67 68 69 70 71 72Add line. Add line. Add line. @discardableResult private func addTargetGraphic(point: AGSPoint) -> AGSGraphic { let symbol = AGSSimpleMarkerSymbol(style: .circle, color: .white, size: 16) symbol.outline = AGSSimpleLineSymbol(style: .solid, color: .black, width: 2) let targetGraphic = AGSGraphic(geometry: point, symbol: symbol, attributes: nil) graphicsOverlay.graphics.add(targetGraphic) return targetGraphic } private var targetGraphics: [AGSGraphic] { graphicsOverlay.graphics.filter { ($0 as! AGSGraphic) != observerGraphic } as! [AGSGraphic] }
-
Define a private method named
setup
to specify variousGraphics Overlay() scene
of theProperties graphics
and add it to the scene view. Scene properties of the graphics overlay allow you to control 3D-specific rendering of graphics.Overlay - Specifying the
surface
asPlacement absolute
treats graphics geometry z-values as absolute altitude values. If the geometry does not contain a z-value or has a value of0
, the graphic will be rendered at surface level. Increasing the z-value renders the graphic at a higher altitude. - Specifying the
altitude
increases the altitude by adding the specified value to the z-value altitude.Offset
ViewController.swiftUse dark colors for code blocks 73 74 75 76Add line. Add line. Add line. Add line. Add line. Add line. private var targetGraphics: [AGSGraphic] { graphicsOverlay.graphics.filter { ($0 as! AGSGraphic) != observerGraphic } as! [AGSGraphic] } private func setupGraphicsOverlay() { graphicsOverlay.graphics.add(observerGraphic) graphicsOverlay.sceneProperties?.surfacePlacement = .absolute graphicsOverlay.sceneProperties?.altitudeOffset = 5 sceneView.graphicsOverlays.add(graphicsOverlay) }
- Specifying the
-
In
view
, call the methodDid Load() setup
.Graphics Overlay() ViewController.swiftUse dark colors for code blocks 20 21 22 23 24 25 27 28Add line. override func viewDidLoad() { super.viewDidLoad() setupScene() setupUI() setupGraphicsOverlay() }
Create line of sight analyses
Visual analyses help you make sense of complex 3D data contained by a scene. Use an AGS
to perform and display a line of sight analysis using 3D geoelements to define observer and target locations.
-
Create a private
AGS
namedAnalysis Overlay analysis
to contain and display the line of sight analyses.Overlay An analysis overlay is a container for analyses. It is used with a scene view to display visual analyses on a scene. You can add more than one analysis overlay to a scene view. Analysis overlays are displayed on top of all other layers and graphics overlays.
ViewController.swiftUse dark colors for code blocks 89Add line. private let analysisOverlay = AGSAnalysisOverlay()
-
Define a private method named
setup
to add the analysis overlay to the scene view.Analysis Overlay() ViewController.swiftUse dark colors for code blocks 88 89Add line. Add line. Add line. private let analysisOverlay = AGSAnalysisOverlay() private func setupAnalysisOverlay() { sceneView.analysisOverlays.add(analysisOverlay) }
-
In
view
, call the methodDid Load() setup
.Analysis Overlay() ViewController.swiftUse dark colors for code blocks 20 21 22 23 24 25 26 27 29 30Add line. override func viewDidLoad() { super.viewDidLoad() setupScene() setupUI() setupGraphicsOverlay() setupAnalysisOverlay() }
-
Define a private method named
create
that accepts an observer and a target as parameters, both areLine Of Sight Analysis(observer :target :) AGS
types. This method creates aGeo Element AGS
analysis and adds it to the scene view analysis overlay.Geo Element Line Of Sight A
AGS
is used to perform and display a line of sight analysis between twoGeo Element Line Of Sight AGS
types, like a feature or graphic. Alternatively, you can perform and display a line of sight analysis between two locations (3D points) using aGeo Element AGS
.Location Line Of Sight ViewController.swiftUse dark colors for code blocks 92 93 94 95Add line. Add line. Add line. Add line. private func setupAnalysisOverlay() { sceneView.analysisOverlays.add(analysisOverlay) } private func createLineOfSightAnalysis(observer: AGSGeoElement, target: AGSGeoElement) { let line = AGSGeoElementLineOfSight(observerGeoElement: observer, targetGeoElement: target) analysisOverlay.analyses.add(line) }
-
Define a private method named
clear
to remove all line of sight analyses from the scene view. This method also removes observer and target graphics.Analyses() ViewController.swiftUse dark colors for code blocks 96 97 98 99 100Add line. Add line. Add line. Add line. Add line. private func createLineOfSightAnalysis(observer: AGSGeoElement, target: AGSGeoElement) { let line = AGSGeoElementLineOfSight(observerGeoElement: observer, targetGeoElement: target) analysisOverlay.analyses.add(line) } private func clearAnalyses() { analysisOverlay.analyses.removeAllObjects() observerGraphic.geometry = nil graphicsOverlay.graphics.removeObjects(in: targetGraphics) }
-
Define a private method named
set
that accepts an AGSPoint as a parameter. This method is used to specify the location ofObserver(point :) observer
and create line of sight analyses for targets, if they exist.Graphic ViewController.swiftUse dark colors for code blocks 101 102 103 104 105 106Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. private func clearAnalyses() { analysisOverlay.analyses.removeAllObjects() observerGraphic.geometry = nil graphicsOverlay.graphics.removeObjects(in: targetGraphics) } private func setObserver(point: AGSPoint) { let shouldCreateAnalyses = observerGraphic.geometry == nil observerGraphic.geometry = point if shouldCreateAnalyses { targetGraphics.forEach { targetGraphic in createLineOfSightAnalysis(observer: observerGraphic, target: targetGraphic) } } }
-
Define a private method named
clear
to remove the observer.Observer() ViewController.swiftUse dark colors for code blocks 107 108 109 110 111 112 113 114 115 116Add line. Add line. Add line. Add line. private func setObserver(point: AGSPoint) { let shouldCreateAnalyses = observerGraphic.geometry == nil observerGraphic.geometry = point if shouldCreateAnalyses { targetGraphics.forEach { targetGraphic in createLineOfSightAnalysis(observer: observerGraphic, target: targetGraphic) } } } private func clearObserver() { analysisOverlay.analyses.removeAllObjects() observerGraphic.geometry = nil }
-
Define a private method named
add
that accepts an AGSPoint as a parameter. This method is used to create a new target and create a line of sight analysis between the observer and the new target.Target(point :) ViewController.swiftUse dark colors for code blocks 117 118 119 120 121Add line. Add line. Add line. Add line. Add line. Add line. private func clearObserver() { analysisOverlay.analyses.removeAllObjects() observerGraphic.geometry = nil } private func addTarget(point: AGSPoint) { let targetGraphic = addTargetGraphic(point: point) if observerGraphic.geometry != nil { createLineOfSightAnalysis(observer: observerGraphic, target: targetGraphic) } }
Display the line of sight analyses with touch events
Touch events determine where to place the observer and targets as well as display line of sight analyses. A user taps to place targets and then long-press and drags to place the observer and display line of sight analyses.
-
Extend
View
to conform to theController AGS
protocol and include the four long-press and single tap geoview touch delegate methods.Geo View Touch Delegate ViewController.swiftUse dark colors for code blocks Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. Add line. extension ViewController: AGSGeoViewTouchDelegate { func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoView(_ geoView: AGSGeoView, didLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoView(_ geoView: AGSGeoView, didMoveLongPressToScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoView(_ geoView: AGSGeoView, didEndLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoViewDidCancelLongPress(_ geoView: AGSGeoView) { } }
-
When the user taps the geoview, call
add
.Target(point :) ViewController.swiftUse dark colors for code blocks 133 134 135 136 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157Add line. extension ViewController: AGSGeoViewTouchDelegate { func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { addTarget(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoView(_ geoView: AGSGeoView, didMoveLongPressToScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoView(_ geoView: AGSGeoView, didEndLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { } func geoViewDidCancelLongPress(_ geoView: AGSGeoView) { } }
-
When the user begins, moves, and ends a long-press touch event, call
set
. Using all three touch delegate methods together allows the user to specify and move the location of the observer by long-pressing and dragging their finger around the screen.Observer(point :) ViewController.swiftUse dark colors for code blocks 135 136 137 138 139 140 141 142 143 144 146 147 148 149 150 152 153 154 155 156 158 159 160 161 162 163 164 165Add line. Add line. Add line. extension ViewController: AGSGeoViewTouchDelegate { func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { addTarget(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { setObserver(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didMoveLongPressToScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { setObserver(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didEndLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { setObserver(point: mapPoint) } func geoViewDidCancelLongPress(_ geoView: AGSGeoView) { } }
-
When the user cancels the long-press touch event, call
clear
.Observer() This may happen, for instance, when you have the magnifier visible and attempt to take a screenshot using the home/lock button combination.
ViewController.swiftUse dark colors for code blocks 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 162 163 164 165Add line. extension ViewController: AGSGeoViewTouchDelegate { func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { addTarget(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { setObserver(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didMoveLongPressToScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { setObserver(point: mapPoint) } func geoView(_ geoView: AGSGeoView, didEndLongPressAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) { setObserver(point: mapPoint) } func geoViewDidCancelLongPress(_ geoView: AGSGeoView) { clearObserver() } }
-
In the
setup
method, assignScene() View
toController scene
.View.touch Delegate
This step connects the scene
user touch interactions with View
via the AGS
protocol.
private func setupScene() {
let scene: AGSScene = {
let portal = AGSPortal.arcGISOnline(withLoginRequired: false)
let item = AGSPortalItem(portal: portal, itemID: "7558ee942b2547019f66885c44d4f0b1")
return AGSScene(item: item)
}()
sceneView.scene = scene
sceneView.touchDelegate = self
}
Run the app
Press Command + R to run the app.
If you are using the Xcode simulator your system must meet these minimum requirements: macOS Big Sur 11.3, Xcode 13, iOS 13. If you are using a physical device, then refer to the system requirements.
You should see a scene of hotspots in the Yosemite Valley. Long-press and drag to set the location of the observer and tap to add target locations. The application performs and displays line of sight analyses between the observer and its targets.
What's next?
Learn how to use additional API features, ArcGIS location services, and ArcGIS tools in these tutorials: