Skip to content

Commit 6bd6596

Browse files
Find closest facility sample (#88)
* Refactored the README and added comments to the code * Apply suggestions from code review Co-authored-by: Jen Merritt <jmerritt@esri.com> * Adopted optimization changes * Adopted changes --------- Co-authored-by: Jen Merritt <jmerritt@esri.com>
1 parent 27c9b67 commit 6bd6596

File tree

4 files changed

+185
-65
lines changed

4 files changed

+185
-65
lines changed
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 firestations 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 on 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. Create a `FeatureTable` for each of the `Facilities` and `Incidents` services using `ServiceFeatureTable.withUri(uri)`.
19+
3. Get the default set of `ClosestFacilityParameters` from the task using `ClosestFacilityTask.createDefaultParameters()`.
20+
4. Add the facilities table to the task parameters, along with `QueryParameters` defined to query all features using `ClosestFacilityParameters.setFacilitiesWithFeatureTable(featureTable, queryParameters)`.
21+
5. Add the incidents table to the task parameters, along with `QueryParameters` defined to query all features using `ClosestFacilityParameters.setIncidentsWithFeatureTable(featureTable, queryParameters)`.
22+
6. Get the `ClosestFacilityResult` by solving the task with the provided parameters: `ClosestFacilityTask.solveClosestFacility(closestFacilityParameters)`.
23+
7. Find the closest facility for each incident by iterating over the list of `result.incidents`.
24+
8. Display the route as a `Graphic` using the `routeGraphicsOverlay.graphics.add(routeGraphic)`.
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

lib/samples/find_closest_facility_from_point/README.metadata.json

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,36 @@
22
"category": "Routing and Logistics",
33
"description": "Find routes from several locations to the respective closest facility.",
44
"ignore": false,
5-
"images": [],
6-
"keywords": [],
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+
],
722
"redirect_from": [],
8-
"relevant_apis": [],
9-
"snippets": [],
23+
"relevant_apis": [
24+
"ClosestFacilityParameters",
25+
"ClosestFacilityResult",
26+
"ClosestFacilityRoute",
27+
"ClosestFacilityTask",
28+
"Facility",
29+
"Graphic",
30+
"GraphicsOverlay",
31+
"Incident"
32+
],
33+
"snippets": [
34+
"find_closest_facility_from_point_sample.dart"
35+
],
1036
"title": "Find closest facility from point"
1137
}
237 KB
Loading

lib/samples/find_closest_facility_from_point/find_closest_facility_from_point_sample.dart

Lines changed: 116 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ class FindClosestFacilityFromPointSample extends StatefulWidget {
2929

3030
class _FindClosestFacilityFromPointSampleState
3131
extends State<FindClosestFacilityFromPointSample> with SampleStateSupport {
32+
// Create the URIs for the fire station and fire images, as well as the URIs for the facilities and incidents layers.
3233
static final _fireStationImageUri = Uri.parse(
3334
'https://static.arcgis.com/images/Symbols/SafetyHealth/FireStation.png');
3435
static final _fireImageUri = Uri.parse(
@@ -37,46 +38,63 @@ class _FindClosestFacilityFromPointSampleState
3738
'https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/ArcGIS/rest/services/San_Diego_Facilities/FeatureServer/0');
3839
static final _incidentsLayerUri = Uri.parse(
3940
'https://services2.arcgis.com/ZQgQTuoyBrtmoGdP/ArcGIS/rest/services/San_Diego_Incidents/FeatureServer/0');
41+
// Create a task for the closest facility service.
4042
final _closestFacilityTask = ClosestFacilityTask.withUrl(Uri.parse(
4143
'https://sampleserver6.arcgisonline.com/arcgis/rest/services/NetworkAnalysis/SanDiego/NAServer/ClosestFacility'));
42-
44+
// Create a controller for the map view.
4345
final _mapViewController = ArcGISMapView.createController();
46+
// Create a graphics overlay for the route.
4447
final _routeGraphicsOverlay = GraphicsOverlay();
45-
bool _isRouteSolved = false;
46-
bool _isInitialized = false;
48+
// Create a flag to track whether the route has been solved.
49+
var _routeSolved = false;
50+
// A flag for when the map view is ready and controls can be used.
51+
var _ready = false;
52+
// Create parameters for the closest facility task.
4753
late final ClosestFacilityParameters _closestFacilityParameters;
54+
// Create a symbol for the route line.
4855
final _routeLineSymbol = SimpleLineSymbol(
4956
style: SimpleLineSymbolStyle.solid, color: Colors.blue, width: 5.0);
5057

5158
@override
5259
Widget build(BuildContext context) {
5360
return Scaffold(
5461
body: SafeArea(
55-
child: Column(
62+
top: false,
63+
child: Stack(
5664
children: [
57-
Expanded(
58-
child: ArcGISMapView(
59-
controllerProvider: () => _mapViewController,
60-
onMapViewReady: onMapViewReady,
61-
),
62-
),
63-
SizedBox(
64-
height: 60,
65-
child: Row(
66-
crossAxisAlignment: CrossAxisAlignment.center,
67-
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
68-
children: [
69-
ElevatedButton(
70-
onPressed: !_isRouteSolved && _isInitialized
71-
? () => solveRoutes()
72-
: null,
73-
child: const Text('Solve Routes'),
65+
Column(
66+
children: [
67+
Expanded(
68+
// Add a map view to the widget tree and set a controller.
69+
child: ArcGISMapView(
70+
controllerProvider: () => _mapViewController,
71+
onMapViewReady: onMapViewReady,
7472
),
75-
ElevatedButton(
76-
onPressed: _isRouteSolved ? () => resetRoutes() : null,
77-
child: const Text('Reset'),
78-
),
79-
],
73+
),
74+
Row(
75+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
76+
children: [
77+
// Create buttons to solve the routes and reset the graphics.
78+
ElevatedButton(
79+
onPressed: !_routeSolved ? solveRoutes : null,
80+
child: const Text('Solve Routes'),
81+
),
82+
ElevatedButton(
83+
onPressed: _routeSolved ? resetRoutes : null,
84+
child: const Text('Reset'),
85+
),
86+
],
87+
),
88+
],
89+
),
90+
// Display a progress indicator and prevent interaction until state is ready.
91+
Visibility(
92+
visible: !_ready,
93+
child: SizedBox.expand(
94+
child: Container(
95+
color: Colors.white30,
96+
child: const Center(child: CircularProgressIndicator()),
97+
),
8098
),
8199
),
82100
],
@@ -85,16 +103,41 @@ class _FindClosestFacilityFromPointSampleState
85103
);
86104
}
87105

88-
Future<void> onMapViewReady() async {
106+
void onMapViewReady() async {
107+
// Create a map with the ArcGIS Streets basemap style.
89108
final map = ArcGISMap.withBasemapStyle(BasemapStyle.arcGISStreets);
90109

110+
// Create feature table for the facilities layer.
111+
final facilitiesFeatureTable =
112+
ServiceFeatureTable.withUri(_facilitiesLayerUri);
113+
// Create a marker symbol for the facilities.
114+
final facilitiesMarkerSymbol =
115+
PictureMarkerSymbol.withUrl(_fireStationImageUri)
116+
..width = 30
117+
..height = 30;
118+
// Create a feature layer for the facilities.
91119
final facilitiesLayer =
92-
buildFeatureLayer(_facilitiesLayerUri, _fireStationImageUri);
93-
final incidentsLayer = buildFeatureLayer(_incidentsLayerUri, _fireImageUri);
120+
FeatureLayer.withFeatureTable(facilitiesFeatureTable)
121+
..renderer = SimpleRenderer(symbol: facilitiesMarkerSymbol);
122+
123+
// Create feature table for the incidents layer.
124+
final incidentsFeatureTable =
125+
ServiceFeatureTable.withUri(_incidentsLayerUri);
126+
// Create a marker symbol for the incidents.
127+
final incidentsMarkerSymbol = PictureMarkerSymbol.withUrl(_fireImageUri)
128+
..width = 30
129+
..height = 30;
130+
// Create a feature layer for the incidents.
131+
final incidentsLayer = FeatureLayer.withFeatureTable(incidentsFeatureTable)
132+
..renderer = SimpleRenderer(symbol: incidentsMarkerSymbol);
133+
134+
// Add the layers to the map.
94135
map.operationalLayers.addAll([facilitiesLayer, incidentsLayer]);
95136

137+
// Set the map on the map view controller.
96138
_mapViewController.arcGISMap = map;
97139

140+
// Add the route graphics overlay to the map view controller.
98141
_routeGraphicsOverlay.opacity = 0.75;
99142
_mapViewController.graphicsOverlays.add(_routeGraphicsOverlay);
100143

@@ -104,80 +147,92 @@ class _FindClosestFacilityFromPointSampleState
104147
incidentsLayer.load(),
105148
]);
106149

107-
// Get the extent from the layers and use the combination as the viewpoint geometry
150+
// Get the extent from the layers and use the combination as the viewpoint geometry.
108151
final mapExtent = GeometryEngine.combineExtents(
109152
geometry1: facilitiesLayer.fullExtent!,
110153
geometry2: incidentsLayer.fullExtent!,
111154
);
112155

156+
// Set the viewpoint geometry on the map view controller.
113157
_mapViewController.setViewpointGeometry(mapExtent, paddingInDiPs: 30);
114158

159+
// Generate the closest facility parameters.
115160
_closestFacilityParameters = await generateClosestFacilityParameters(
116-
facilitiesLayer, incidentsLayer);
161+
facilitiesFeatureTable, incidentsFeatureTable);
117162

118-
setState(() => _isInitialized = true);
163+
// Set the initialized flag to true.
164+
setState(() => _ready = true);
119165
}
120166

121167
FeatureLayer buildFeatureLayer(Uri tableUri, Uri imageUri) {
168+
// Create a feature table and feature layer for the facilities or incidents.
122169
final featureTable = ServiceFeatureTable.withUri(tableUri);
123-
final featureLayer = FeatureLayer.withFeatureTable(featureTable);
124-
final markerSymbol = PictureMarkerSymbol.withUrl(imageUri);
125-
markerSymbol.width = 30;
126-
markerSymbol.height = 30;
127-
featureLayer.renderer = SimpleRenderer(symbol: markerSymbol);
170+
final markerSymbol = PictureMarkerSymbol.withUrl(imageUri)
171+
..width = 30
172+
..height = 30;
173+
final featureLayer = FeatureLayer.withFeatureTable(featureTable)
174+
..renderer = SimpleRenderer(symbol: markerSymbol);
128175

129176
return featureLayer;
130177
}
131178

132179
Future<ClosestFacilityParameters> generateClosestFacilityParameters(
133-
FeatureLayer facilitiesLayer, FeatureLayer incidentsLayer) async {
180+
FeatureTable facilitiesFeatureTable,
181+
FeatureTable incidentsFeatureTable) async {
182+
// Create query parameters to get all features.
134183
final featureQueryParams = QueryParameters()..whereClause = '1=1';
135-
136-
final parameters = await _closestFacilityTask.createDefaultParameters();
137-
parameters.setFacilitiesWithFeatureTable(
138-
featureTable: facilitiesLayer.featureTable! as ArcGISFeatureTable,
139-
queryParameters: featureQueryParams,
140-
);
141-
parameters.setIncidentsWithFeatureTable(
142-
featureTable: incidentsLayer.featureTable! as ArcGISFeatureTable,
143-
queryParameters: featureQueryParams,
144-
);
184+
// Create default parameters for the closest facility task.
185+
final parameters = await _closestFacilityTask.createDefaultParameters()
186+
..setFacilitiesWithFeatureTable(
187+
featureTable: facilitiesFeatureTable as ArcGISFeatureTable,
188+
queryParameters: featureQueryParams,
189+
)
190+
..setIncidentsWithFeatureTable(
191+
featureTable: incidentsFeatureTable as ArcGISFeatureTable,
192+
queryParameters: featureQueryParams,
193+
);
145194

146195
return parameters;
147196
}
148197

149-
Future<void> solveRoutes() async {
150-
final results = await _closestFacilityTask.solveClosestFacility(
151-
closestFacilityParameters: _closestFacilityParameters,
152-
);
153-
198+
void solveRoutes() async {
199+
setState(() => _ready = false);
200+
// Solve the closest facility task with the parameters.
201+
final result = await _closestFacilityTask.solveClosestFacility(
202+
closestFacilityParameters: _closestFacilityParameters);
154203
for (var incidentIdx = 0;
155-
incidentIdx < results.incidents.length;
204+
incidentIdx < result.incidents.length;
156205
++incidentIdx) {
157206
final rankedFacilities =
158-
results.getRankedFacilityIndexes(incidentIndex: incidentIdx);
207+
result.getRankedFacilityIndexes(incidentIndex: incidentIdx);
159208
if (rankedFacilities.isEmpty) {
160209
continue;
161210
}
162211

212+
// Get the route to the closest facility.
163213
final closestFacilityIdx = rankedFacilities.first;
164-
final routeToFacility = results.getRoute(
214+
final routeToFacility = result.getRoute(
165215
facilityIndex: closestFacilityIdx,
166216
incidentIndex: incidentIdx,
167217
);
218+
// Add the route to the graphics overlay.
168219
if (routeToFacility != null) {
169220
final routeGraphic = Graphic(
170-
geometry: routeToFacility.routeGeometry,
171-
symbol: _routeLineSymbol,
172-
);
221+
geometry: routeToFacility.routeGeometry, symbol: _routeLineSymbol);
173222
_routeGraphicsOverlay.graphics.add(routeGraphic);
174223
}
175224
}
176-
setState(() => _isRouteSolved = true);
225+
226+
// Set the route solved flag to true.
227+
setState(() {
228+
_ready = true;
229+
_routeSolved = true;
230+
});
177231
}
178232

179233
void resetRoutes() {
234+
// Clear the graphics overlay and set the route solved flag to false.
180235
_routeGraphicsOverlay.graphics.clear();
181-
setState(() => _isRouteSolved = false);
236+
setState(() => _routeSolved = false);
182237
}
183238
}

0 commit comments

Comments
 (0)