Skip to content

Commit b2d7342

Browse files
authored
Merge branch 'v.next' into Caleb/New-FindRouteInMobileMapPackage
2 parents 77852f9 + 382c8c9 commit b2d7342

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
@@ -238,6 +238,8 @@
238238
D76EE6082AF9AFEC00DA0325 /* FindRouteAroundBarriersView.Model.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D76EE6062AF9AFE100DA0325 /* FindRouteAroundBarriersView.Model.swift */; };
239239
D7705D582AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7705D552AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift */; };
240240
D7705D5B2AFC246A00CC0335 /* FindClosestFacilityToMultiplePointsView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7705D552AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift */; };
241+
D7705D642AFC570700CC0335 /* FindClosestFacilityFromPointView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */; };
242+
D7705D662AFC575000CC0335 /* FindClosestFacilityFromPointView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */; };
241243
D7749AD62AF08BF50086632F /* FindRouteInTransportNetworkView.Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7749AD52AF08BF50086632F /* FindRouteInTransportNetworkView.Model.swift */; };
242244
D77570C02A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D77570BF2A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift */; };
243245
D77570C12A2943D900F490CD /* AnimateImagesWithImageOverlayView.swift in Copy Source Code Files */ = {isa = PBXBuildFile; fileRef = D77570BF2A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift */; };
@@ -375,6 +377,7 @@
375377
D737237B2AF5AE1A00846884 /* FindRouteInMobileMapPackageView.Models.swift in Copy Source Code Files */,
376378
D737237A2AF5AE1600846884 /* FindRouteInMobileMapPackageView.MobileMapView.swift in Copy Source Code Files */,
377379
D76000B12AF19C4600B3084D /* FindRouteInMobileMapPackageView.swift in Copy Source Code Files */,
380+
D7705D662AFC575000CC0335 /* FindClosestFacilityFromPointView.swift in Copy Source Code Files */,
378381
D73FD0002B02C9610006360D /* FindRouteAroundBarriersView.Views.swift in Copy Source Code Files */,
379382
D76EE6082AF9AFEC00DA0325 /* FindRouteAroundBarriersView.Model.swift in Copy Source Code Files */,
380383
D7DDF8562AF47C86004352D9 /* FindRouteAroundBarriersView.swift in Copy Source Code Files */,
@@ -639,6 +642,7 @@
639642
D769C2112A29019B00030F61 /* SetUpLocationDrivenGeotriggersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetUpLocationDrivenGeotriggersView.swift; sourceTree = "<group>"; };
640643
D76EE6062AF9AFE100DA0325 /* FindRouteAroundBarriersView.Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRouteAroundBarriersView.Model.swift; sourceTree = "<group>"; };
641644
D7705D552AFC244E00CC0335 /* FindClosestFacilityToMultiplePointsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindClosestFacilityToMultiplePointsView.swift; sourceTree = "<group>"; };
645+
D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindClosestFacilityFromPointView.swift; sourceTree = "<group>"; };
642646
D7749AD52AF08BF50086632F /* FindRouteInTransportNetworkView.Model.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FindRouteInTransportNetworkView.Model.swift; sourceTree = "<group>"; };
643647
D77570BF2A2942F800F490CD /* AnimateImagesWithImageOverlayView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnimateImagesWithImageOverlayView.swift; sourceTree = "<group>"; };
644648
D77572AD2A295DDD00F490CD /* PacificSouthWest2 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = PacificSouthWest2; sourceTree = "<group>"; };
@@ -824,6 +828,7 @@
824828
E070A0A1286F3B3400F2B606 /* Download preplanned map area */,
825829
E004A6EE284E4B7A002A1FE6 /* Download vector tiles to local cache */,
826830
1C26ED122A859525009B7721 /* Filter features in scene */,
831+
D7705D5F2AFC570700CC0335 /* Find closest facility from point */,
827832
D7705D542AFC244E00CC0335 /* Find closest facility to multiple points */,
828833
D78666A92A21616D00C60110 /* Find nearest vertex */,
829834
E066DD33285CF3A0004D3D5B /* Find route */,
@@ -1554,6 +1559,14 @@
15541559
path = "Find closest facility to multiple points";
15551560
sourceTree = "<group>";
15561561
};
1562+
D7705D5F2AFC570700CC0335 /* Find closest facility from point */ = {
1563+
isa = PBXGroup;
1564+
children = (
1565+
D7705D612AFC570700CC0335 /* FindClosestFacilityFromPointView.swift */,
1566+
);
1567+
path = "Find closest facility from point";
1568+
sourceTree = "<group>";
1569+
};
15571570
D77570BC2A29427200F490CD /* Animate images with image overlay */ = {
15581571
isa = PBXGroup;
15591572
children = (
@@ -2235,6 +2248,7 @@
22352248
D7058FB12ACB423C00A40F14 /* Animate3DGraphicView.Model.swift in Sources */,
22362249
0044CDDF2995C39E004618CE /* ShowDeviceLocationHistoryView.swift in Sources */,
22372250
E041ABC0287CA9F00056009B /* WebView.swift in Sources */,
2251+
D7705D642AFC570700CC0335 /* FindClosestFacilityFromPointView.swift in Sources */,
22382252
E088E1572862579D00413100 /* SetSurfacePlacementModeView.swift in Sources */,
22392253
1CAF831F2A20305F000E1E60 /* ShowUtilityAssociationsView.swift in Sources */,
22402254
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)