Location
An app using maps usually tracks a user’s location and displays it on the map. The NAVER Maps SDK helps you easily implement such features by providing the location overlay and location tracking feature. You can also implement your own location based features, without using the embedded location tracking feature.
Location overlay
It is recommended to use a location overlay when you need to display a user’s location on the map. Only one location overlay exists on a map, and it supports location specific characteristics, such as coordinates and directions. For more information, refer to Location overlay.
Embedded location tracking feature
The NAVER Maps SDK supports the location tracking feature which receives a user location event, displays the location on the map and moves the camera.
Permissions and LocationSource
Since the NAVER Maps SDK basically does not use user’s location information, it does not request location-related permissions from users. Therefore, for apps using the location tracking feature, you should specify ACCESS_COARSE_LOCATION
or ACCESS_FINE_LOCATION
permission in AndroidManifest.xml
.
The following code example specifies the ACCESS_FINE_LOCATION
permission in AndroidManifext.xml
.
<manifest>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest>
In addition, you should call setLocationSource()
to set a LocationSource
implementation. LocationSource
is an interface that provides location to the NAVER Maps SDK. As the LocationSource
’s methods including activate()
and deactivate()
are called by the map object, do not directly call it.
FusedLocationSource
The NAVER Maps SDK provides FusedLocationSource
, an implementation that returns the best location by using FusedLocationProviderClient
of Google Play Service, terrestrial magnetism and acceleration sensors. To use FusedLocationSource
, you should add dependencies for play-services-location
to build.gradle
of your app module.
dependencies {
implementation 'com.google.android.gms:play-services-location:21.0.1'
}
Groovy
dependencies {
implementation 'com.google.android.gms:play-services-location:21.0.1'
}
Kotlin
dependencies {
implementation("com.google.android.gms:play-services-location:21.0.1")
}
FusedLocationSource
requires an activity or fragment to process permissions in runtime. You should pass an activity or fragment object to the constructor and specify a permission request code. And then, you should pass the result of onRequestPermissionResult()
to onRequestPermissionsResult()
of FusedLocationSource
.
The following code example creates a FusedLocationSource
in an activity and set it to NaverMap
.
public class LocationTrackingActivity extends AppCompatActivity
implements OnMapReadyCallback {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
private FusedLocationSource locationSource;
private NaverMap naverMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
locationSource =
new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (locationSource.onRequestPermissionsResult(
requestCode, permissions, grantResults)) {
if (!locationSource.isActivated()) { // Permission denied
naverMap.setLocationTrackingMode(LocationTrackingMode.None);
}
return;
}
super.onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
@Override
public void onMapReady(@NonNull NaverMap naverMap) {
this.naverMap = naverMap;
naverMap.setLocationSource(locationSource);
}
}
Java
public class LocationTrackingActivity extends AppCompatActivity
implements OnMapReadyCallback {
private static final int LOCATION_PERMISSION_REQUEST_CODE = 1000;
private FusedLocationSource locationSource;
private NaverMap naverMap;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
locationSource =
new FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (locationSource.onRequestPermissionsResult(
requestCode, permissions, grantResults)) {
if (!locationSource.isActivated()) { // Permission denied
naverMap.setLocationTrackingMode(LocationTrackingMode.None);
}
return;
}
super.onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
@Override
public void onMapReady(@NonNull NaverMap naverMap) {
this.naverMap = naverMap;
naverMap.setLocationSource(locationSource);
}
}
Kotlin
class LocationTrackingActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var locationSource: FusedLocationSource
private lateinit var naverMap: NaverMap
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
locationSource =
FusedLocationSource(this, LOCATION_PERMISSION_REQUEST_CODE)
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
if (locationSource.onRequestPermissionsResult(requestCode, permissions,
grantResults)) {
if (!locationSource.isActivated) { // Permission denied
naverMap.locationTrackingMode = LocationTrackingMode.None
}
return
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onMapReady(naverMap: NaverMap) {
this.naverMap = naverMap
naverMap.locationSource = locationSource
}
companion object {
private const val LOCATION_PERMISSION_REQUEST_CODE = 1000
}
}
Location tracking mode
You can set LocationSource
to NaverMap
to use the location tracking feature. There are two ways to use the location tracking feature:
- Enable location tracking mode: Call
setLocationTrackingMode()
to programmatically enable the location tracking mode. - Enable current location button control: Use
UiSettings.setLocationButtonEnabled(true)
to enable the current location button control, and the location tracking mode is enabled or disabled by a user’s click on the button.
The location tracking mode is subdivided into the following four modes, which are defined in the LocationTrackingMode
enum.
None
: Location is not tracked.NoFollow
: Location tracking is enabled, and the current location overlay moves following a user’s location. However, the map does not move.
Follow
: Location tracking is enabled, and the current location overlay and the camera’s coordinates move following a user’s location. If the camera moves according to an API call or a user’s gestures, the mode turns intoNoFollow
.
Face
: Location tracking is enabled, and the current location overlay, the camera’s coordinates and bearing move following a user’s location and direction. If the camera moves according to an API call or a user’s gestures, the mode turns intoNoFollow
.
The following code example sets the location tracking mode to Follow
by calling setLocationTrackingMode()
.
naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);
Java
naverMap.setLocationTrackingMode(LocationTrackingMode.Follow);
Kotlin
naverMap.locationTrackingMode = LocationTrackingMode.Follow
Location change event
Add OnLocationChangeListener
with the addOnLocationChangeListener()
method to receive an event for location changes. If the location tracking mode is enabled and a user’s location changes, the onLocationChange()
callback method is called with the user’s location passed as a parameter.
The following code example displays a user’s location coordinates as a toast message when the location changes.
naverMap.addOnLocationChangeListener(location ->
Toast.makeText(this,
location.getLatitude() + ", " + location.getLongitude(),
Toast.LENGTH_SHORT).show());
Java
naverMap.addOnLocationChangeListener(location ->
Toast.makeText(this,
location.getLatitude() + ", " + location.getLongitude(),
Toast.LENGTH_SHORT).show());
Kotlin
naverMap.addOnLocationChangeListener { location ->
Toast.makeText(this, "${location.latitude}, ${location.longitude}",
Toast.LENGTH_SHORT).show()
}
Custom implementation
If it is hard to use the LocationSource
or the location tracking feature supported by the NAVER Maps SDK, you can implement your own location tracking feature.
Custom location source
If you use the embedded location tracking mode, it is recommended to use FusedLocationSource
which supports sensors and the well-designed permission handling feature. If needed, however, you can implement LocationSource
on your own.
Methods of LocationSource
are called by the NAVER Maps SDK. The activate()
method is called if the location tracking mode is enabled, and the deactivate()
method is called if disabled. Therefore, the implementation of LocationSource
should start tracking a user’s location when the activate()
is called, and stop tracking it when the deactivate()
is called. In addition, while the location tracking is enabled, the LocationSource.OnLocationChangedListener
object’s onLocationChanged()
passed to the activate()
should be called whenever the user’s location changes.
LocationSource
is also responsible for handling location permissions. When the activate()
method is called, LocationSource
should check and request permissions.
The following code example shows an implementation of LocationSource
that receives GPS locations by using LocationManager
.
public class GpsOnlyLocationSource implements LocationSource, LocationListener {
@NonNull
private final Context context;
@Nullable
private final LocationManager locationManager;
@Nullable
private LocationSource.OnLocationChangedListener listener;
public GpsOnlyLocationSource(@NonNull Context context) {
this.context = context;
locationManager =
(LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
}
@Override
public void activate(
@NonNull LocationSource.OnLocationChangedListener listener) {
if (locationManager == null) {
return;
}
if (PermissionChecker.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
&& PermissionChecker.checkSelfPermission(context,
Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Permission request logic omitted.
return;
}
this.listener = listener;
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this);
}
@Override
public void deactivate() {
if (locationManager == null) {
return;
}
listener = null;
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(Location location) {
if (listener != null) {
listener.onLocationChanged(location);
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
Java
public class GpsOnlyLocationSource implements LocationSource, LocationListener {
@NonNull
private final Context context;
@Nullable
private final LocationManager locationManager;
@Nullable
private LocationSource.OnLocationChangedListener listener;
public GpsOnlyLocationSource(@NonNull Context context) {
this.context = context;
locationManager =
(LocationManager)context.getSystemService(Context.LOCATION_SERVICE);
}
@Override
public void activate(
@NonNull LocationSource.OnLocationChangedListener listener) {
if (locationManager == null) {
return;
}
if (PermissionChecker.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
&& PermissionChecker.checkSelfPermission(context,
Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Permission request logic omitted.
return;
}
this.listener = listener;
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this);
}
@Override
public void deactivate() {
if (locationManager == null) {
return;
}
listener = null;
locationManager.removeUpdates(this);
}
@Override
public void onLocationChanged(Location location) {
if (listener != null) {
listener.onLocationChanged(location);
}
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
}
Kotlin
class GpsOnlyLocationSource(
private val context: Context) : LocationSource, LocationListener {
private val locationManager = context
.getSystemService(Context.LOCATION_SERVICE) as LocationManager?
private var listener: LocationSource.OnLocationChangedListener? = null
override fun activate(listener: LocationSource.OnLocationChangedListener) {
if (locationManager == null) {
return
}
if (PermissionChecker.checkSelfPermission(context,
Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED
&& PermissionChecker.checkSelfPermission(context,
Manifest.permission.ACCESS_COARSE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// Permission request logic omitted.
return
}
this.listener = listener
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10f, this)
}
override fun deactivate() {
if (locationManager == null) {
return
}
listener = null
locationManager.removeUpdates(this)
}
override fun onLocationChanged(location: Location) {
listener?.onLocationChanged(location)
}
override fun onStatusChanged(provider: String, status: Int,
extras: Bundle) {
}
override fun onProviderEnabled(provider: String) {
}
override fun onProviderDisabled(provider: String) {
}
}
Custom location tracking
You do not need to use the embedded location tracking feature when displaying a user’s location on the map. You can customize the feature as you need, by specifying the LocationOverlay
’s position
and bearing
to move the camera after directly receiving location events in your app.
The following code example receives GPS locations by using LocationManager
in an activity and moves the LocationOverlay
and the camera.
public class CustomLocationTrackingActivity extends AppCompatActivity
implements LocationListener {
private static final int PERMISSION_REQUEST_CODE = 100;
private static final String[] PERMISSIONS = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
};
private NaverMap map;
@Nullable
private LocationManager locationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
FragmentManager fm = getSupportFragmentManager();
MapFragment mapFragment = (MapFragment)fm.findFragmentById(R.id.map);
if (mapFragment == null) {
mapFragment = MapFragment.newInstance();
fm.beginTransaction().add(R.id.map, mapFragment).commit();
}
mapFragment.getMapAsync(naverMap -> map = naverMap);
locationManager = (LocationManager)getSystemService(LOCATION_SERVICE);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (hasPermission() && locationManager != null) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this);
}
return;
}
super.onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
@Override
protected void onStart() {
super.onStart();
if (hasPermission()) {
if (locationManager != null) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this);
}
} else {
ActivityCompat.requestPermissions(
this, PERMISSIONS, PERMISSION_REQUEST_CODE);
}
}
@Override
protected void onStop() {
super.onStop();
if (locationManager != null) {
locationManager.removeUpdates(this);
}
}
@Override
public void onLocationChanged(Location location) {
if (map == null || location == null) {
return;
}
LatLng coord = new LatLng(location);
LocationOverlay locationOverlay = map.getLocationOverlay();
locationOverlay.setVisible(true);
locationOverlay.setPosition(coord);
locationOverlay.setBearing(location.getBearing());
map.moveCamera(CameraUpdate.scrollTo(coord));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
private boolean hasPermission() {
return PermissionChecker.checkSelfPermission(this, PERMISSIONS[0])
== PermissionChecker.PERMISSION_GRANTED
&& PermissionChecker.checkSelfPermission(this, PERMISSIONS[1])
== PermissionChecker.PERMISSION_GRANTED;
}
}
Java
public class CustomLocationTrackingActivity extends AppCompatActivity
implements LocationListener {
private static final int PERMISSION_REQUEST_CODE = 100;
private static final String[] PERMISSIONS = {
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
};
private NaverMap map;
@Nullable
private LocationManager locationManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ...
FragmentManager fm = getSupportFragmentManager();
MapFragment mapFragment = (MapFragment)fm.findFragmentById(R.id.map);
if (mapFragment == null) {
mapFragment = MapFragment.newInstance();
fm.beginTransaction().add(R.id.map, mapFragment).commit();
}
mapFragment.getMapAsync(naverMap -> map = naverMap);
locationManager = (LocationManager)getSystemService(LOCATION_SERVICE);
}
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (hasPermission() && locationManager != null) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this);
}
return;
}
super.onRequestPermissionsResult(
requestCode, permissions, grantResults);
}
@Override
protected void onStart() {
super.onStart();
if (hasPermission()) {
if (locationManager != null) {
locationManager.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10, this);
}
} else {
ActivityCompat.requestPermissions(
this, PERMISSIONS, PERMISSION_REQUEST_CODE);
}
}
@Override
protected void onStop() {
super.onStop();
if (locationManager != null) {
locationManager.removeUpdates(this);
}
}
@Override
public void onLocationChanged(Location location) {
if (map == null || location == null) {
return;
}
LatLng coord = new LatLng(location);
LocationOverlay locationOverlay = map.getLocationOverlay();
locationOverlay.setVisible(true);
locationOverlay.setPosition(coord);
locationOverlay.setBearing(location.getBearing());
map.moveCamera(CameraUpdate.scrollTo(coord));
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
}
@Override
public void onProviderDisabled(String provider) {
}
private boolean hasPermission() {
return PermissionChecker.checkSelfPermission(this, PERMISSIONS[0])
== PermissionChecker.PERMISSION_GRANTED
&& PermissionChecker.checkSelfPermission(this, PERMISSIONS[1])
== PermissionChecker.PERMISSION_GRANTED;
}
}
Kotlin
class CustomLocationTrackingActivity : AppCompatActivity(), LocationListener {
private var map: NaverMap? = null
private var locationManager: LocationManager? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// ...
val fm = supportFragmentManager
val mapFragment = fm.findFragmentById(R.id.map) as MapFragment?
?: MapFragment.newInstance().also {
fm.beginTransaction().add(R.id.map, it).commit()
}
mapFragment.getMapAsync { naverMap ->
map = naverMap
}
locationManager = getSystemService(LOCATION_SERVICE) as LocationManager?
}
override fun onRequestPermissionsResult(requestCode: Int,
permissions: Array<String>,
grantResults: IntArray) {
if (requestCode == PERMISSION_REQUEST_CODE) {
if (hasPermission()) {
locationManager?.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10f, this)
}
return
}
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
override fun onStart() {
super.onStart()
if (hasPermission()) {
locationManager?.requestLocationUpdates(
LocationManager.GPS_PROVIDER, 1000, 10f, this)
} else {
ActivityCompat.requestPermissions(
this, PERMISSIONS, PERMISSION_REQUEST_CODE)
}
}
override fun onStop() {
super.onStop()
locationManager?.removeUpdates(this)
}
override fun onLocationChanged(location: Location?) {
if (location == null) {
return
}
map?.let {
val coord = LatLng(location)
val locationOverlay = it.locationOverlay
locationOverlay.isVisible = true
locationOverlay.position = coord
locationOverlay.bearing = location.bearing
it.moveCamera(CameraUpdate.scrollTo(coord))
}
}
override fun onStatusChanged(provider: String, status: Int,
extras: Bundle) {
}
override fun onProviderEnabled(provider: String) {
}
override fun onProviderDisabled(provider: String) {
}
private fun hasPermission(): Boolean {
return PermissionChecker.checkSelfPermission(this, PERMISSIONS[0]) ==
PermissionChecker.PERMISSION_GRANTED &&
PermissionChecker.checkSelfPermission(this, PERMISSIONS[1]) ==
PermissionChecker.PERMISSION_GRANTED
}
companion object {
private const val PERMISSION_REQUEST_CODE = 100
private val PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION)
}
}