How to use the osmdroid library (Kotlin)
This page is a kotlin adaptation of the original page. It is incomplete.
osmdroid's MapView is basically a replacement for Google's MapView class. First of all, create your Android project, and follow HowToMaven if you're using Maven, or follow HowToGradle if you're using Gradle/Android Studio. This will help you get the binaries for osmdroid included in your project.
In most cases, you will have to set the following authorizations in your AndroidManifest.xml:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
If you are only using parts of the library, you can adjust the permissions accordingly.
Online tile provider
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Location provider
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
Android 6.0+ devices require you have to check for "dangerous" permissions at runtime.
osmdroid requires the following dangerous permissions:
WRITE_EXTERNAL_STORAGE and ACCESS_COARSE_LOCATION/ACCESS_FINE_LOCATION.
See MainActivity.java
below for reference.
Create a "src/main/res/layouts/main.xml" layout like this one. With Android Studio, it probably created one already. The default is called "src/main/res/layouts/activity_main.xml":
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<org.osmdroid.views.MapView android:id="@+id/map"
android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>
We now create the main activity (MainActivity.kt):
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.preference.PreferenceManager
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.niels_ole.customtileserver.R
import org.osmdroid.config.Configuration.*
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.views.MapView
import java.util.ArrayList
class MainActivity : AppCompatActivity() {
private val REQUEST_PERMISSIONS_REQUEST_CODE = 1
private lateinit var map : MapView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
//handle permissions first, before map is created. not depicted here
//load/initialize the osmdroid configuration, this can be done
// This won't work unless you have imported this: org.osmdroid.config.Configuration.*
getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this))
//setting this before the layout is inflated is a good idea
//it 'should' ensure that the map has a writable location for the map cache, even without permissions
//if no tiles are displayed, you can try overriding the cache path using Configuration.getInstance().setCachePath
//see also StorageUtils
//note, the load method also sets the HTTP User Agent to your application's package name, if you abuse osm's
//tile servers will get you banned based on this string.
//inflate and create the map
setContentView(R.layout.activity_main)
map = findViewById<MapView>(R.id.map)
map.setTileSource(TileSourceFactory.MAPNIK)
}
override fun onResume() {
super.onResume()
//this will refresh the osmdroid configuration on resuming.
//if you make changes to the configuration, use
//SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
//Configuration.getInstance().load(this, PreferenceManager.getDefaultSharedPreferences(this));
map.onResume() //needed for compass, my location overlays, v6.0.0 and up
}
override fun onPause() {
super.onPause()
//this will refresh the osmdroid configuration on resuming.
//if you make changes to the configuration, use
//SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
//Configuration.getInstance().save(this, prefs);
map.onPause() //needed for compass, my location overlays, v6.0.0 and up
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
val permissionsToRequest = ArrayList<String>()
var i = 0
while (i < grantResults.size) {
permissionsToRequest.add(permissions[i])
i++
}
if (permissionsToRequest.size > 0) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toTypedArray(),
REQUEST_PERMISSIONS_REQUEST_CODE)
}
}
/*private fun requestPermissionsIfNecessary(String[] permissions) {
val permissionsToRequest = ArrayList<String>();
permissions.forEach { permission ->
if (ContextCompat.checkSelfPermission(this, permission)
!= PackageManager.PERMISSION_GRANTED) {
// Permission is not granted
permissionsToRequest.add(permission);
}
}
if (permissionsToRequest.size() > 0) {
ActivityCompat.requestPermissions(
this,
permissionsToRequest.toArray(new String[0]),
REQUEST_PERMISSIONS_REQUEST_CODE);
}
}*/
}
And that's enough to give it a try, and see the world map.
We can move the map on a default view point. For this, we need access to the map controller:
val mapController = map.controller
mapController.setZoom(9.5)
val startPoint = GeoPoint(48.8583, 2.2944);
mapController.setCenter(startPoint);
The best example of how to use the osmdroid library is our OpenStreetMapViewer sample project. It contains a basic osmdroid application plus a few special-use examples. It is recommended you use this project as an example for building your application.
You can add a MapView
to your xml layout using:
<org.osmdroid.views.MapView
android:id="@+id/mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"
tilesource="Mapnik" />
This will allow you to configure the tile source imagery for your MapView
but not much else.
However, for more control over your MapView
, you will want to create a MapView
programmatically.
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
map = MapView(inflater.getContext(), MapTileProviderBasic(inflater.getContext()));
return map
}
Note: you need manifest Fine Location permissions and if you are targeting API23+ APIs, you have to ask the user to explicitly grant location runtime permissions.
Notice: this is a very simple example that does not handle android lifecycle correctly. Versions 5.6.5 and older, you'll need to handle automatically disabling/enabling the location provider with android life cycle events. Version 6.0.0, this is handled so long as you call map view onPause and onResume appropriately.
this.mLocationOverlay = new MyLocationNewOverlay(new GpsMyLocationProvider(context), this.map);
this.mLocationOverlay.enableMyLocation();
this.map.getOverlays().add(this.mLocationOverlay);
Notice: this is a very simple example that does not handle android lifecycle correctly. Versions 5.6.5 and older, you'll need to handle automatically disabling/enabling the compass/orientation provider with android life cycle events. Version 6.0.0, this is handled so long as you call map view onPause and onResume appropriately.
compassOverlay = CompassOverlay(context, InternalCompassOrientationProvider(context), map)
compassOverlay.enableCompass()
map.overlays.add(compassOverlay)
Useful for displaying latitude/longitude grid lines.
v5.2 up to v5.6.5, see the sample app's source code from those tagged versions.
v6.0.0 and up
val overlay = new LatLonGridlineOverlay2();
map.overlays.add(overlay);
val rotationGestureOverlay = RotationGestureOverlay(mMapView)
rotationGestureOverlay.isEnabled
map.setMultiTouchControls(true)
map.overlays.add(rotationGestureOverlay)
// Note, "context" refers to your activity/application context.
// You can simply do resources.displayMetrics when inside an activity.
// When you aren't in an activity class, you will need to have passed the context
// to the non-activity class.
val dm : DisplayMetrics = context.resources.displayMetrics
val scaleBarOverlay = ScaleBarOverlay(mapView)
scaleBarOverlay.setCentred(true)
//play around with these values to get the location on screen in the right place for your application
mScaleBarOverlay.setScaleBarOffset(dm.widthPixels / 2, 10)
mMapView.overlays.add(scaleBarOverlay)
val minimapOverlay = MinimapOverlay(context, mMapView.tileRequestCompleteHandler)
minimapOverlay.setWidth(dm.widthPixels / 5)
minimapOverlay.setHeight(dm.heightPixels / 5)
//optionally, you can set the minimap to a different tile source
//minimapOverlay.setTileSource(....)
map.overlays.add(minimapOverlay)
//your items
val items = ArrayList<OverlayItem>()
items.add(OverlayItem("Title", "Description", GeoPoint(0.0, 0.0)))
//the overlay
var overlay = ItemizedOverlayWithFocus<OverlayItem>(items, object:ItemizedIconOverlay.OnItemGestureListener<OverlayItem>() {
override fun onItemSingleTapUp(index:Int, item:OverlayItem):Boolean {
//do something
return true
}
override fun onItemLongPress(index:Int, item:OverlayItem):Boolean {
return false
}
}, context)
overlay.setFocusItemsOnTap(true);
mapView.overlays.add(overlay);
The answer is greatly dependent on what hardware the osmdroid based app is running on. A Samsung S5 (no endorsement intended) ran just fine at 3k icons and was noticeably choppy at 6k icons. Your mileage may vary. X86 Android running on modern hardware will perform great at even higher numbers. However, it's recommended to limit the amount of stuff you're rendering, if at all possible.
If you're also drawing paths, lines, polygons, etc, then this also changes the equation. Drawing multipoint graphics is computationally more expensive and thus negatively affects performance under higher loads. To mitigate performance issues with multipoint graphics, one strategy would be to reduce the number of points handed off to the map engine when at a higher zoom level (numerically lower), then increase the fidelity as the user zoom's in. In effect, you would be clipping the visible data at the map view bounds so that the map view only "knows" about what's on screen and doesn't have to loop through all 10k icons that you want on the map. Although you can give the map view all 10k objects, but every time the map moves or zooms, it will iterate over all 10k items to calculate where to draw them (if at all). Using this mechanism paired with map motion listeners and a database query that supports geographic bounds, you can support a rich experience for users with lots of data and still have reasonable performance.
Reusing drawables for icons will help with memory usage too.
See https://github.com/osmdroid/osmdroid/wiki/Map-Sources
Add in version 6.1.3, a support library is available that can read ESRI shape files, specifically shape and metadata files.
- not all shape file formats are supported, such as 3d shapes.
- the code in this library, and the dependency are asf/mit compatible. no gpl issues
To use this, add this to your gradle build file:
implementation 'org.osmdroid:osmdroid-shape:VERSION'
In Kotlin
val folder: List<Overlay> = ShapeConverter.convert(mapView, File(myshape))
mapView.overlayManager.addAll(folder)
mapView.invalidate()
Where myshape
is a .shp file somewhere on the drive.
If other metadata is present, it is appended to the description information for the converted item. Output is one of:
- Marker
- Polyline
- Polygon