Skip to content

Commit 3787ae0

Browse files
authored
Merge branch 'v.next' into Caleb/New-FindAddressWithReverseGeocode
2 parents 606c8ee + 382c8c9 commit 3787ae0

File tree

5 files changed

+325
-0
lines changed

5 files changed

+325
-0
lines changed

Samples.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,8 @@
233233
D76EE6082AF9AFEC00DA0325 /* FindRouteAroundBarriersView.Model.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D76EE6062AF9AFE100DA0325 /* FindRouteAroundBarriersView.Model.swift */; };
234234
D7705D582AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7705D552AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift */; };
235235
D7705D5B2AFC246A00CC0335 /* FindClosestFacilityToMultiplePointsView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7705D552AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift */; };
236+
D7705D642AFC570700CC0335 /* FindClosestFacilityFromPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */; };
237+
D7705D662AFC575000CC0335 /* FindClosestFacilityFromPointView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */; };
236238
D7749AD62AF08BF50086632F /* FindRouteInTransportNetworkView.Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7749AD52AF08BF50086632F /* FindRouteInTransportNetworkView.Model.swift */; };
237239
D77570C02A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77570BF2A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift */; };
238240
D77570C12A2943D900F490CD /* AnimateImagesWithImageOverlayView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D77570BF2A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift */; };
@@ -368,6 +370,7 @@
368370
dstSubfolderSpec = 7;
369371
files = (
370372
D73FCFFA2B02A3C50006360D /* FindAddressWithReverseGeocodeView.swift in Copy Source Code Files */,
373+
D7705D662AFC575000CC0335 /* FindClosestFacilityFromPointView.swift in Copy Source Code Files */,
371374
D73FD0002B02C9610006360D /* FindRouteAroundBarriersView.Views.swift in Copy Source Code Files */,
372375
D76EE6082AF9AFEC00DA0325 /* FindRouteAroundBarriersView.Model.swift in Copy Source Code Files */,
373376
D7DDF8562AF47C86004352D9 /* FindRouteAroundBarriersView.swift in Copy Source Code Files */,
@@ -629,6 +632,7 @@
629632
D769C2112A29019B00030F61 /* SetUpLocationDrivenGeotriggersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetUpLocationDrivenGeotriggersView.swift; sourceTree = "<group>"; };
630633
D76EE6062AF9AFE100DA0325 /* FindRouteAroundBarriersView.Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRouteAroundBarriersView.Model.swift; sourceTree = "<group>"; };
631634
D7705D552AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindClosestFacilityToMultiplePointsView.swift; sourceTree = "<group>"; };
635+
D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindClosestFacilityFromPointView.swift; sourceTree = "<group>"; };
632636
D7749AD52AF08BF50086632F /* FindRouteInTransportNetworkView.Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRouteInTransportNetworkView.Model.swift; sourceTree = "<group>"; };
633637
D77570BF2A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimateImagesWithImageOverlayView.swift; sourceTree = "<group>"; };
634638
D77572AD2A295DDD00F490CD /* PacificSouthWest2 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PacificSouthWest2; sourceTree = "<group>"; };
@@ -815,6 +819,7 @@
815819
E004A6EE284E4B7A002A1FE6 /* Download vector tiles to local cache */,
816820
1C26ED122A859525009B7721 /* Filter features in scene */,
817821
D73FCFF32B02A3AA0006360D /* Find address with reverse geocode */,
822+
D7705D5F2AFC570700CC0335 /* Find closest facility from point */,
818823
D7705D542AFC244E00CC0335 /* Find closest facility to multiple points */,
819824
D78666A92A21616D00C60110 /* Find nearest vertex */,
820825
E066DD33285CF3A0004D3D5B /* Find route */,
@@ -1533,6 +1538,14 @@
15331538
path = "Find closest facility to multiple points";
15341539
sourceTree = "<group>";
15351540
};
1541+
D7705D5F2AFC570700CC0335 /* Find closest facility from point */ = {
1542+
isa = PBXGroup;
1543+
children = (
1544+
D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */,
1545+
);
1546+
path = "Find closest facility from point";
1547+
sourceTree = "<group>";
1548+
};
15361549
D77570BC2A29427200F490CD /* Animate images with image overlay */ = {
15371550
isa = PBXGroup;
15381551
children = (
@@ -2212,6 +2225,7 @@
22122225
D7058FB12ACB423C00A40F14 /* Animate3DGraphicView.Model.swift in Sources */,
22132226
0044CDDF2995C39E004618CE /* ShowDeviceLocationHistoryView.swift in Sources */,
22142227
E041ABC0287CA9F00056009B /* WebView.swift in Sources */,
2228+
D7705D642AFC570700CC0335 /* FindClosestFacilityFromPointView.swift in Sources */,
22152229
E088E1572862579D00413100 /* SetSurfacePlacementModeView.swift in Sources */,
22162230
1CAF831F2A20305F000E1E60 /* ShowUtilityAssociationsView.swift in Sources */,
22172231
00C7993B2A845AAF00AFE342 /* Sidebar.swift in Sources */,
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
// Copyright 2023 Esri
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
import ArcGIS
16+
import SwiftUI
17+
18+
struct FindClosestFacilityFromPointView: View {
19+
/// The view model for the sample.
20+
@StateObject private var model = Model()
21+
22+
/// A Boolean value indicating whether a routing operation is in progress.
23+
@State private var isRouting = false
24+
25+
/// A Boolean value indicating whether routing is currently disabled.
26+
@State private var routingIsDisabled = true
27+
28+
/// A Boolean value indicating whether the error alert is showing.
29+
@State private var errorAlertIsShowing = false
30+
31+
/// The error shown in the error alert.
32+
@State private var error: Error? {
33+
didSet { errorAlertIsShowing = error != nil }
34+
}
35+
36+
var body: some View {
37+
MapViewReader { mapViewProxy in
38+
MapView(map: model.map, graphicsOverlays: [model.graphicsOverlay])
39+
.overlay(alignment: .center) {
40+
if isRouting {
41+
ProgressView("Routing...")
42+
.padding()
43+
.background(.ultraThickMaterial)
44+
.cornerRadius(10)
45+
.shadow(radius: 50)
46+
}
47+
}
48+
.toolbar {
49+
ToolbarItemGroup(placement: .bottomBar) {
50+
Button("Solve Routes") {
51+
Task {
52+
do {
53+
isRouting = true
54+
defer { isRouting = false }
55+
56+
try await model.solveRoutes()
57+
routingIsDisabled = true
58+
} catch {
59+
self.error = error
60+
}
61+
}
62+
}
63+
.disabled(routingIsDisabled)
64+
65+
Spacer()
66+
67+
Button("Reset") {
68+
model.graphicsOverlay.removeAllGraphics()
69+
routingIsDisabled = false
70+
}
71+
.disabled(model.graphicsOverlay.graphics.isEmpty)
72+
}
73+
}
74+
.task {
75+
// Get the extents of the layers on the map.
76+
await model.map.operationalLayers.load()
77+
let layerExtents = model.map.operationalLayers.compactMap(\.fullExtent)
78+
79+
// Zoom to the extents to view the layers' features.
80+
guard let extent = GeometryEngine.combineExtents(of: layerExtents) else { return }
81+
await mapViewProxy.setViewpointGeometry(extent, padding: 30)
82+
}
83+
}
84+
.task {
85+
// Set up the closest facility parameters when the sample loads.
86+
do {
87+
try await model.configureClosestFacilityParameters()
88+
routingIsDisabled = false
89+
} catch {
90+
self.error = error
91+
}
92+
}
93+
.alert(isPresented: $errorAlertIsShowing, presentingError: error)
94+
}
95+
}
96+
97+
private extension FindClosestFacilityFromPointView {
98+
/// The view model for the sample.
99+
class Model: ObservableObject {
100+
/// A map with a streets relief basemap.
101+
let map = Map(basemapStyle: .arcGISStreetsRelief)
102+
103+
/// The graphics overlay for the route graphics.
104+
let graphicsOverlay = GraphicsOverlay()
105+
106+
/// The blue line symbol for the route graphics.
107+
private let routeSymbol = SimpleLineSymbol(
108+
style: .solid,
109+
color: UIColor(red: 0, green: 0, blue: 1, alpha: 77 / 255),
110+
width: 5
111+
)
112+
113+
/// The task for finding the closest facility.
114+
private let closestFacilityTask = ClosestFacilityTask(url: .sanDiegoNetworkAnalysis)
115+
116+
/// The parameters to be passed to the closest facility task.
117+
private var closestFacilityParameters: ClosestFacilityParameters?
118+
119+
init() {
120+
// Create the feature layers and add them to the map.
121+
addFeatureLayer(tableURL: .facilitiesLayer, imageURL: .fireStationImage)
122+
addFeatureLayer(tableURL: .incidentsLayer, imageURL: .fireImage)
123+
}
124+
125+
/// Creates the closest facility parameters and adds the facilities and incidents from the feature layers.
126+
func configureClosestFacilityParameters() async throws {
127+
// Create the default parameters from the closest facility task.
128+
async let parameters = try closestFacilityTask.makeDefaultParameters()
129+
130+
// Get the feature layers on the map.
131+
await map.operationalLayers.load()
132+
let facilitiesLayer = map.operationalLayers.first(
133+
where: { $0.name == "sandiegofacilities" }
134+
) as? FeatureLayer
135+
let incidentsLayer = map.operationalLayers.first(
136+
where: { $0.name == "sandiegoincidents" }
137+
) as? FeatureLayer
138+
139+
// Get the feature tables from the feature layers.
140+
guard let facilitiesTable = facilitiesLayer?.featureTable as? ArcGISFeatureTable,
141+
let incidentsTable = incidentsLayer?.featureTable as? ArcGISFeatureTable
142+
else { return }
143+
144+
// Create query parameters that will return all the features.
145+
let queryParameters = QueryParameters()
146+
queryParameters.whereClause = "1=1"
147+
148+
// Set the parameters' facilities and incidents using the tables.
149+
try await parameters.setFacilities(
150+
fromFeaturesIn: facilitiesTable,
151+
queryParameters: queryParameters
152+
)
153+
try await parameters.setIncidents(
154+
fromFeaturesIn: incidentsTable,
155+
queryParameters: queryParameters
156+
)
157+
closestFacilityParameters = try await parameters
158+
}
159+
160+
/// Finds the closest facility routes for the incidents.
161+
func solveRoutes() async throws {
162+
guard let closestFacilityParameters else { return }
163+
164+
// Get the closest facility result from the task using the parameters.
165+
let closestFacilityResult = try await closestFacilityTask.solveClosestFacility(
166+
using: closestFacilityParameters
167+
)
168+
169+
// Create a route graphic for each incident in the result.
170+
let incidentsIndices = closestFacilityResult.incidents.indices
171+
let routeGraphics = incidentsIndices.compactMap { incidentIndex -> Graphic? in
172+
// Get the index for the facility closest to the given incident and facility route.
173+
guard let closestFacilityIndex = closestFacilityResult.rankedIndexesOfFacilities(
174+
forIncidentAtIndex: incidentIndex
175+
).first,
176+
let closestFacilityRoute = closestFacilityResult.route(
177+
toFacilityAtIndex: closestFacilityIndex,
178+
fromIncidentAtIndex: incidentIndex
179+
) else {
180+
return nil
181+
}
182+
183+
// Create a graphic using the route's geometry.
184+
return Graphic(geometry: closestFacilityRoute.routeGeometry, symbol: routeSymbol)
185+
}
186+
187+
graphicsOverlay.addGraphics(routeGraphics)
188+
}
189+
190+
/// Creates and adds a feature layer to the map.
191+
/// - Parameters:
192+
/// - tableURL: The URL to the feature table to create the feature layer from.
193+
/// - imageURL: The URL to the image to use as the layer's renderer.
194+
private func addFeatureLayer(tableURL: URL, imageURL: URL) {
195+
// Create a layer from the feature table URL.
196+
let featureTable = ServiceFeatureTable(url: tableURL)
197+
let featureLayer = FeatureLayer(featureTable: featureTable)
198+
199+
// Create a simple renderer from the image URL and add it to the layer.
200+
let markerSymbol = PictureMarkerSymbol(url: imageURL)
201+
markerSymbol.width = 30
202+
markerSymbol.height = 30
203+
featureLayer.renderer = SimpleRenderer(symbol: markerSymbol)
204+
205+
map.addOperationalLayer(featureLayer)
206+
}
207+
}
208+
}
209+
210+
private extension URL {
211+
/// The URL to a network analysis server for San Diego, CA, USA on ArcGIS Online.
212+
static var sanDiegoNetworkAnalysis: URL {
213+
URL(string: "https://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/ClosestFacility")!
214+
}
215+
216+
/// The URL to a San Diego facilities feature layer on ArcGIS Online.
217+
static var facilitiesLayer: URL {
218+
URL(string: "https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/ArcGIS/rest/services/San_Diego_Facilities/FeatureServer/0")!
219+
}
220+
221+
/// The URL to a San Diego facilities feature layer on ArcGIS Online.
222+
static var incidentsLayer: URL {
223+
URL(string: "https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/ArcGIS/rest/services/San_Diego_Incidents/FeatureServer/0")!
224+
}
225+
226+
/// The URL to an image of a fire station symbol on ArcGIS Online.
227+
static var fireStationImage: URL {
228+
URL(string: "https://static.arcgis.com/images/Symbols/SafetyHealth/FireStation.png")!
229+
}
230+
231+
/// The URL to an image of a fire symbol on ArcGIS Online.
232+
static var fireImage: URL {
233+
URL(string: "https://static.arcgis.com/images/Symbols/SafetyHealth/esriCrimeMarker_56_Gradient.png")!
234+
}
235+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Find closest facility from point
2+
3+
Find routes from several locations to the respective closest facility.
4+
5+
![Image of find closest facility from point](find-closest-facility-from-point.png)
6+
7+
## Use case
8+
9+
Quickly and accurately determining the most efficient route between a location and a facility is a frequently encountered task. For example, a city's fire department may need to know which fire stations in the vicinity offer the quickest routes to multiple fires. Solving for the closest fire station to the fire's location using an impedance of "travel time" would provide this information.
10+
11+
## How to use the sample
12+
13+
Tap the "Solve Routes" button to solve and display the route from each incident (fire) to the nearest facility (fire station).
14+
15+
## How it works
16+
17+
1. Create a `ClosestFacilityTask` using a URL from an online service.
18+
2. Get the default set of `ClosestFacilityParameters` from the task: `ClosestFacilityTask.makeDefaultParameters()`.
19+
3. Create a `FeatureTable` using `ServiceFeatureTable.init(url:)`.
20+
4. Add a list of all facilities to the task parameters: `ClosestFacilityParameters.setFacilities(fromFeaturesIn:queryParameters:)`.
21+
5. Add a list of all incidents to the task parameters: `ClosestFacilityParameters.setIncidents(fromFeaturesIn:queryParameters:)`.
22+
6. Get `ClosestFacilityResult` by solving the task with the provided parameters: `ClosestFacilityTask.solveClosestFacility(using:)`.
23+
7. Find the closest facility for each incident by iterating over the list of `Incident`s.
24+
8. Display the route as a `Graphic` using the `ClosestFacilityRoute.routeGeometry`.
25+
26+
## Relevant API
27+
28+
* ClosestFacilityParameters
29+
* ClosestFacilityResult
30+
* ClosestFacilityRoute
31+
* ClosestFacilityTask
32+
* Facility
33+
* Graphic
34+
* GraphicsOverlay
35+
* Incident
36+
37+
## Tags
38+
39+
incident, network analysis, route, search
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"category": "Routing and Logistics",
3+
"description": "Find routes from several locations to the respective closest facility.",
4+
"ignore": false,
5+
"images": [
6+
"find-closest-facility-from-point.png"
7+
],
8+
"keywords": [
9+
"incident",
10+
"network analysis",
11+
"route",
12+
"search",
13+
"ClosestFacilityParameters",
14+
"ClosestFacilityResult",
15+
"ClosestFacilityRoute",
16+
"ClosestFacilityTask",
17+
"Facility",
18+
"Graphic",
19+
"GraphicsOverlay",
20+
"Incident"
21+
],
22+
"redirect_from": [],
23+
"relevant_apis": [
24+
"ClosestFacilityParameters",
25+
"ClosestFacilityResult",
26+
"ClosestFacilityRoute",
27+
"ClosestFacilityTask",
28+
"Facility",
29+
"Graphic",
30+
"GraphicsOverlay",
31+
"Incident"
32+
],
33+
"snippets": [
34+
"FindClosestFacilityFromPointView.swift"
35+
],
36+
"title": "Find closest facility from point"
37+
}
238 KB
Loading

0 commit comments

Comments
 (0)