Simple User Interface

Introduction

This Example shows how to display simple mapview and perform basic map actions:

  • Display MapView Widget
  • User Interface on MapView
    • Customize MapView Size
    • MapView UI Settings
    • MapView GestureDetector
docs-imagedocs-image
Android snapshotiOS snapshot

For all code examples, refer to Flutter Maps SDK Code Examples

MapUiPage view source

1
import 'dart:math';
2
3
import 'package:flutter/material.dart';
4
import 'package:collection/collection.dart';
5
import 'package:nb_maps_flutter/nb_maps_flutter.dart';
6
7
import 'main.dart';
8
import 'page.dart';
9
10
final LatLngBounds sydneyBounds = LatLngBounds(
11
southwest: const LatLng(-34.022631, 150.620685),
12
northeast: const LatLng(-33.571835, 151.325952),
13
);
14
15
class MapUiPage extends ExamplePage {
16
MapUiPage() : super(const Icon(Icons.map), 'User interface');
17
18
@override
19
Widget build(BuildContext context) {
20
return const MapUiBody();
21
}
22
}
23
24
class MapUiBody extends StatefulWidget {
25
const MapUiBody();
26
27
@override
28
State<StatefulWidget> createState() => MapUiBodyState();
29
}
30
31
class MapUiBodyState extends State<MapUiBody> {
32
MapUiBodyState();
33
34
static final CameraPosition _kInitialPosition = const CameraPosition(
35
target: LatLng(-33.852, 151.211),
36
zoom: 11.0,
37
);
38
39
NextbillionMapController? mapController;
40
CameraPosition _position = _kInitialPosition;
41
bool _isMoving = false;
42
bool _compassEnabled = true;
43
bool _mapExpanded = true;
44
CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded;
45
MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded;
46
int _styleStringIndex = 0;
47
// Style string can a reference to a local or remote resources.
48
// On Android the raw JSON can also be passed via a styleString, on iOS this is not supported.
49
List<String> _styleStrings = [
50
"https://api.nextbillion.io/maps/streets/style.json",
51
"https://api.nextbillion.io/maps/streets/style.json",
52
"https://api.nextbillion.io/maps/streets/style.json"
53
];
54
List<String> _styleStringLabels = [
55
"NBMAP_STREETS",
56
"SATELLITE",
57
"LOCAL_ASSET"
58
];
59
bool _rotateGesturesEnabled = true;
60
bool _scrollGesturesEnabled = true;
61
bool? _doubleClickToZoomEnabled;
62
bool _tiltGesturesEnabled = true;
63
bool _zoomGesturesEnabled = true;
64
bool _myLocationEnabled = true;
65
bool _telemetryEnabled = true;
66
MyLocationTrackingMode _myLocationTrackingMode = MyLocationTrackingMode.None;
67
List<Object>? _featureQueryFilter;
68
Fill? _selectedFill;
69
70
@override
71
void initState() {
72
super.initState();
73
}
74
75
void _onMapChanged() {
76
setState(() {
77
_extractMapInfo();
78
});
79
}
80
81
void _extractMapInfo() {
82
final position = mapController!.cameraPosition;
83
if (position != null) _position = position;
84
_isMoving = mapController!.isCameraMoving;
85
}
86
87
@override
88
void dispose() {
89
mapController?.removeListener(_onMapChanged);
90
super.dispose();
91
}
92
93
Widget _myLocationTrackingModeCycler() {
94
final MyLocationTrackingMode nextType = MyLocationTrackingMode.values[
95
(_myLocationTrackingMode.index + 1) %
96
MyLocationTrackingMode.values.length];
97
return TextButton(
98
child: Text('change to $nextType'),
99
onPressed: () {
100
setState(() {
101
_myLocationTrackingMode = nextType;
102
});
103
},
104
);
105
}
106
107
Widget _queryFilterToggler() {
108
return TextButton(
109
child: Text(
110
'filter zoo on click ${_featureQueryFilter == null ? 'disabled' : 'enabled'}'),
111
onPressed: () {
112
setState(() {
113
if (_featureQueryFilter == null) {
114
_featureQueryFilter = [
115
"==",
116
["get", "type"],
117
"zoo"
118
];
119
} else {
120
_featureQueryFilter = null;
121
}
122
});
123
},
124
);
125
}
126
127
Widget _mapSizeToggler() {
128
return TextButton(
129
child: Text('${_mapExpanded ? 'shrink' : 'expand'} map'),
130
onPressed: () {
131
setState(() {
132
_mapExpanded = !_mapExpanded;
133
});
134
},
135
);
136
}
137
138
Widget _compassToggler() {
139
return TextButton(
140
child: Text('${_compassEnabled ? 'disable' : 'enable'} compasss'),
141
onPressed: () {
142
setState(() {
143
_compassEnabled = !_compassEnabled;
144
});
145
},
146
);
147
}
148
149
Widget _latLngBoundsToggler() {
150
return TextButton(
151
child: Text(
152
_cameraTargetBounds.bounds == null
153
? 'bound camera target'
154
: 'release camera target',
155
),
156
onPressed: () {
157
setState(() {
158
_cameraTargetBounds = _cameraTargetBounds.bounds == null
159
? CameraTargetBounds(sydneyBounds)
160
: CameraTargetBounds.unbounded;
161
});
162
},
163
);
164
}
165
166
Widget _zoomBoundsToggler() {
167
return TextButton(
168
child: Text(_minMaxZoomPreference.minZoom == null
169
? 'bound zoom'
170
: 'release zoom'),
171
onPressed: () {
172
setState(() {
173
_minMaxZoomPreference = _minMaxZoomPreference.minZoom == null
174
? const MinMaxZoomPreference(12.0, 16.0)
175
: MinMaxZoomPreference.unbounded;
176
});
177
},
178
);
179
}
180
181
Widget _setStyleToSatellite() {
182
return TextButton(
183
child: Text(
184
'change map style to ${_styleStringLabels[(_styleStringIndex + 1) % _styleStringLabels.length]}'),
185
onPressed: () {
186
setState(() {
187
_styleStringIndex = (_styleStringIndex + 1) % _styleStrings.length;
188
});
189
},
190
);
191
}
192
193
Widget _rotateToggler() {
194
return TextButton(
195
child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
196
onPressed: () {
197
setState(() {
198
_rotateGesturesEnabled = !_rotateGesturesEnabled;
199
});
200
},
201
);
202
}
203
204
Widget _scrollToggler() {
205
return TextButton(
206
child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
207
onPressed: () {
208
setState(() {
209
_scrollGesturesEnabled = !_scrollGesturesEnabled;
210
});
211
},
212
);
213
}
214
215
Widget _doubleClickToZoomToggler() {
216
final stateInfo = _doubleClickToZoomEnabled == null
217
? "disable"
218
: _doubleClickToZoomEnabled!
219
? 'unset'
220
: 'enable';
221
return TextButton(
222
child: Text('$stateInfo double click to zoom'),
223
onPressed: () {
224
setState(() {
225
if (_doubleClickToZoomEnabled == null) {
226
_doubleClickToZoomEnabled = false;
227
} else if (!_doubleClickToZoomEnabled!) {
228
_doubleClickToZoomEnabled = true;
229
} else {
230
_doubleClickToZoomEnabled = null;
231
}
232
});
233
},
234
);
235
}
236
237
Widget _tiltToggler() {
238
return TextButton(
239
child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
240
onPressed: () {
241
setState(() {
242
_tiltGesturesEnabled = !_tiltGesturesEnabled;
243
});
244
},
245
);
246
}
247
248
Widget _zoomToggler() {
249
return TextButton(
250
child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
251
onPressed: () {
252
setState(() {
253
_zoomGesturesEnabled = !_zoomGesturesEnabled;
254
});
255
},
256
);
257
}
258
259
Widget _myLocationToggler() {
260
return TextButton(
261
child: Text('${_myLocationEnabled ? 'disable' : 'enable'} my location'),
262
onPressed: () {
263
setState(() {
264
_myLocationEnabled = !_myLocationEnabled;
265
});
266
},
267
);
268
}
269
270
Widget _telemetryToggler() {
271
return TextButton(
272
child: Text('${_telemetryEnabled ? 'disable' : 'enable'} telemetry'),
273
onPressed: () {
274
setState(() {
275
_telemetryEnabled = !_telemetryEnabled;
276
});
277
mapController?.setTelemetryEnabled(_telemetryEnabled);
278
},
279
);
280
}
281
282
Widget _visibleRegionGetter() {
283
return TextButton(
284
child: Text('get currently visible region'),
285
onPressed: () async {
286
var result = await mapController!.getVisibleRegion();
287
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
288
content: Text(
289
"SW: ${result.southwest.toString()} NE: ${result.northeast.toString()}"),
290
));
291
},
292
);
293
}
294
295
_clearFill() {
296
if (_selectedFill != null) {
297
mapController!.removeFill(_selectedFill!);
298
setState(() {
299
_selectedFill = null;
300
});
301
}
302
}
303
304
_drawFill(List<dynamic> features) async {
305
Map<String, dynamic>? feature =
306
features.firstWhereOrNull((f) => f['geometry']['type'] == 'Polygon');
307
308
if (feature != null) {
309
List<List<LatLng>> geometry = feature['geometry']['coordinates']
310
.map(
311
(ll) => ll.map((l) => LatLng(l[1], l[0])).toList().cast<LatLng>())
312
.toList()
313
.cast<List<LatLng>>();
314
Fill fill = await mapController!.addFill(FillOptions(
315
geometry: geometry,
316
fillColor: "#FF0000",
317
fillOutlineColor: "#FF0000",
318
fillOpacity: 0.6,
319
));
320
setState(() {
321
_selectedFill = fill;
322
});
323
}
324
}
325
326
@override
327
Widget build(BuildContext context) {
328
final NBMap nbMap = NBMap(
329
onMapCreated: onMapCreated,
330
initialCameraPosition: _kInitialPosition,
331
trackCameraPosition: true,
332
compassEnabled: _compassEnabled,
333
cameraTargetBounds: _cameraTargetBounds,
334
minMaxZoomPreference: _minMaxZoomPreference,
335
styleString: _styleStrings[_styleStringIndex],
336
rotateGesturesEnabled: _rotateGesturesEnabled,
337
scrollGesturesEnabled: _scrollGesturesEnabled,
338
tiltGesturesEnabled: _tiltGesturesEnabled,
339
zoomGesturesEnabled: _zoomGesturesEnabled,
340
doubleClickZoomEnabled: _doubleClickToZoomEnabled,
341
myLocationEnabled: _myLocationEnabled,
342
myLocationTrackingMode: _myLocationTrackingMode,
343
myLocationRenderMode: MyLocationRenderMode.GPS,
344
onMapClick: (point, latLng) async {
345
print(
346
"Map click: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}");
347
print("Filter $_featureQueryFilter");
348
List features = await mapController!
349
.queryRenderedFeatures(point, ["landuse"], _featureQueryFilter);
350
print('# features: ${features.length}');
351
_clearFill();
352
if (features.isEmpty && _featureQueryFilter != null) {
353
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
354
content: Text('QueryRenderedFeatures: No features found!')));
355
} else if (features.isNotEmpty) {
356
_drawFill(features);
357
}
358
},
359
onMapLongClick: (point, latLng) async {
360
print(
361
"Map long press: ${point.x},${point.y} ${latLng.latitude}/${latLng.longitude}");
362
Point convertedPoint = await mapController!.toScreenLocation(latLng);
363
LatLng convertedLatLng = await mapController!.toLatLng(point);
364
print(
365
"Map long press converted: ${convertedPoint.x},${convertedPoint.y} ${convertedLatLng.latitude}/${convertedLatLng.longitude}");
366
double metersPerPixel =
367
await mapController!.getMetersPerPixelAtLatitude(latLng.latitude);
368
369
print(
370
"Map long press The distance measured in meters at latitude ${latLng.latitude} is $metersPerPixel m");
371
372
List features =
373
await mapController!.queryRenderedFeatures(point, [], null);
374
if (features.length > 0) {
375
print(features[0]);
376
}
377
},
378
onCameraTrackingDismissed: () {
379
this.setState(() {
380
_myLocationTrackingMode = MyLocationTrackingMode.None;
381
});
382
},
383
onUserLocationUpdated: (location) {
384
print(
385
"new location: ${location.position}, alt.: ${location.altitude}, bearing: ${location.bearing}, speed: ${location.speed}, horiz. accuracy: ${location.horizontalAccuracy}, vert. accuracy: ${location.verticalAccuracy}");
386
},
387
);
388
389
final List<Widget> listViewChildren = <Widget>[];
390
391
if (mapController != null) {
392
listViewChildren.addAll(
393
<Widget>[
394
Text('camera bearing: ${_position.bearing}'),
395
Text('camera target: ${_position.target.latitude.toStringAsFixed(4)},'
396
'${_position.target.longitude.toStringAsFixed(4)}'),
397
Text('camera zoom: ${_position.zoom}'),
398
Text('camera tilt: ${_position.tilt}'),
399
Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
400
_mapSizeToggler(),
401
_queryFilterToggler(),
402
_compassToggler(),
403
_myLocationTrackingModeCycler(),
404
_latLngBoundsToggler(),
405
_setStyleToSatellite(),
406
_zoomBoundsToggler(),
407
_rotateToggler(),
408
_scrollToggler(),
409
_doubleClickToZoomToggler(),
410
_tiltToggler(),
411
_zoomToggler(),
412
_myLocationToggler(),
413
_telemetryToggler(),
414
_visibleRegionGetter(),
415
],
416
);
417
}
418
return Column(
419
mainAxisSize: MainAxisSize.min,
420
children: [
421
Center(
422
child: SizedBox(
423
width: _mapExpanded ? null : 300.0,
424
height: 200.0,
425
child: nbMap,
426
),
427
),
428
Expanded(
429
child: ListView(
430
children: listViewChildren,
431
),
432
)
433
],
434
);
435
}
436
437
void onMapCreated(NextbillionMapController controller) {
438
mapController = controller;
439
mapController!.addListener(_onMapChanged);
440
_extractMapInfo();
441
442
mapController!.getTelemetryEnabled().then((isEnabled) => setState(() {
443
_telemetryEnabled = isEnabled;
444
}));
445
}
446
}

Code summary

The above code snippet defines a MapUiBody widget that displays a map using the NBMap widget from the nb_maps_flutter package. The widget allows users to interact with the map and control various aspects of its user interface.

  1. Display MapView Widget:

    • The NBMap widget is used to display the map on the screen.
    • The onMapCreated callback is used to get access to the NextbillionMapController, which allows interaction with the map.
    • The initialCameraPosition property sets the initial position of the camera when the map is first displayed.
  2. User Interface on MapView: The MapUiBody widget provides several buttons and toggles that allow users to interact with the map and customize its behavior:

    • Camera Information: Displays the current camera bearing, target latitude and longitude, zoom level, and tilt. Also shows whether the camera is moving or idle.
    • Map Size Toggler: Toggles between expanding and shrinking the map view.
    • Query Filter Toggler: Toggles the filter for querying features on the map.
    • Compass Toggler: Toggles the compass display on the map.
    • My Location Tracking Mode Cycler: Cycles through different tracking modes for user location.
    • Camera Target Bounds Toggler: Toggles between bounding and releasing the camera target within specific bounds.
    • Set Style To Satellite: Cycles through different map styles (NBMAP_STREETS, SATELLITE, LOCAL_ASSET).
    • Zoom Bounds Toggler: Toggles between bounding and releasing the zoom level within specific bounds.
    • Rotate Toggler: Toggles the rotate gesture on the map.
    • Scroll Toggler: Toggles the scroll gesture on the map.
    • Double Click To Zoom Toggler: Toggles the double click to zoom gesture on the map.
    • Tilt Toggler: Toggles the tilt gesture on the map.
    • Zoom Toggler: Toggles the zoom gesture on the map.
    • My Location Toggler: Toggles the display of the user's location on the map.
    • Telemetry Toggler: Toggles the telemetry tracking on the map.
    • Visible Region Getter: Retrieves the currently visible region on the map and displays it in a snackbar.

To use this widget in your Flutter app, make sure you have installed the nb_maps_flutter package and added the necessary dependencies to your pubspec.yaml file.

Overall, this code provides a comprehensive example of how to use the NBMap widget and interact with the map's user interface in a Flutter app using the NextbillionMap SDK.

© 2024 NextBillion.ai all rights reserved.