Viewshed (geoprocessing)

View on GitHub
Sample viewer app

Calculate a viewshed using a geoprocessing service, in this case showing what parts of a landscape are visible from points on mountainous terrain.

Viewshed analysis

Use case

A viewshed is used to highlight what is visible from a given point. A viewshed could be created to show what a hiker might be able to see from a given point at the top of a mountain. Equally, a viewshed could also be created from a point representing the maximum height of a proposed wind turbine to see from what areas the turbine would be visible.

How to use the sample

Tap the map to see all areas visible from that point within a 15km radius. Tapping on an elevated area will highlight a larger part of the surrounding landscape. It may take a few seconds for the task to run and send back the results.

How it works

  1. Create an AGSGeoprocessingTask object with the URL set to a geoprocessing service endpoint.
  2. Create an AGSFeatureCollectionTable object and add a new AGSFeature object whose geometry is the viewshed's observer AGSPoint.
  3. Make an AGSGeoprocessingParameters object passing in the observer point.
  4. Use the geoprocessing task to create an AGSGeoprocessingJob object with the parameters.
  5. Start the job and wait for it to complete and return an AGSGeoprocessingResult object.
  6. Get the resulting AGSGeoprocessingFeatures object.
  7. Iterate through the viewshed features to use their geometry or display the geometry in a new AGSGraphic object.

Relevant API

  • AGSFeatureCollectionTable
  • AGSGeoprocessingFeatures
  • AGSGeoprocessingJob
  • AGSGeoprocessingParameters
  • AGSGeoprocessingResult
  • AGSGeoprocessingTask

Tags

geoprocessing, heat map, heatmap, viewshed

Sample Code

ViewshedGeoprocessingViewController.swift
                                                                                                                                                          
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
// 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.

import UIKit
import ArcGIS

class ViewshedGeoprocessingViewController: UIViewController, AGSGeoViewTouchDelegate {
    @IBOutlet var mapView: AGSMapView!

    private var geoprocessingTask: AGSGeoprocessingTask!
    private var geoprocessingJob: AGSGeoprocessingJob!

    private var inputGraphicsOverlay = AGSGraphicsOverlay()
    private var resultGraphicsOverlay = AGSGraphicsOverlay()

    override func viewDidLoad() {
        super.viewDidLoad()

        // add the source code button item to the right of navigation bar
        (self.navigationItem.rightBarButtonItem as! SourceCodeBarButtonItem).filenames = ["ViewshedGeoprocessingViewController"]

        self.mapView.map = AGSMap(basemapStyle: .arcGISTopographic)
        self.mapView.setViewpoint(AGSViewpoint(latitude: 45.3790902612337, longitude: 6.84905317262762, scale: 144447.638572))

        self.mapView.touchDelegate = self

        // renderer for graphics overlays
        let pointSymbol = AGSSimpleMarkerSymbol(style: .circle, color: .red, size: 10)
        let renderer = AGSSimpleRenderer(symbol: pointSymbol)
        self.inputGraphicsOverlay.renderer = renderer

        let fillColor = UIColor(red: 226 / 255.0, green: 119 / 255.0, blue: 40 / 255.0, alpha: 120 / 255.0)
        let fillSymbol = AGSSimpleFillSymbol(style: .solid, color: fillColor, outline: nil)
        self.resultGraphicsOverlay.renderer = AGSSimpleRenderer(symbol: fillSymbol)

        // add graphics overlays to the map view
        self.mapView.graphicsOverlays.addObjects(from: [self.resultGraphicsOverlay, self.inputGraphicsOverlay])

        let viewshedURL = URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/Elevation/ESRI_Elevation_World/GPServer/Viewshed")!
        self.geoprocessingTask = AGSGeoprocessingTask(url: viewshedURL)
    }

    private func addGraphicForPoint(_ point: AGSPoint) {
        // remove existing graphics
        self.inputGraphicsOverlay.graphics.removeAllObjects()

        // new graphic
        let graphic = AGSGraphic(geometry: point, symbol: nil, attributes: nil)

        // add new graphic to the graphics overlay
        self.inputGraphicsOverlay.graphics.add(graphic)
    }

    private func calculateViewshed(at point: AGSPoint) {
        // remove previous graphics
        self.resultGraphicsOverlay.graphics.removeAllObjects()

        // Cancel previous job
        self.geoprocessingJob?.progress.cancel()

        // the service requires input of rest data type GPFeatureRecordSetLayer
        // which is AGSGeoprocessingFeatures in runtime
        // in order to create an object of AGSGeoprocessingFeatures we need a featureSet
        // for which we will create a featureCollectionTable (since feature collection table
        // is a feature set) and add the geometry as a feature to that table.

        // create feature collection table for point geometry
        let featureCollectionTable = AGSFeatureCollectionTable(fields: [AGSField](), geometryType: .point, spatialReference: point.spatialReference)

        // create a new feature and assign the geometry
        let newFeature = featureCollectionTable.createFeature()
        newFeature.geometry = point

        // show progress hud
        UIApplication.shared.showProgressHUD(message: "Adding Feature")

        // add the new feature to the feature collection table
        featureCollectionTable.add(newFeature) { [weak self] (error: Error?) in
            // dismiss progress hud
            UIApplication.shared.hideProgressHUD()

            if let error = error {
                // show error
                self?.presentAlert(error: error)
            } else {
                self?.performGeoprocessing(featureCollectionTable)
            }
        }
    }

    private func performGeoprocessing(_ featureCollectionTable: AGSFeatureCollectionTable) {
        // geoprocessing parameters
        let params = AGSGeoprocessingParameters(executionType: .synchronousExecute)
        params.processSpatialReference = featureCollectionTable.spatialReference
        params.outputSpatialReference = featureCollectionTable.spatialReference

        // use the feature collection table to create the required AGSGeoprocessingFeatures input
        params.inputs["Input_Observation_Point"] = AGSGeoprocessingFeatures(featureSet: featureCollectionTable)

        // initialize job from geoprocessing task
        geoprocessingJob = geoprocessingTask.geoprocessingJob(with: params)

        // start the job
        geoprocessingJob.start(statusHandler: { (status: AGSJobStatus) in
            // show progress hud with job status
            UIApplication.shared.showProgressHUD(message: status.statusString())
        }, completion: { [weak self] (result: AGSGeoprocessingResult?, error: Error?) in
            // dismiss progress hud
            UIApplication.shared.hideProgressHUD()

            guard let self = self else {
                return
            }

            if let error = error {
                if (error as NSError).code != NSUserCancelledError { // if not cancelled
                    self.presentAlert(error: error)
                }
            } else {
                // The service returns result in form of AGSGeoprocessingFeatures
                // Cast the results and add the features from featureSet to graphics overlay
                // in form of graphics
                if let resultFeatures = result?.outputs["Viewshed_Result"] as? AGSGeoprocessingFeatures,
                    let featureSet = resultFeatures.features {
                    for feature in featureSet.featureEnumerator().allObjects {
                        let graphic = AGSGraphic(geometry: feature.geometry, symbol: nil, attributes: nil)
                        self.resultGraphicsOverlay.graphics.add(graphic)
                    }
                }
            }
        })
    }

    // MARK: - AGSGeoViewTouchDelegate

    func geoView(_ geoView: AGSGeoView, didTapAtScreenPoint screenPoint: CGPoint, mapPoint: AGSPoint) {
        // add a graphic in graphics overlay for the tapped point
        self.addGraphicForPoint(mapPoint)

        // calculate viewshed
        self.calculateViewshed(at: mapPoint)
    }
}

Your browser is no longer supported. Please upgrade your browser for the best experience. See our browser deprecation post for more details.