Markers, Lines and Polygons (Kotlin) - osmdroid/osmdroid GitHub Wiki
Pushpins, markers, icons, symbols, etc
The Marker overlay was generously donated to osmdroid from osmdroidbonuspack. It mirrors the Google Maps v2 API.
Create a marker like this:
firstMarker = Marker(mapView)
You can then give it position like this:
firstMarker.position = geoPoint
GeoPoints can be created like this:
// latitude and longitude have to be predefined variables
geoPoint = GeoPoint(latitude, longitude)
If you want the marker to be at the center of the current map position you can do it like this:
firstMarker.position = map.mapCenter as GeoPoint
setAnchor
is used for specifying the logical point of the image that the location represents.
For pin style icons, such as the icons Google Maps uses for its markers, "Bottom-Center" is usually what you want:
firstMarker.setAnchor(Marker.ANCHOR_BOTTOM, Marker.ANCHOR_CENTER)
Crosshair or other similar icons "Center-Center" may be what you want. Of course, you can specify whatever you need you so that the icon provide an accurate representation of the location.
firstMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
You then want to set the icon of the marker:
// Note: In an activity, the "context" is simply "this"
firstMarker.icon = ContextCompat.getDrawable(context, R.drawable.marker_icon)
You can set the "title". This is the text that will show when you click on a marker:
firstMarker.title = "Title"
NOTE: If you want more complex windows to show up when you click on a marker, please refer to the InfoWindow section of the article.
Final you can add the marker like this.
mapView.overlays.add(firstMarker)
// "Invalidating" the map displays the marker as soon as it has been added.
mapView.invalidate()
Here is the final code:
marker = Marker(mapView)
marker.position = geoPoint
marker.icon = ContextCompat.GetDrawable(context, R.drawable.marker_icon)
marker.title = "Test Marker"
marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_CENTER)
mapView.overlays.add(marker)
mapView.invalidate()
The fast overlay is great if you have a huge number points to render and they all share the same icon. It is optimized to render over 100k points, however performance will vary based on hardware.
// create 10k labelled points
// in most cases, there will be no problems of displaying >100k points, feel free to try
val points = new ArrayList<IGeoPoint>();
for (int i = 0; i < 10000; i++) {
points.add(LabelledGeoPoint(37 + Math.random() * 5, -8 + Math.random() * 5
, "Point #" + i));
}
// wrap them in a theme
val pt = SimplePointTheme(points, true);
// create label style
val textStyle = Paint();
textStyle.style = Paint.Style.FILL
textStyle.color = Color.parseColor("#0000ff")
textStyle.textAlign = Paint.Align.CENTER
textStyle.textSize = 24
// set some visual options for the overlay
// we use here MAXIMUM_OPTIMIZATION algorithm, which works well with >100k points
val opt = SimpleFastPointOverlayOptions.getDefaultStyle()
.setAlgorithm(SimpleFastPointOverlayOptions.RenderingAlgorithm.MAXIMUM_OPTIMIZATION)
.setRadius(7).setIsClickable(true).setCellSize(15).setTextStyle(textStyle);
// create the overlay with the theme
val sfpo = SimpleFastPointOverlay(pt, opt);
// onClick callback
sfpo.setOnClickListener {
// Note, "context" is just "this" in an activity.
// In non-activity classes, "context" has to be passed
// to the class manually.
Toast.makeText(context,
, "You clicked " + (points.get(point) as LabelledGeoPoint).getLabel()
, Toast.LENGTH_SHORT).show()
}
});
// add overlay
mMapView.overlays.add(sfpo);
How do you have a custom dialog show up when you click on a marker?
You do this using InfoWindows.
Firstly, you need to create a new layout. The layout should be a simple type, such as LinearLayout or FrameLayout. Do not use ConstraintLayout.
(Note: Can someone test when <space>
is needed and when it isn't?)
You need to make sure the layout fills the entire screen. You can do this by creating empty <Space>
elements which fill out all the empty vertical space.
Here is an example layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:layout_marginStart="5dp"
android:text="Title"
android:textColor="@color/black" />
<Space
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/move_button"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginEnd="5dp"
android:backgroundTint="@android:color/darker_gray"
android:text="@string/move"
android:textColor="@color/black" />
<Button
android:id="@+id/delete_button"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="10dp"
android:backgroundTint="@android:color/darker_gray"
android:text="@string/delete"
android:textColor="@color/black" />
</LinearLayout>
</LinearLayout>
This layout creates a custom window with a title for the marker, and two buttons to move or delete the marker. Lets call this layout info_window.xml
You then need to create a new class to handle the InfoWindow:
class MarkerWindow(private val mapView: MapView) :
InfoWindow(R.layout.info_window, mapView) {
}
This class extends InfoWindow. We give it the layout we created earlier, and a mapView. In this function, the InfoWindow is referred to as mView
. So in order to do anything with it (for example findViewById
or setOnClickListener
), you need to refer to mView
first.
Inside the function you need to override onOpen
and onClose
.
override fun onOpen(item: Any?) {
// Following command
closeAllInfoWindowsOn(mapView)
// Here we are settings onclick listeners for the buttons in the layouts.
val moveButton = mView.findViewById<Button>(R.id.move_button)
val deleteButton = mView.findViewById<Button>(R.id.delete_button)
moveButton.setOnClickListener {
// How to create a moveMarkerMapListener is not covered here.
// Use the Map Listeners guide for this instead
mapView.addMapListener(MoveMarkerMapListener)
}
deleteButton.setOnClickListener {
// Do Something
// In order to delete the marker,
// You would probably have to pass the "map class"
// where the map was created,
// along with an ID to reference the marker.
// Using a HashMap to store markers would be useful here
// so that the markers can be referenced by ID.
// Once you get the marker,
// you would do map.overlays.remove(marker)
}
// You can set an onClickListener on the InfoWindow itself.
// This is so that you can close the InfoWindow once it has been tapped.
// Instead, you could also close the InfoWindows when the map is pressed.
// This is covered in the Map Listeners guide.
mView.setOnClickListener {
close()
}
}
NOTE: How to move a marker (and closing marker when the map is pressed) is covered in the Map Listeners guide. Currently this hasn't been made, but hopefully it is done in the future.
You also then need to override onClose
:
override fun onClose(item: Any?) {
// Do something
}
Finally, you can set the infowindow of the Marker:
val infoWindow = MarkerWindow(mapView)
firstMarker.infoWindow = infoWindow
osmdroid includes two classes that more or less mirror Google Maps v2 API, the Polyline
and Polygon
.
val geoPoints = ArrayList<>();
//add your points here
val line = Polyline(); //see note below!
line.setPoints(geoPoints);
line.setOnClickListener(new Polyline.OnClickListener() {
Toast.makeText(mapView.context, "polyline with " + line.actualPoints.size + " pts was tapped", Toast.LENGTH_LONG).show()
return false
}
});
map.overlays.add(line);
val geoPoints = ArrayList<GeoPoint>();
//add your points here
val polygon = Polygon(); //see note below
geoPoints.add(GeoPoint(3.2, 4.4))
geoPoints.add(GeoPoint(4.5, 5.6))
geoPoints.add(GeoPoint(6.5, 3.2))
geoPoints.add(geoPoints.get(0)); //forces the loop to close(connect last point to first point)
polygon.fillPaint.color = Color.parseColor("#1EFFE70E") //set fill color
polygon.setPoints(geoPoints);
polygon.title = "A sample polygon"
//polygons supports holes too, points should be in a counter-clockwise order
val holes = new ArrayList<ArrayList<GeoPoint>>()
// Note, you will have to create "moreGeoPoints" yourself.
holes.add(moreGeoPoints)
polygon.setHoles(holes)
map.overlays.add(polygon)
Both Polyline and Polygons support
- InfoWindow (the cartoon bubble that is displayed when clicking on the item). The default (built in) info window is only available when using the alternate constructor of PolyLine(map) and Polygon(map). The default zero args constructor can still be used but you'll have to provide your own info window.
- The location over the InfoWindow anchor point can be overridden by extending both classes and overriding the appropriate method. In both classes, the default is to use the first point.
The Folder Overlay class is just a container of other overlays. Useful for bunching stuff together.
Device | Markers | Itemized Icon Overlay | Fast Overlay | Polylines | Polygons |
---|---|---|---|---|---|
Galaxy S5 | 5k | 3-6k | 100k | 1000s | 2k |
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.
You need to call mapView.invalidate()
after add Marker, Line or Polygon
to the map. If not, these overlay can`t display immediately.