3
import 'package:flutter/material.dart';
4
import 'package:collection/collection.dart';
5
import 'package:nb_maps_flutter/nb_maps_flutter.dart';
10
final LatLngBounds sydneyBounds = LatLngBounds(
11
southwest: const LatLng(-34.022631, 150.620685),
12
northeast: const LatLng(-33.571835, 151.325952),
15
class MapUiPage extends ExamplePage {
16
MapUiPage() : super(const Icon(Icons.map), 'User interface');
19
Widget build(BuildContext context) {
20
return const MapUiBody();
24
class MapUiBody extends StatefulWidget {
28
State<StatefulWidget> createState() => MapUiBodyState();
31
class MapUiBodyState extends State<MapUiBody> {
34
static final CameraPosition _kInitialPosition = const CameraPosition(
35
target: LatLng(-33.852, 151.211),
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"
54
List<String> _styleStringLabels = [
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;
75
void _onMapChanged() {
81
void _extractMapInfo() {
82
final position = mapController!.cameraPosition;
83
if (position != null) _position = position;
84
_isMoving = mapController!.isCameraMoving;
89
mapController?.removeListener(_onMapChanged);
93
Widget _myLocationTrackingModeCycler() {
94
final MyLocationTrackingMode nextType = MyLocationTrackingMode.values[
95
(_myLocationTrackingMode.index + 1) %
96
MyLocationTrackingMode.values.length];
98
child: Text('change to $nextType'),
101
_myLocationTrackingMode = nextType;
107
Widget _queryFilterToggler() {
110
'filter zoo on click ${_featureQueryFilter == null ? 'disabled' : 'enabled'}'),
113
if (_featureQueryFilter == null) {
114
_featureQueryFilter = [
120
_featureQueryFilter = null;
127
Widget _mapSizeToggler() {
129
child: Text('${_mapExpanded ? 'shrink' : 'expand'} map'),
132
_mapExpanded = !_mapExpanded;
138
Widget _compassToggler() {
140
child: Text('${_compassEnabled ? 'disable' : 'enable'} compasss'),
143
_compassEnabled = !_compassEnabled;
149
Widget _latLngBoundsToggler() {
152
_cameraTargetBounds.bounds == null
153
? 'bound camera target'
154
: 'release camera target',
158
_cameraTargetBounds = _cameraTargetBounds.bounds == null
159
? CameraTargetBounds(sydneyBounds)
160
: CameraTargetBounds.unbounded;
166
Widget _zoomBoundsToggler() {
168
child: Text(_minMaxZoomPreference.minZoom == null
173
_minMaxZoomPreference = _minMaxZoomPreference.minZoom == null
174
? const MinMaxZoomPreference(12.0, 16.0)
175
: MinMaxZoomPreference.unbounded;
181
Widget _setStyleToSatellite() {
184
'change map style to ${_styleStringLabels[(_styleStringIndex + 1) % _styleStringLabels.length]}'),
187
_styleStringIndex = (_styleStringIndex + 1) % _styleStrings.length;
193
Widget _rotateToggler() {
195
child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
198
_rotateGesturesEnabled = !_rotateGesturesEnabled;
204
Widget _scrollToggler() {
206
child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
209
_scrollGesturesEnabled = !_scrollGesturesEnabled;
215
Widget _doubleClickToZoomToggler() {
216
final stateInfo = _doubleClickToZoomEnabled == null
218
: _doubleClickToZoomEnabled!
222
child: Text('$stateInfo double click to zoom'),
225
if (_doubleClickToZoomEnabled == null) {
226
_doubleClickToZoomEnabled = false;
227
} else if (!_doubleClickToZoomEnabled!) {
228
_doubleClickToZoomEnabled = true;
230
_doubleClickToZoomEnabled = null;
237
Widget _tiltToggler() {
239
child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
242
_tiltGesturesEnabled = !_tiltGesturesEnabled;
248
Widget _zoomToggler() {
250
child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
253
_zoomGesturesEnabled = !_zoomGesturesEnabled;
259
Widget _myLocationToggler() {
261
child: Text('${_myLocationEnabled ? 'disable' : 'enable'} my location'),
264
_myLocationEnabled = !_myLocationEnabled;
270
Widget _telemetryToggler() {
272
child: Text('${_telemetryEnabled ? 'disable' : 'enable'} telemetry'),
275
_telemetryEnabled = !_telemetryEnabled;
277
mapController?.setTelemetryEnabled(_telemetryEnabled);
282
Widget _visibleRegionGetter() {
284
child: Text('get currently visible region'),
285
onPressed: () async {
286
var result = await mapController!.getVisibleRegion();
287
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
289
"SW: ${result.southwest.toString()} NE: ${result.northeast.toString()}"),
296
if (_selectedFill != null) {
297
mapController!.removeFill(_selectedFill!);
299
_selectedFill = null;
304
_drawFill(List<dynamic> features) async {
305
Map<String, dynamic>? feature =
306
features.firstWhereOrNull((f) => f['geometry']['type'] == 'Polygon');
308
if (feature != null) {
309
List<List<LatLng>> geometry = feature['geometry']['coordinates']
311
(ll) => ll.map((l) => LatLng(l[1], l[0])).toList().cast<LatLng>())
313
.cast<List<LatLng>>();
314
Fill fill = await mapController!.addFill(FillOptions(
316
fillColor: "#FF0000",
317
fillOutlineColor: "#FF0000",
321
_selectedFill = fill;
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 {
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}');
352
if (features.isEmpty && _featureQueryFilter != null) {
353
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
354
content: Text('QueryRenderedFeatures: No features found!')));
355
} else if (features.isNotEmpty) {
359
onMapLongClick: (point, latLng) async {
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);
365
"Map long press converted: ${convertedPoint.x},${convertedPoint.y} ${convertedLatLng.latitude}/${convertedLatLng.longitude}");
366
double metersPerPixel =
367
await mapController!.getMetersPerPixelAtLatitude(latLng.latitude);
370
"Map long press The distance measured in meters at latitude ${latLng.latitude} is $metersPerPixel m");
373
await mapController!.queryRenderedFeatures(point, [], null);
374
if (features.length > 0) {
378
onCameraTrackingDismissed: () {
380
_myLocationTrackingMode = MyLocationTrackingMode.None;
383
onUserLocationUpdated: (location) {
385
"new location: ${location.position}, alt.: ${location.altitude}, bearing: ${location.bearing}, speed: ${location.speed}, horiz. accuracy: ${location.horizontalAccuracy}, vert. accuracy: ${location.verticalAccuracy}");
389
final List<Widget> listViewChildren = <Widget>[];
391
if (mapController != null) {
392
listViewChildren.addAll(
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)'),
401
_queryFilterToggler(),
403
_myLocationTrackingModeCycler(),
404
_latLngBoundsToggler(),
405
_setStyleToSatellite(),
406
_zoomBoundsToggler(),
409
_doubleClickToZoomToggler(),
412
_myLocationToggler(),
414
_visibleRegionGetter(),
419
mainAxisSize: MainAxisSize.min,
423
width: _mapExpanded ? null : 300.0,
430
children: listViewChildren,
437
void onMapCreated(NextbillionMapController controller) {
438
mapController = controller;
439
mapController!.addListener(_onMapChanged);
442
mapController!.getTelemetryEnabled().then((isEnabled) => setState(() {
443
_telemetryEnabled = isEnabled;