Skip to content

Commit b604e60

Browse files
authored
Merge pull request #83 from Esri/cs/1553_location_history_update
"Show Device Location History" sample updated
2 parents 4c5f029 + d22a437 commit b604e60

File tree

4 files changed

+204
-84
lines changed

4 files changed

+204
-84
lines changed
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Show device location history
2+
3+
Display your device's location history on the map.
4+
5+
![Image of show device location history](show_device_location_history.png)
6+
7+
## Use case
8+
9+
You can track device location history and display it as lines and points on the map. The history can be used to visualize how the user moved through the world, to retrace their steps, or to create new feature geometry. An unmapped trail, for example, could be added to the map using this technique.
10+
11+
## How to use the sample
12+
13+
Tap 'Start Tracking' to start tracking your location, which will appear as points on the map. A line will connect the points for easier visualization. Tap 'Stop Tracking' to stop updating the location history. This sample uses a simulated data source to allow the sample to be useful on desktop/non-mobile devices. To track a user's real position, use the `LocationDataSource` instead.
14+
15+
## How it works
16+
17+
1. Create a graphics overlay to show each point and another graphics overlay for displaying the route line.
18+
2. Create a `SimulatedLocationDataSource` and initialize it with a polyline. Start the `SimulatedLocationDataSource` to begin receiving location updates.
19+
- NOTE: To track a user's real position, use `LocationDataSource` instead.
20+
3. Subscribe to the `onLocationChanged` event to handle location updates.
21+
4. Every time the location updates, store that location, display a point on the map, and recreate the route line.
22+
23+
## Relevant API
24+
25+
* LocationDataSource
26+
* LocationDisplay
27+
* LocationDisplayAutoPanMode
28+
* PolylineBuilder
29+
* SimpleLineSymbol
30+
* SimpleMarkerSymbol
31+
* SimpleRenderer
32+
* SimulatedLocationDataSource
33+
* Graphic
34+
* GraphicsOverlay
35+
36+
## About the data
37+
38+
A custom set of points is used to create a `Polyline` and initialize a `SimulatedLocationDataSource`. This simulated location data source enables easier testing and allows the sample to be used on devices without an actively updating GPS signal.
39+
40+
## Tags
41+
42+
bread crumb, breadcrumb, GPS, history, movement, navigation, real-time, trace, track, trail

lib/samples/show_device_location_history/README.metadata.json

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,46 @@
22
"category": "Maps",
33
"description": "Display your device's location history on the map.",
44
"ignore": false,
5-
"images": [],
6-
"keywords": [],
5+
"images": [
6+
"show_device_location_history.png"
7+
],
8+
"keywords": [
9+
"GPS",
10+
"bread crumb",
11+
"breadcrumb",
12+
"history",
13+
"movement",
14+
"navigation",
15+
"real-time",
16+
"trace",
17+
"track",
18+
"trail",
19+
"Graphic",
20+
"GraphicsOverlay",
21+
"LocationDataSource",
22+
"LocationDisplay",
23+
"LocationDisplayAutoPanMode",
24+
"PolylineBuilder",
25+
"SimpleLineSymbol",
26+
"SimpleMarkerSymbol",
27+
"SimpleRenderer",
28+
"SimulatedLocationDataSource"
29+
],
730
"redirect_from": [],
8-
"relevant_apis": [],
9-
"snippets": [],
31+
"relevant_apis": [
32+
"Graphic",
33+
"GraphicsOverlay",
34+
"LocationDataSource",
35+
"LocationDisplay",
36+
"LocationDisplayAutoPanMode",
37+
"PolylineBuilder",
38+
"SimpleLineSymbol",
39+
"SimpleMarkerSymbol",
40+
"SimpleRenderer",
41+
"SimulatedLocationDataSource"
42+
],
43+
"snippets": [
44+
"show_device_location_history_sample.dart"
45+
],
1046
"title": "Show device location history"
1147
}
204 KB
Loading

lib/samples/show_device_location_history/show_device_location_history_sample.dart

Lines changed: 122 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,31 @@ class ShowDeviceLocationHistorySample extends StatefulWidget {
3232

3333
class _ShowDeviceLocationHistorySampleState
3434
extends State<ShowDeviceLocationHistorySample> with SampleStateSupport {
35+
// Create a controller for the map view.
3536
final _mapViewController = ArcGISMapView.createController();
36-
double _latitude = 0.0;
37-
double _longitude = 0.0;
38-
double _heading = 0.0;
37+
// A location data source to simulate location updates.
3938
final _locationDataSource = SimulatedLocationDataSource();
40-
StreamSubscription? _statusSubscription;
39+
// Subscription to listen for location changes.
4140
StreamSubscription? _locationSubscription;
42-
ArcGISException? _ldsException;
41+
// A GraphicsOverlay to display the location history polyline.
42+
final _locationHistoryLineOverlay = GraphicsOverlay();
43+
// A GraphicsOverlay to display the location history points.
44+
final _locationHistoryPointOverlay = GraphicsOverlay();
45+
// A PolylineBuilder to build the location history polyline.
46+
final _polylineBuilder = PolylineBuilder.fromSpatialReference(
47+
SpatialReference.wgs84,
48+
);
49+
// A flag for when the map view is ready and controls can be used.
50+
var _ready = false;
51+
// A flag for toggling location tracking.
52+
var _enableTracking = false;
4353

4454
@override
4555
void dispose() {
4656
_locationDataSource.stop();
4757
_locationSubscription?.cancel();
48-
_statusSubscription?.cancel();
58+
_locationHistoryLineOverlay.graphics.clear();
59+
_locationHistoryPointOverlay.graphics.clear();
4960

5061
super.dispose();
5162
}
@@ -54,80 +65,59 @@ class _ShowDeviceLocationHistorySampleState
5465
Widget build(BuildContext context) {
5566
return Scaffold(
5667
body: SafeArea(
57-
child: Column(
58-
crossAxisAlignment: CrossAxisAlignment.center,
68+
top: false,
69+
child: Stack(
5970
children: [
60-
Expanded(
61-
child: Stack(
62-
alignment: Alignment.bottomRight,
63-
children: [
64-
ArcGISMapView(
71+
Column(
72+
children: [
73+
Expanded(
74+
// Add a map view to the widget tree and set a controller.
75+
child: ArcGISMapView(
6576
controllerProvider: () => _mapViewController,
6677
onMapViewReady: onMapViewReady,
6778
),
68-
Positioned(
69-
bottom: 60.0,
70-
right: 10.0,
71-
child: Container(
72-
color: Colors.white,
73-
padding: const EdgeInsets.all(5.0),
74-
child: Column(
75-
mainAxisAlignment: MainAxisAlignment.center,
76-
crossAxisAlignment: CrossAxisAlignment.start,
77-
children: [
78-
const Text(
79-
'Device Location:',
80-
style: TextStyle(fontWeight: FontWeight.bold),
81-
),
82-
Text('Latitude: $_latitude'),
83-
Text('Longitude: $_longitude'),
84-
Text('Heading: $_heading')
85-
],
86-
),
87-
),
88-
)
89-
],
90-
),
91-
),
92-
SizedBox(
93-
height: 90,
94-
width: double.infinity,
95-
child: Column(
96-
children: [
97-
const Text('Location Data Source'),
98-
Visibility(
99-
visible: _ldsException == null,
100-
child: ElevatedButton(
101-
child: Text(_locationDataSource.status ==
102-
LocationDataSourceStatus.started
103-
? 'Stop'
104-
: 'Start'),
79+
),
80+
Row(
81+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
82+
children: [
83+
// A button to enable or disable location tracking.
84+
ElevatedButton(
10585
onPressed: () {
106-
if (_locationDataSource.status ==
107-
LocationDataSourceStatus.started) {
108-
_mapViewController.locationDisplay.stop();
109-
} else {
110-
_mapViewController.locationDisplay.start();
111-
}
86+
setState(() {
87+
_enableTracking = !_enableTracking;
88+
});
11289
},
90+
child: Text(
91+
_enableTracking ? 'Stop Tracking' : 'Start Tracking',
92+
),
11393
),
94+
],
95+
),
96+
],
97+
),
98+
// Display a progress indicator and prevent interaction until state is ready.
99+
Visibility(
100+
visible: !_ready,
101+
child: SizedBox.expand(
102+
child: Container(
103+
color: Colors.white30,
104+
child: const Center(
105+
child: CircularProgressIndicator(),
114106
),
115-
Visibility(
116-
visible: _ldsException != null,
117-
child: Text('Exception: ${_ldsException?.message}'),
118-
),
119-
],
107+
),
120108
),
121-
)
109+
),
122110
],
123111
),
124112
),
125113
);
126114
}
127115

116+
// The method is called when the map view is ready to be used.
128117
void onMapViewReady() async {
129-
final map = ArcGISMap.withBasemapStyle(BasemapStyle.arcGISImageryStandard);
130-
118+
// Create a map with the ArcGIS Navigation basemap style.
119+
final map = ArcGISMap.withBasemapStyle(BasemapStyle.arcGISNavigation);
120+
// Set the initial viewpoint.
131121
map.initialViewpoint = Viewpoint.fromCenter(
132122
ArcGISPoint(
133123
x: -110.8258,
@@ -136,43 +126,95 @@ class _ShowDeviceLocationHistorySampleState
136126
),
137127
scale: 2e4,
138128
);
139-
129+
// Add the map to the map view controller.
140130
_mapViewController.arcGISMap = map;
141-
await _initLocationDisplay();
131+
// Add the graphics overlays to the map view.
132+
_mapViewController.graphicsOverlays.addAll([
133+
_locationHistoryLineOverlay,
134+
_locationHistoryPointOverlay,
135+
]);
136+
// Set the renderers for the graphics overlays.
137+
_locationHistoryLineOverlay.renderer = SimpleRenderer(
138+
symbol: SimpleLineSymbol(
139+
color: Colors.red[100]!,
140+
width: 2.0,
141+
),
142+
);
143+
_locationHistoryPointOverlay.renderer = SimpleRenderer(
144+
symbol: SimpleMarkerSymbol(
145+
color: Colors.red,
146+
size: 10.0,
147+
),
148+
);
149+
// Wait for the map to be displayed before starting the location display.
150+
_mapViewController.onDrawStatusChanged.listen((status) async {
151+
if (status == DrawStatus.completed &&
152+
_locationDataSource.status == LocationDataSourceStatus.stopped) {
153+
await _initLocationDisplay();
154+
}
155+
});
156+
// Set the ready state variable to true to enable the sample UI.
157+
setState(() => _ready = true);
142158
}
143159

160+
// Initialize the location display with the location data source.
144161
Future<void> _initLocationDisplay() async {
145162
final locationDisplay = _mapViewController.locationDisplay;
146163
locationDisplay.dataSource = _locationDataSource;
147164
locationDisplay.autoPanMode = LocationDisplayAutoPanMode.recenter;
148165
locationDisplay.useCourseSymbolOnMovement = true;
149-
await _initLocationDataSource();
166+
await _startLocationDataSource();
150167
}
151168

152-
Future<void> _initLocationDataSource() async {
169+
// Start the location data source and listen for location changes.
170+
Future<void> _startLocationDataSource() async {
153171
final routeLineJson =
154172
await rootBundle.loadString('assets/SimulatedRoute.json');
155173
final routeLine = Geometry.fromJsonString(routeLineJson) as Polyline;
156174
_locationDataSource.setLocationsWithPolyline(routeLine);
157-
_statusSubscription = _locationDataSource.onStatusChanged.listen((_) {
158-
// Redraw the screen when the LDS status changes
159-
setState(() {});
160-
});
161175

176+
// Start the location data source.
162177
try {
163178
await _locationDataSource.start();
164-
_locationSubscription = _locationDataSource.onLocationChanged
165-
.listen(_handleLdsLocationChange);
166179
} on ArcGISException catch (e) {
167-
setState(() => _ldsException = e);
180+
if (mounted) {
181+
showDialog(
182+
context: context,
183+
builder: (_) => AlertDialog(
184+
content: Text(
185+
e.message,
186+
),
187+
),
188+
);
189+
}
190+
}
191+
192+
// Listen for location changes.
193+
if (_locationDataSource.status == LocationDataSourceStatus.started) {
194+
_locationSubscription = _locationDataSource.onLocationChanged.listen(
195+
_handleLdsLocationChange,
196+
);
168197
}
169198
}
170199

200+
// Handle location changes from the location data source.
171201
void _handleLdsLocationChange(ArcGISLocation location) {
172-
setState(() {
173-
_latitude = location.position.y;
174-
_longitude = location.position.x;
175-
_heading = location.course;
176-
});
202+
if (!_enableTracking) return;
203+
// Add the location to the location history as a graphic point.
204+
final point = location.position;
205+
_locationHistoryPointOverlay.graphics.add(
206+
Graphic(
207+
geometry: point,
208+
),
209+
);
210+
// Add the location to the location history as a polyline.
211+
_polylineBuilder.addPoint(point);
212+
// Visualize the location history polyline on the map.
213+
_locationHistoryLineOverlay.graphics.clear();
214+
_locationHistoryLineOverlay.graphics.add(
215+
Graphic(
216+
geometry: _polylineBuilder.toGeometry() as Polyline,
217+
),
218+
);
177219
}
178220
}

0 commit comments

Comments
 (0)