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
The following are required for this tutorial:
- An ArcGIS account to access your API keys. If you don't have an account, sign up for free.
- 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 project, set your API key.
An API Key enables access to services, web maps, and web scenes hosted in ArcGIS Online.
-
Go to your developer dashboard to get your API key. For these tutorials, use your default API key. It is scoped to include all of the services demonstrated in the tutorials.
- In Xcode, in the Project Navigator, click AppDelegate.swift.
- In the editor, set the
APIKey
property on theAGSArcGISRuntime
with your API key.Environment
AppDelegate.swiftUse dark colors for code blocks func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Note: it is not best practice to store API keys in source code. // The API key is referenced here for the convenience of this tutorial. AGSArcGISRuntimeEnvironment.apiKey = "YOUR_API_KEY" 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() AGSScene
for the web scene. To do this, create a portal item providing the web scene's item ID and anAGSPortal
referencing ArcGIS Online.ViewController.swiftUse dark colors for code blocks Change 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 aUI() "Clear"
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.ViewController.swiftUse dark colors for code blocks Add 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 theUIBar
API.Button Item ViewController.swiftUse dark colors for code blocks Add 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
, callD i d Load() setup
.UI() ViewController.swiftUse dark colors for code blocks Add 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
AGSGraphics
namedOverlay 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 Add line. private let graphicsOverlay = AGSGraphicsOverlay()
-
Create a private
AGSGraphic
namedobserver
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 Add 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 Add 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 Add 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 Add 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 methodD i d Load() setup
.Graphics Overlay() ViewController.swiftUse dark colors for code blocks Add 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 AGSGeo
to perform and display a line of sight analysis using 3D geoelements to define observer and target locations.
-
Create a private
AGSAnalysis
namedOverlay 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 Add 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 Add line. Add line. Add line. private let analysisOverlay = AGSAnalysisOverlay() private func setupAnalysisOverlay() { sceneView.analysisOverlays.add(analysisOverlay) }
-
In
view
, call the methodD i d Load() setup
.Analysis Overlay() ViewController.swiftUse dark colors for code blocks Add 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: ) AGSGeo
types. This method creates aElement AGSGeo
analysis and adds it to the scene view analysis overlay.Element Line Of Sight A
AGSGeo
is used to perform and display a line of sight analysis between twoElement Line Of Sight AGSGeo
types, like a feature or graphic. Alternatively, you can perform and display a line of sight analysis between two locations (3D points) using aElement AGSLocation
.Line Of Sight ViewController.swiftUse dark colors for code blocks Add 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 Add 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 Add 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 Add 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 Add 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 AGSGeo
protocol and include the four long-press and single tap geoview touch delegate methods.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 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) { } 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 Add 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 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) { clearObserver() } }
-
In the
setup
method, assignScene() View
toController scene
.View.touch Delegate
This step connects the scene
user touch interactions with View
via the AGSGeo
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: