Lazy loading Google-Maps Markers with firebase - flutter

In my app, people can put markers on a map (using google_maps_flutter). These markers are public. When other people open the map, they can see the markers too.
The problem is, this will overtime cause lots of reads, because when you open the map, my app will read every marker from the Firestore database. So I am trying to implement some lazy loading system.
However this has proven to be more difficult than I thought.
This is what I have:
// get all markers within a certain radius
Future<Set<Marker>> getMarkersWithinRadius(LatLng center, double radius) async {
final Set<Marker> markers = {};
// only get markers within a certain radius
final QuerySnapshot<Map<String, dynamic>> querySnapshot = await markersFirestoreReference
.where('latitude', isGreaterThanOrEqualTo: center.latitude - radius)
.where('latitude', isLessThanOrEqualTo: center.latitude + radius)
.where('longitude', isGreaterThanOrEqualTo: center.longitude - radius)
.where('longitude', isLessThanOrEqualTo: center.longitude + radius)
.get();
// loop through all docs and add them to the markers set
for (final QueryDocumentSnapshot<Map<String, dynamic>> doc in querySnapshot.docs) {
final MarkerId markerId = MarkerId(doc.data()['markerId']);
final Marker marker = Marker(
markerId: markerId,
position: LatLng(
doc.data()['latitude'],
doc.data()['longitude'],
),
infoWindow: InfoWindow(title: markerId.value.toString(), snippet: '*'),
onTap: () {
//Navigator.pushNamed(context, '/markerView',
// arguments: markerId.value);
},
);
markers.add(marker);
}
return markers;
}
This does not work because apparently Firebase doesn't currently support querying multiple fields in a single request.

What you are interested in doing is using a spatial index to query firestore. You would want to store data using geofire. Geofire is a way to deterministically determine an index value for your data and then query upon that index by dividing up the globe into several smaller grids. You can then use geoqueries to query within a certain location and then retrieve realtime updates about that location. This does radius queries from a central location and my recommendation would be that as your map scale changes, update the radius of those queries to be larger than your current mapview. Additionally, while this spatial indexing does work well for knn queries in normal locations, as you move towards the poles, there are issues with adjusting for locations that are closer by traversing the poles.
Extra documents:
GeoFire android documentation

Related

How to use Google Maps directions API in Flutter (is it a good practice to keep calling directions API while current location is changing on the map)

In Flutter I use
google_maps_flutter, google_directions_api and flutter_polyline_points packages to have a map with the following functionalities;
Drawing routes between current location and destination points, get distance and durations between them, driver must be notified to take left/right while driving to the destination.
I have done these all, but when current location keeps updating on the map I'm calling direction api which return lots of data like legs and steps this api call is almost every second and google will charge me a lot.
Is anybody faced to same issue, I really appreciate a help.
There is part of my codes I have done so far
void _getCurrentLocation(context) {
showGeneralDialog(
context: context,
barrierDismissible: false,
barrierColor: Colors.black45,
pageBuilder: (BuildContext buildContext, Animation animation,
Animation secondaryAnimation) {
return Center(
child: Container(
width: MediaQuery.of(context).size.width - 10,
height: MediaQuery.of(context).size.height - 80,
padding: EdgeInsets.all(20),
color: Tingsapp.transparent,
child: CurrentLocation(),
),
);
}).then((location) {
if (location != null) {
_addMoverMarker(location, 'mover');
//updateFirebase(location);
_animateCameraToCurrentLocation(location);
}
});
}
destination point are already set.
In the above code I get user current location and add a marker as bellow
void _addMoverMarker(newLocationData, String id) async {
Uint8List imageData = await getMarker();
//LatLng latlng = LatLng(newLocationData.latitude, newLocationData.longitude);
LatLng latlng = LatLng(_moverLatitude!, _moverLongitude!);
MarkerId markerId = MarkerId(id);
Marker marker = Marker(
markerId: markerId,
position: latlng,
zIndex: 2,
icon: BitmapDescriptor.fromBytes(imageData),
infoWindow: InfoWindow(
title: "Mover location",
),
);
markers[markerId] = marker;
circle = Circle(
circleId: CircleId("circle"),
radius: 20,
zIndex: 1,
center: latlng,
strokeColor: Colors.orange.withAlpha(60),
fillColor: Colors.orange.withAlpha(300),
);
_getMoverPolyline(newLocationData);
}
and here I animate the camera
_animateCameraToCurrentLocation(newLocationData) {
if (_locationSubscription != null) {
_locationSubscription!.cancel();
}
_locationSubscription =
_locationTracker.onLocationChanged.listen((newLocationData) {
if (_mapController != null) {
_addMoverMarker(newLocationData, 'mover');
_animateCamera(_moverLatitude, _moverLongitude, 16.0);
//updateFirebase(newLocationData);
}
});
}
when I draw the polyline I call directions api here my problem starts
_getMoverPolyline(LocationData locationData) async {
PolylineResult result = await polylinePoints.getRouteBetweenCoordinates(
mapKey,
PointLatLng(_originLatitude!, _originLongitude!),
PointLatLng(_moverLatitude!, _moverLongitude!),
//PointLatLng(locationData.latitude!, locationData.longitude!),
travelMode: TravelMode.driving,
);
if (result.points.isNotEmpty) {
moverPolylineCoordinates = [];
result.points.forEach((PointLatLng point) {
moverPolylineCoordinates.add(LatLng(point.latitude, point.longitude));
});
}
_addMoverPolyLine();
_getDirections(_moverLatitude, _moverLongitude, _originLatitude,
_originLongitude).then((data){
_updateData(data);
});
}
_getDirections(_moverLatitude, _moverLongitude, _originLatitude, _originLongitude) async {
Api api = Api();
var res = await api.getDirections(
_moverLatitude, _moverLongitude, _originLatitude, _originLongitude);
var jsonData = jsonDecode(res.body);
print(jsonData['routes'][0]['legs'][0]);
return jsonData['routes'][0]['legs'][0];
}
In the above code _getDirections method gets calling every second.
Isn't possible to call directions api one time?
_updateData method update data like tern right/left or Head south on my map
No I don't think that it's good practice to keep calling the API. And even in the google_directions_api documentation, they say,
Note: This service is not designed to respond in real time to user input.
And to answer your main question..
Don't call the API every time the location changes, instead call it once using the current location. And the response contains everything you need to navigate the user. Check the maneuver key inside each step of a leg.
And you should only use the location subscription to update the current location and animate the camera on the map. Use the getLocation() method to get the current location before starting the trip and use that location to generate the route. Store that route in a variable and use the data inside the route response to display whatever you want.
It´s not a good practice to constantly call directionsAPI, for the good of your pocket and to follow the recommendations of google.
If you are creating a Navigation app you first need to call the Api when the user set a destination, capture de coordinates (overview_polyline) and after that if the user goes off the road you can use maps toolkit to determine when this occurs and after that call again directionsAPI.

Remove google's markers from google_maps_flutter

Objective:
To be able to show custom markers from dev only and disable google maps' default markers.
Description:
I am trying to put markers in GoogleMap from google_maps_flutter plugin but Google already has its own markers so it is getting in the way of the markers that I am trying to add. Is there any way to just show the map and add user-defined markers only? If not is it possible to minimize the number of markers shown by default map?
Just looked around and found some possible fix.
Seems like we can generate map style from this website:
Styling Wizard.
From there I toned down landmarks and then I was able to remove markers using this:
final String mapStyle =
await rootBundle.loadString('assets/map/map_style.txt');
//Set it on mapcontroller after map is created.
onMapCreated: (GoogleMapController controller) {
if (_controller.isCompleted) {
return;
}
controller.setMapStyle(mapStyle);
_controller.complete(controller);
},
// create a function to create custom marker
Future<BitmapDescriptor> createCustomMarkerBitmap() async {
Uint8List? data = await getBytesFromAsset("assets/icons/map_marker.png", 100);
return BitmapDescriptor.fromBytes(data!);
}
// then call the function to create a custom marker.
BitmapDescriptor? _marker = await createCustomMarkerBitmap();
final Marker marker = Marker(
markerId: _markerId,
position: _position,
icon: _marker, // use the marker
infoWindow: _infoWindow,
onTap: () {},
);

How to get LatLng position by clicking the maps in flutter

I am using google_maps_flutter to show the maps of user location, I come up with an idea where user can click anything in maps and show the the coordinates they clicked. What I mean with the coordinates here is Lat and Lng position that the click from the maps. Is that possible to do that ? and are there some articles as guide for me to do that ?
use onTap callback of GoogleMap widget like below to get coordinates from map where use clicks.
GoogleMap(
onTap: (LatLng latLng) {
final lat = latLng.latitude;
final long = latLng.longitude;
},
);

Retrieve Data from Firestore and Pass it to a variable. Flutter

Hi i would like to ask how can I retrieve the data from the Firestore and send it to a double.
This is the code where I retrieve the data from Firestore.
Firestore.instance
.collection('locations')
.snapshots()
.listen((driverLocation){
driverLocation.documents.forEach((dLocation){
dLocation.data['Latitude'] = latitude;
dLocation.data['Longitude'] = longitude;
print(latitude);
});
});
I store it inside the dLocation and when i print(dLocation.data) it will display the latitude and longitude in the Firestore. But when i pass it to the double latitude and double longitude it returns null.
busStop.add(
Marker(
markerId: MarkerId('driverLocation')
,
draggable: false,
icon: BitmapDescriptor.defaultMarker,
onTap: () {
},
position: LatLng(latitude, longitude),
));
Then i would like to pass the data that is in the double latitude and double longitude into the marker so that the marker will move accordingly to the latitude and longitude in the Firestore.
Everything that is happening here is in a initState().
**If theres anything you would want to ask please feel free to do so as i do not have any idea on how to convey my question. Thank you so much in advance.
You're doing it in the wrong way. Right now you are assigning the value of latitude (which is null) to the value of dLocation.data['latitude']. What you want to do is this:
latitude = dLocation.data['latitude'];
longitude = dLocation.data['longitude'];
with this change, the value of dLocation.data['latitude'] will be assigned to latitude and the value of dLocation.data['longitude'] will be assigned to longitude variable
Update:
To get new markers and show them on the screen with latitude and longitude values, you can do something like this:
#override
void initState(){
Firestore.instance
.collection('locations')
.snapshots()
.listen((driverLocation){
//busStop = []; removes all the old markers and you don't get duplicate markers with different coordinates
driverLocation.documents.forEach((dLocation){
dLocation.data['Latitude'] = latitude;
dLocation.data['Longitude'] = longitude;
busStop.add(
Marker(
markerId: MarkerId('driverLocation')
,
draggable: false,
icon: BitmapDescriptor.defaultMarker,
onTap: () {
},
position: LatLng(latitude, longitude),
)); //you need to check for duplicate markers here. you can do it by giving each marker an unique id and check for the same marker in the list so you don't get duplicate markers.
});
setState((){}); //rebuilds the widget tree after adding the markers to the busStop list
});
}
What's happening here is you add the markers to the busStop list and after adding all the markers, you call setState and the widget tree rebuilds the screen with the latest data. You might need to check for duplicate markers because they might be re-added to the busStop list. Or you can simply remove all the old markers and add the new ones by using busStop = []; before adding to busStop

Flutter maps - check if location is within current bounds

In Flutter Google Maps, how can I check if location is within bounds of the current maps view? Other libs provide such functions, like leaflet Checking if marker coordinates are in bounds
You can use getVisibleRegion() of the GoogleMapController to get the LatLngBounds and then use the method contains of that LatLngBounds.
Future<bool> _checkIfWithinBounds() async {
GoogleMapController mapController;
var mapBounds = await mapController.getVisibleRegion();
return mapBounds.contains(LatLng(yourLocation.lat, yourLocation.lng ),);
}