Flutter, shows realtime position in map, with object is facing forward following the direction.

Daniel H Prasetyo
8 min readDec 12, 2021

--

This article will discuss about several things that related with location and a map. First, we will obtain our current location, second, we will showing a map with our position as a center, and the third we will monitor a Taxi movement in the map.

Preparation
We will need a screen with stateful skeleton:
(This code use Null Safety)

import 'package:flutter/material.dart';class MyMap extends StatefulWidget {
const MyMap({Key? key}) : super(key: key);
@override
State<MyMap> createState() => _MyMapState();
}
class _MyMapState extends State<MyMap> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Map test"),),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const <Widget>[],),
);
}
}

GET LOCATION

Sometimes our app need a data of where our current location in the world. Usually in form of longitude (X) and latitude (Y). For example :
- App that can record location when taking a picture using camera.
- App that check our position to enable or disable some other features , for example to enabling button check-in when we are around a specific location.

Here, we will use Flutter Location Plugin to get our X,Y location. https://pub.dev/packages/location

Installation, in pubspec.yaml :
location: ^4.3.0

Using:
import ‘package:location/location.dart’;

We will store X,Y in two variable _xMarker and _yMarker , it initiate with some value for example 0. Also we prepare another x,y for map center (will be used later).

double _xCenter = 0; 
double _yCenter = 0;
double _xMarker = 0;
double _yMarker = 0;

and we will show the location in a simple Text widget:

Center(child: Text(_xMarker.toString() + " , " + _yMarker.toString()))

Above variables will be updated with a function, here is : getXY function, and the function will be called in the initState()

@override
void initState() {
super.initState();
_getXY();
}

and here is the _getXY() function:

void _getXY() async {
Location location = new Location();
bool _serviceEnabled;
PermissionStatus _permissionGranted;
LocationData _locationData;
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return;
}
}
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return;
}
}
_locationData = await location.getLocation();
setState(() {
_yMarker = _locationData.latitude!;
_xMarker = _locationData.longitude!;
});
}

Result:

DISPLAYING MAP

Some applications shows a map for specific purpose, for example to show some of thematic data location such as stores, hotel, tourist attraction, etc , and sometime also the user current location. There are several plugin for showing map. Here we will use flutter_map plugin.
https://pub.dev/packages/flutter_map

Installation, add to pubspec.yaml:

flutter_map: ^0.14.0
latlong2: ^0.8.1

and import in the screen that use it

import "package:flutter_map/flutter_map.dart";
import "package:latlong2/latlong.dart";

Add a new function that display a Map with a marker as a center that taken from our current position which already obtained in previous section.
To re-centering map, it will need a controller. The map will have 2 layers, the first layer is OpenStreetMap as a base layer, and the second is a point marker layer represented in an heart icon for showing our location.

MapController mapCon = MapController();Widget buildMap() {
return FlutterMap(
mapController: mapCon,
options: MapOptions( center: LatLng(_y, _x), zoom: 16.0,),
layers: [
TileLayerOptions(urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",subdomains: ['a', 'b', 'c'],),
MarkerLayerOptions(
markers: [
Marker(width: 80.0,height: 80.0,
point: LatLng(_y, _x),
builder: (ctx) => const Icon(Icons.favorite,
color: Colors.pink,size: 24.0,),
),
],
),
],
);
}

Add the _buildMap widget function below the X,Y Text

children: <Widget>[
Center(child: Text(_xMarker.toString() + " , " + _yMarker.toString())),
SizedBox(height: 400, child: buildMap())
]

Set _xCenter and _yCenter with the same value with our current position, and then re-center the map using move method in the map controller . This code is placed in setState block when we got our X,Y position.

setState(() {
_yMarker = _locationData.latitude!;
_xMarker = _locationData.longitude!;
_yCenter = _locationData.latitude!;
_xCenter = _locationData.longitude!;
mapCon.move(LatLng(_yCenter, _xCenter), mapCon.zoom);
});

Result : (example in mine. will be different for each user location)

REALTIME POSITION

Still about map, several popular application nowadays have a screen that show a real time position of something time by time in the map. For example Grab or Uber or online taxi app that show us the location of the car when heading to our position or when it bring us to a certain destination.

We will try to show this kind of object, but with a dummy data. This data is represent a taxi position that moving time by time around Surabaya city hall. The web service can be access at:

https://ubaya.fun/flutter/daniel/taxi_xy.php

The output form is very simple JSON like this:

Before reading the web service, we will prepare the Timer first. The Timer will tick and read the web service in certain interval, for example in a second.

But before reading the web service we will simplified first by using the _xMarker value.

Create these two function, first function is the Timer, and the second is a function that call in the timer tick. If it in a second, this function will call every second. Later, this second function will be use for calling the web service (here is the dummy web service) and redraw the interface including the map and its objects. Therefore this second function use setState. In our first experiment, it just adding the _xMarker value with a little number (but in degree) to make our marker moving to the east every second.

startTimer() {
final _timer = Timer.periodic(const Duration(milliseconds: 1000),(timer) {
updateMap();
});
}
updateMap() {
setState(() {
_xMarker = _xMarker + 0.0005;
});
}

Call the timer in initState()

@override
void initState() {
super.initState();
_getXY();
startTimer();
}

Run and check the result. If the marker moving to the east every second, our timer things is ready to use.

In order to contact the web service and get its JSON response, we will need to add another plugin, Http plugin
https://pub.dev/packages/http

To install it, add to pubspec.yaml
http: ^0.13.4

and to use it:
import ‘package:http/http.dart’ as http;

In order to view the “taxi” movement, we need to make the map center around Surabaya City Hall . Update the _xCenter and _yCenter with this value:

Also set Zoom level lower.

options: MapOptions(
center: LatLng(_yCenter, _yCenter),
zoom: 15.0,
),

And the map center will no longer follows our current position anymore

setState(() {
_yMarker = _locationData.latitude!;
_xMarker = _locationData.longitude!;
//_yCenter = _locationData.latitude!;
//_xCenter = _locationData.longitude!;
mapCon.move(LatLng(_yCenter, _xCenter), mapCon.zoom);
});

We need to add new variables to store the Taxi position.

double _xTaxi = 0;
double _yTaxi = 0;

And we need a new Marker object in the map that use this position. We use a taxi image for showing the marker. You can download it from https://ubaya.fun/flutter/daniel/redcar.png or from another sources, and put it as image asset.

Marker(
point: LatLng(_yTaxi, _xTaxi),
builder: (ctx) => Image.asset("assets/images/redcar.png", width: 20),
),

Next, we update the updateMap function to call the webservice and then put the JSON value to _xTaxi and _yTaxi.

updateMap() async {
final response = await http.post(Uri.parse("https://ubaya.fun/flutter/daniel/taxi_xy.php"));
if (response.statusCode == 200) {
print(response.body);
Map json = jsonDecode(response.body);
setState(() {
_xTaxi = json['x'];
_yTaxi = json['y'];
});
}
}

The result : Taxi is moving time by time.. but the car position still facing to the north.

Refining Car Facing Direction

We will refine the taxi direction by rotating the asset image base on it previous location and current location. We will need 3 new variables:

double _xTaxiPrev = 0;
double _yTaxiPrev = 0;
double _angleTaxi = 0;

For rotating image we use Transform.rotate widget which wrap our previous Image.assets

builder: (ctx) => Transform.rotate(
angle: _angleTaxi,
child: Image.asset("assets/images/redcar.png",width: 20,)),

We put the calculation after reading web service for the new Taxi location

setState(() {
_xTaxiPrev = _xTaxi;
_yTaxiPrev = _yTaxi;
_xTaxi = json['x'];
_yTaxi = json['y'];
double _temp = Math.atan((_xTaxi - _xTaxiPrev) / (_yTaxi - _yTaxiPrev) );
if (_yTaxi < _yTaxiPrev) _temp = Math.pi + _temp;
if (!_temp.isNaN) _angleTaxi = _temp;
});

Result: Now our taxi have a normal facing direction

The complete final source code is here :

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:location/location.dart';
import 'package:flutter_map/flutter_map.dart';
import 'package:latlong2/latlong.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:math' as Math;
class MyMap extends StatefulWidget {
const MyMap({Key? key}) : super(key: key);
@override
State<MyMap> createState() => _MyMapState();
}
class _MyMapState extends State<MyMap> {
double _xCenter = 112.74757902;
double _yCenter = -7.26093038;
double _xMarker = 0;
double _yMarker = 0;
double _xTaxi = 0;
double _yTaxi = 0;
double _xTaxiPrev = 0;
double _yTaxiPrev = 0;
double _angleTaxi = 0;
MapController mapCon = MapController();
void _getXY() async {
Location location = new Location();
bool _serviceEnabled;
PermissionStatus _permissionGranted;
LocationData _locationData;
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
if (!_serviceEnabled) {
return;
}
}
_permissionGranted = await location.hasPermission();
if (_permissionGranted == PermissionStatus.denied) {
_permissionGranted = await location.requestPermission();
if (_permissionGranted != PermissionStatus.granted) {
return;
}
}
_locationData = await location.getLocation();
setState(() {
_yMarker = _locationData.latitude!;
_xMarker = _locationData.longitude!;
//_yCenter = _locationData.latitude!;
//_xCenter = _locationData.longitude!;
mapCon.move(LatLng(_yCenter, _xCenter), mapCon.zoom);
});
} Widget buildMap() {
return FlutterMap(
mapController: mapCon,
options: MapOptions( center: LatLng(_y, _x), zoom: 16.0,),
layers: [
TileLayerOptions(urlTemplate: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",subdomains: ['a', 'b', 'c'],),
MarkerLayerOptions(
markers: [
Marker(width: 80.0,height: 80.0,
point: LatLng(_y, _x),
builder: (ctx) => const Icon(Icons.favorite,
color: Colors.pink,size: 24.0,),
),
Marker(
point: LatLng(_yTaxi, _xTaxi),
builder: (ctx) => Image.asset("assets/images/redcar.png", width: 20),
),
],
),
],
);
}
startTimer() {
final _timer = Timer.periodic(const Duration(milliseconds: 1000),(timer) {
updateMap();
});
}
updateMap() async {
final response = await http.post(Uri.parse("https://ubaya.fun/flutter/daniel/taxi_xy.php"));
if (response.statusCode == 200) {
Map json = jsonDecode(response.body);
setState(() {
_xTaxiPrev = _xTaxi;
_yTaxiPrev = _yTaxi;
_xTaxi = json['x'];
_yTaxi = json['y'];double _temp = Math.atan((_xTaxi - _xTaxiPrev) / (_yTaxi - _yTaxiPrev));
if (_yTaxi < _yTaxiPrev) _temp = Math.pi + _temp;
if (!_temp.isNaN) _angleTaxi = _temp;
});
}
}
@override
void initState() {
super.initState();
_getXY();
startTimer();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Map test"),
),
body: Column(mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text(_xMarker.toString() + " , " + _yMarker.toString())),
SizedBox(height: 500, child: buildMap())
],
),
);
}
}

--

--

Daniel H Prasetyo
Daniel H Prasetyo

Written by Daniel H Prasetyo

A full stack developer and GIS enthusiast with 20+ years of experiences (desktop, web, app) and 15+ years of GIS programming, data management and analyst.

No responses yet