Calculate a viewshed using a geoprocessing service, in this case showing what parts of a landscape are visible from points on mountainous terrain.
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
Create an AGSGeoprocessingTask object with the URL set to a geoprocessing service endpoint.
Create an AGSFeatureCollectionTable object and add a new AGSFeature object whose geometry is the viewshed's observer AGSPoint.
Make an AGSGeoprocessingParameters object passing in the observer point.
Use the geoprocessing task to create an AGSGeoprocessingJob object with the parameters.
Start the job and wait for it to complete and return an AGSGeoprocessingResult object.
Get the resulting AGSGeoprocessingFeatures object.
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
Use dark colors for code blocksCopy
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
classViewshedGeoprocessingViewController: UIViewController, AGSGeoViewTouchDelegate{
@IBOutletvar mapView: AGSMapView!
privatevar geoprocessingTask: AGSGeoprocessingTask!
privatevar geoprocessingJob: AGSGeoprocessingJob!
privatevar inputGraphicsOverlay =AGSGraphicsOverlay()
privatevar resultGraphicsOverlay =AGSGraphicsOverlay()
overridefuncviewDidLoad() {
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 overlayslet 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 viewself.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)
}
privatefuncaddGraphicForPoint(_point: AGSPoint) {
// remove existing graphicsself.inputGraphicsOverlay.graphics.removeAllObjects()
// new graphiclet graphic =AGSGraphic(geometry: point, symbol: nil, attributes: nil)
// add new graphic to the graphics overlayself.inputGraphicsOverlay.graphics.add(graphic)
}
privatefunccalculateViewshed(atpoint: AGSPoint) {
// remove previous graphicsself.resultGraphicsOverlay.graphics.removeAllObjects()
// Cancel previous jobself.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 geometrylet featureCollectionTable =AGSFeatureCollectionTable(fields: [AGSField](), geometryType: .point, spatialReference: point.spatialReference)
// create a new feature and assign the geometrylet newFeature = featureCollectionTable.createFeature()
newFeature.geometry = point
// show progress hudUIApplication.shared.showProgressHUD(message: "Adding Feature")
// add the new feature to the feature collection table featureCollectionTable.add(newFeature) { [weakself] (error: Error?) in// dismiss progress hudUIApplication.shared.hideProgressHUD()
iflet error = error {
// show errorself?.presentAlert(error: error)
} else {
self?.performGeoprocessing(featureCollectionTable)
}
}
}
privatefuncperformGeoprocessing(_featureCollectionTable: AGSFeatureCollectionTable) {
// geoprocessing parameterslet 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 statusUIApplication.shared.showProgressHUD(message: status.statusString())
}, completion: { [weakself] (result: AGSGeoprocessingResult?, error: Error?) in// dismiss progress hudUIApplication.shared.hideProgressHUD()
guardletself=selfelse {
return }
iflet error = error {
if (error asNSError).code !=NSUserCancelledError { // if not cancelledself.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 graphicsiflet 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: - AGSGeoViewTouchDelegatefuncgeoView(_geoView: AGSGeoView, didTapAtScreenPointscreenPoint: CGPoint, mapPoint: AGSPoint) {
// add a graphic in graphics overlay for the tapped pointself.addGraphicForPoint(mapPoint)
// calculate viewshedself.calculateViewshed(at: mapPoint)
}
}