Android Tutorial OSMDroid - MapView for B4A tutorial

You can find the OSMDroid library thread here: http://www.b4x.com/forum/additional...tes/16309-osmdroid-mapview-b4a.html#post92643.

AIM: Create and initialize a MapView, enable the map zoom controller and multitouch controller, set a zoom level then center the map on a location.

B4X:
Sub Process_Globals
End Sub

Sub Globals
   Dim MapView1 As MapView
End Sub

Sub Activity_Create(FirstTime As Boolean)
   If File.ExternalWritable=False Then
      '   OSMDroid requires the use of external storage to cache tiles
      '   if no external storage is available then the MapView will display no tiles
      Log("WARNING NO EXTERNAL STORAGE AVAILABLE")
   End If
   
   '   no EventName is required as we don't need to listen for MapView events
   MapView1.Initialize("")
   Activity.AddView(MapView1, 0, 0, 100%x, 100%y)
   
   '   by default the map will zoom in on a double tap and also be draggable - no other user interface features are enabled
   
   '   enable the built in zoom controller - the map can now be zoomed in and out
   MapView1.SetZoomEnabled(True)
   
   '   enable the built in multi touch controller - the map can now be 'pinch zoomed'
   MapView1.SetMultiTouchEnabled(True)
   
   '   set the zoom level BEFORE the center (otherwise unpredictable map center may be set)
   MapView1.Zoom=14
   MapView1.SetCenter(52.75192, 0.40505)
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub

The code is pretty self-explanatory.

I've added the code to check if the device has available external storage as OSMDroid will not display any tiles if no external storage is available.
External storage is used to save/cache all downloaded tiles - no external storage means no map!
(I'll omit that check from future tutorials but it's something to bear in mind - not that i know of any Android devices that have no external storage).

Create and initialize a MapView, add it to the Activity 100% width and 100% height.
Enable the zoom and multi-touch controller.
Zoom in to level 14 then set the MapView center to a location (sunny Norfolk, UK!).

I've found that setting the map center and then immediately setting the zoom level does not work as expected.
I think that while the MapView is setting the map center it also zooms in so the end result is unpredictable.

Pan and zoom the map, now rotate your device and you'll see the map returns to it's initial state of zoom level 14, center (52.75192, 0.40505).

I shall show you how to save and restore the MapView state next...

Martin.
 

Attachments

  • 01 - SimpleMap.zip
    5.8 KB · Views: 4,638
Last edited:

warwound

Expert
Licensed User
Longtime User
I have downloaded and copied the two latest xml files to the Extralibs folder. I get the same error with 1.7 as posted above. I went back to check that the files are there and readable and they are. Not sure why it will not work? Any Ideas?

Look at the attached screengrab - do NativeOSMDroid and slf4j-android-1.5.8 appear in your IDE list of libraries?

If so have you checked them to include them in your project?

Have you right-clicked the IDE libraries panel and Refreshed it OR have you restarted the B4A IDE?

Martin.
 

Attachments

  • osmdroid_library_view.jpg
    osmdroid_library_view.jpg
    24.4 KB · Views: 664

warwound

Expert
Licensed User
Longtime User
Version 1.03 of OSMDroid updates PathOverlay with a new method:

AddGreatCircle (StartPoint As GeoPoint, EndPoint As GeoPoint, NumberOfPoints As Int)

Add NumberOfPoints points to the path to create a great circle from StartPoint to EndPoint.
Does NOT clear existing points of the path.

The GeoPoint and BoundingBox objects have also been updated, more details can be found here: http://www.b4x.com/forum/additional...s/16309-osmdroid-mapview-b4a-3.html#post93396.

And here's some example code showing the new AddGreatCircle method, and new GeoPoint GetDistanceTo and GetDestinationPoint methods:

B4X:
Sub Process_Globals
   Dim MapCenter As GeoPoint
   Dim ZoomLevel As Int
   
   '   create a List to store the path points in Activity_Pause
   Dim PathPoints As List
End Sub

Sub Globals
   Dim MapView1 As MapView
   
   '   create the PathOverlay
   Dim PathOverlay1 As PathOverlay
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.AddMenuItem("Do something", "DoSomething")
   
   MapView1.Initialize("MapView1")
   Activity.AddView(MapView1, 0, 0, 100%x, 100%y)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   
   '   initialize the PathOverlay and add it to the MapView, the path will be colored red
   PathOverlay1.Initialize(MapView1, Colors.Red)
   MapView1.AddOverlay(PathOverlay1)
   
   If FirstTime Then
      '   initialize the PathPoints List
      PathPoints.Initialize
      
      Dim Distance, NumberOfPoints As Int
      Dim StartPoint, EndPoint As GeoPoint
      StartPoint.Initialize(52.76, 0.38)
      EndPoint.Initialize(43.3, 54.6)
      
      '   use the GeoPoint GetDistanceTo method to get the (haversine) distance between start and end points
      Distance=StartPoint.GetDistanceTo(EndPoint)
      
      '   base the number of points of the great circle path on the distance - one point for every 100kms looks good
      NumberOfPoints=Distance/100000
      
      Log("Distance="&Distance&", NumberOfPoints="&NumberOfPoints)
      
      PathOverlay1.AddGreatCircle(StartPoint, EndPoint, NumberOfPoints)
      
      '   show that AddGreatCircle does not clear any existing points on the path
      '   and also show use of the GeoPoint GetDestinationPoint method
      '   which return a new GeoPoint 5000kms from EndPoint with a bearing of 270 degrees
      PathOverlay1.AddGreatCircle(EndPoint, EndPoint.GetDestinationPoint(5000000, 270), NumberOfPoints)
      
      MapView1.FitMapToBoundingBox(PathOverlay1.GetBoundingBox)
   Else
      '   restore the path points
      PathOverlay1.AddPoints(PathPoints)
      
      '   clear the PathPoints List as it's no longer required
      PathPoints.Clear
      
      '   restore the map center and zoom level
      MapView1.Zoom=ZoomLevel
      MapView1.SetCenter3(MapCenter)
   End If
   
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   MapCenter=MapView1.GetCenter
   ZoomLevel=MapView1.Zoom
   
   '   save the PathOverlay1 points
   PathPoints=PathOverlay1.GetAllPoints
End Sub

Sub DoSomething_Click
   Log("PathOverlay1 contains "&PathOverlay1.GetNumberOfPoints&" points")
   '   set the map center and zoom level so that it contains the entire path
   MapView1.FitMapToBoundingBox(PathOverlay1.GetBoundingBox)
   '   no need to call MapView Invalidate - the MapView FitMapToBoundingBox methods calls this internally
   '   MapView1.Invalidate
End Sub

All self-explanatory i think (and hope!).

The only point to make (no pun intended) is the NumberOfPoints parameter in the AddGreatCircle method.
More points = a smoother line.
Too many points = a map that may not perform very well depending on the device capabilities.

Maybe the PathOverlay should have a method:

Decimate (Distance as Int)

Decimate would remove points from the path that are nearer than Distance (in meters) to each other.
Any comments welcome.

Martin.
 

Attachments

  • 14 - GreatCircle.zip
    6.3 KB · Views: 847

imbault

Well-Known Member
Licensed User
Longtime User
Hi martin,

In the offline example, it doesn't work if the OSMDROID doesn't exists : I add
If File.IsDirectory(File.DirRootExternal,"osmdroid")=False Then
File.MakeDir(File.DirRootExternal,"osmdroid")
End If

which makes :

Dim OfflineTileCacheFilename, TileCacheDir As String
OfflineTileCacheFilename="offline_tile_cache_mapnik.zip"
TileCacheDir=File.DirRootExternal&"/osmdroid"

If File.Exists(TileCacheDir, OfflineTileCacheFilename)=False Then
' copy the offline tile cache to the OSMDroid cache folder
If File.IsDirectory(File.DirRootExternal,"osmdroid")=False Then
File.MakeDir(File.DirRootExternal,"osmdroid")
End If
File.Copy(File.DirAssets, OfflineTileCacheFilename, TileCacheDir, OfflineTileCacheFilename)
End If

Am I correct ?

Thanks
 

margret

Well-Known Member
Licensed User
Longtime User
@warwound

When I created the xml file shown by thedesolatesoul, I looked for the libraries in the libs tab. I guess I needed the second file or I created his xml wrong and the original support files did not show, but yours did. After going back and copying your two xml files and doing a refresh, they did show and I checked them. All works fine now, sorry for the oversight.:signOops:

This is a great addition, awesome job!;)

Thanks,

Margret
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Hi martin,

In the offline example, it doesn't work if the OSMDROID doesn't exists : I add
If File.IsDirectory(File.DirRootExternal,"osmdroid")=False Then
File.MakeDir(File.DirRootExternal,"osmdroid")
End If

which makes :

Dim OfflineTileCacheFilename, TileCacheDir As String
OfflineTileCacheFilename="offline_tile_cache_mapnik.zip"
TileCacheDir=File.DirRootExternal&"/osmdroid"

If File.Exists(TileCacheDir, OfflineTileCacheFilename)=False Then
' copy the offline tile cache to the OSMDroid cache folder
If File.IsDirectory(File.DirRootExternal,"osmdroid")=False Then
File.MakeDir(File.DirRootExternal,"osmdroid")
End If
File.Copy(File.DirAssets, OfflineTileCacheFilename, TileCacheDir, OfflineTileCacheFilename)
End If

Am I correct ?

Thanks

Well spotted!

I've updated that example now so that it checks if the osmdroid cache folder exists and creates it if it does not exist.

I used some code from this thread to check if the cache folder exists.

Martin.
 

warwound

Expert
Licensed User
Longtime User
OSMDroid version 3.00 or later is required for future examples.

With version 3.00 of OSMDroid i have created two new Overlay layers: MarkersEventsOverlay and MarkersBalloonLayer.

I shall start with some example code to demonstrate the MarkersEventsLayer.

MarkersEventsLayer is almost the same as the existing MarkersOverlay.

It differs in the values that are passed to it's event handler Subs:

MarkersEventsOverlay Events:
  • Click (Marker1 As Marker)
  • LongClick (Marker1 As Marker)

MarkersOverlay Events
  • Click (Title As String, Description As String, Point As GeoPoint)
  • LongClick (Title As String, Description As String, Point As GeoPoint)

The new MarkersEventsLayer passes the original Marker back to the event handler rather than the original Marker's Title, Description and Point values.

Other than that there are no changes.
The updated overlay should be more versatile as it's more likely that the Marker object is useful rather than the Marker object's properties.

Example code:

B4X:
Sub Process_Globals
   Dim MapCenter As GeoPoint
   Dim TileSource As String
   Dim ZoomLevel As Int
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim MinimapOverlay1 As MinimapOverlay
   Dim ScaleBarOverlay1 As ScaleBarOverlay
   Dim TileSourceSpinner As Spinner
   
   '   create the MarkersEventsOverlay
   Dim MarkersEventsOverlay1 As MarkersEventsOverlay
   
   '   make Marker1 global so we can test the new IsEqualTo method on a click
   Dim Marker1 As Marker
End Sub

Sub Activity_Create(FirstTime As Boolean)
   '   update the MenuItems
   Activity.AddMenuItem("Fit map to markers", "MenuItemSelect")
   
   '   MapView initialized with no EventName as we'll not be listening for MapView events
   MapView1.Initialize("")
   Activity.AddView(MapView1, 0, 48dip, 100%x, 100%y-48dip)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   
   '   initialize the MarkersEventsOverlay and add it to the MapView
   '   an EventName is required as we will listen for the two events that MarkersEventsOverlay generates
   MarkersEventsOverlay1.Initialize(MapView1, "MarkersEventsOverlay1")
   MapView1.AddOverlay(MarkersEventsOverlay1)
   
   '   create and initialize 2 Markers
   
   '   for Marker1 i'll use a custom icon
   Dim Icon As BitmapDrawable
   Icon.Initialize(LoadBitmap(File.DirAssets, "my_icon.png"))
   
   Marker1.Initialize("Home sweet home", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi egestas suscipit convallis. Etiam pellentesque gravida est, quis luctus nunc commodo at. Nam et risus orci. Integer malesuada lorem dolor. Maecenas vestibulum cursus enim, tincidunt luctus libero placerat non. In vitae metus tellus, nec euismod nibh. Phasellus ut quam vitae justo sagittis auctor. Sed vel sapien dolor. Etiam ut sem id dolor iaculis ullamcorper. Aenean eget sem nibh, a tempor augue. Nulla interdum luctus molestie.", 52.75610, 0.39748, Icon)
   
   '   Marker2 will display the default OSMDroid icon
   '   the default icon is used if Null is passed as the Icon parameter
   Dim Marker2 As Marker
   Marker2.Initialize("Elsewhere", "Downham Market", 52.60801, 0.39047, Null)
   
   '   create a List and initialize it with the Markers
   Dim Markers As List
   Markers.Initialize2(Array As Object(Marker1, Marker2))
   
   '   add the List of Markers to the MarkersEventsOverlay
   MarkersEventsOverlay1.AddMarkers(Markers)
   
   If FirstTime Then
      TileSource="Mapnik"
      
      '   fit the MapView to the MarkersEventsOverlay
      MapView1.FitMapToBoundingBox(MarkersEventsOverlay1.GetBoundingBox)
   Else
      '   restore saved zoom level and map center
      MapView1.Zoom=ZoomLevel
      MapView1.SetCenter3(MapCenter)
   End If
   
   
   ScaleBarOverlay1.Initialize(MapView1)
   MapView1.AddOverlay(ScaleBarOverlay1)
   
   '   ensure that the MinimapOverlay is the LAST overlay added to the MapView
   MinimapOverlay1.Initialize(MapView1)
   MapView1.AddOverlay(MinimapOverlay1)
   
   TileSourceSpinner.Initialize("TileSourceSelect")
   Activity.AddView(TileSourceSpinner, 0, 0, 100%x, 48dip)
   
   TileSourceSpinner.AddAll(MapView1.GetTileSources)
   TileSourceSpinner.Prompt="Select a TileSource"
   TileSourceSpinner.SelectedIndex=TileSourceSpinner.IndexOf(TileSource)
   TileSourceSelect_ItemClick(TileSourceSpinner.SelectedIndex, TileSourceSpinner.SelectedItem)
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   MapCenter=MapView1.GetCenter
   TileSource=MapView1.GetTileSource
   ZoomLevel=MapView1.Zoom
End Sub

'   two event listeners listen for the MarkersEventsOverlay's two events

Sub MarkersEventsOverlay1_Click(ClickedMarker As Marker)
   Log("MarkersEventsOverlay1_Click")
   If Marker1.IsEqualTo(ClickedMarker) Then
      Log("You clicked Marker1")
   End If
   Log(ClickedMarker.Title&", "&ClickedMarker.Description&": "&ClickedMarker.GeoPoint)
End Sub

Sub MarkersEventsOverlay1_LongClick(LongClickedMarker As Marker)
   Log("MarkersEventsOverlay1_LongClick")
   
   '   zoom the map in and center (with animation) on LongClickedMarker
   MapView1.Zoom=MapView1.GetMaxZoomLevel-1
   MapView1.AnimateTo3(LongClickedMarker.GeoPoint)
End Sub

Sub MenuItemSelect_Click
   Dim MenuItem As String
   MenuItem=Sender
   Select MenuItem
      Case "Fit map to markers"
         '   fit the MapView to the MarkersOverlay
         MapView1.FitMapToBoundingBox(MarkersEventsOverlay1.GetBoundingBox)
   End Select
End Sub

Sub TileSourceSelect_ItemClick (Position As Int, Value As Object)
   MapView1.SetTileSource(Value)
   MinimapOverlay1.SetTileSource(Value)
End Sub

All much the same as the MarkersOverlay example.
The Marker object has a new method IsEqualTo whic returns True if the Marker is the same object as the compared to Marker.
I picture a List or Array of Markers that have been (previously) added to the MapView and this method will enable you to identify which of those added Markers has been Clicked (or LongClicked) - and take action.

The attached file contains the default OSMDroid icons in the drawable-nodpi folder - no need to copy them from the library download to this project to run it.

Martin.
 

Attachments

  • MarkersEventsOverlay.zip
    40 KB · Views: 858
Last edited:

warwound

Expert
Licensed User
Longtime User
MarkersBalloonOverlay is the successor to the rather abysmal MarkersFocusOverlay.

It implements the MapViewBalloons library, you can now create a professional looking map with a customisable information balloon!

Now we need to handle 'restore map state on orientation change or Activity_Pause'.
Previous examples restored the map center, zoom level and TileSource.
To save and restore the state of the balloon it is required that all Marker objects are Process_Globals.

Here's the code:

B4X:
Sub Process_Globals
   '    a Marker is not an Activity object so can be created as a Process_Global
   '    the Markers will need to be declared as Process_Globals so we can identify and restore the open Balloon on orientation change
   Dim FocusedMarker, Marker1, Marker2, Marker3, Marker4 As Marker
   Dim MapCenter As GeoPoint
   Dim TileSource As String
   Dim ZoomLevel As Int
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim TileSourceSpinner As Spinner
   
   '   create the MarkersBalloonOverlay
   Dim MarkersBalloonOverlay1 As MarkersBalloonOverlay
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.AddMenuItem("Fit Map to Markers", "FitMap")
   '   MapView initialized with no EventName as we'll not be listening for MapView events
   MapView1.Initialize("")
   Activity.AddView(MapView1, 0, 48dip, 100%x, 100%y-48dip)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   
   '   initialize the MarkersBalloonOverlay and add it to the MapView
   '   no EventName is passed as we'll not listen for MarkersBalloonOverlay events in this example
   '   new with version 3.10 of the library is the *requirement* to set the default balloon overlay layout name
   '   i'm using "balloon_overlay", in this project's Objects/res/layout folder the layout file is "balloon_overlay.xml"
   MarkersBalloonOverlay1.Initialize(MapView1, "", "balloon_overlay")
   
   MapView1.AddOverlay(MarkersBalloonOverlay1)
   
   If FirstTime Then
      '   create and initialize the Markers
      '   for Marker1 i'll use a custom icon
      Dim Icon As BitmapDrawable
      Icon.Initialize(LoadBitmap(File.DirAssets, "my_icon.png"))   '   ** how to prevent scaling on high density devices? **
      Marker1.Initialize("Home sweet home", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi egestas suscipit convallis. Etiam pellentesque gravida est, quis luctus nunc commodo at. Nam et risus orci. Integer malesuada lorem dolor. Maecenas vestibulum cursus enim, tincidunt luctus libero placerat non. In vitae metus tellus, nec euismod nibh. Phasellus ut quam vitae justo sagittis auctor. Sed vel sapien dolor. Etiam ut sem id dolor iaculis ullamcorper. Aenean eget sem nibh, a tempor augue. Nulla interdum luctus molestie.", 52.75610, 0.39748, Icon)
      '   Marker2 will display the default OSMDroid icon
      '   the default icon is used if Null is passed as the Icon parameter
      Marker2.Initialize("Elsewhere", "Downham Market", 52.60801, 0.39047, Null)
      Marker3.Initialize("Title but no description", "", 52.6959, 0.606, Null)
      Marker4.Initialize("", "Description but no title", 52.8440, 0.506, Null)
   End If
   
   '   create a List and initialize it with the 3 Markers
   Dim Markers As List
   Markers.Initialize2(Array As Object(Marker1, Marker2, Marker3, Marker4))

   '   add the List of Markers to the MarkersBalloonOverlay
   MarkersBalloonOverlay1.AddMarkers(Markers)
   
   If FirstTime Then
      TileSource="Mapnik"
      
      '   fit the MapView to the MarkersBalloonOverlay
      MapView1.FitMapToBoundingBox(MarkersBalloonOverlay1.GetBoundingBox)
   Else
      '   restore saved zoom level and map center
      MapView1.Zoom=ZoomLevel
      MapView1.SetCenter3(MapCenter)
      If FocusedMarker<>Null Then
         '   restore the Balloon if was open before the orientation change (or Activity_Pause)
         MarkersBalloonOverlay1.FocusedMarker=FocusedMarker
      End If
   End If
   
   TileSourceSpinner.Initialize("TileSourceSelect")
   Activity.AddView(TileSourceSpinner, 0, 0, 100%x, 48dip)
   
   TileSourceSpinner.AddAll(MapView1.GetTileSources)
   TileSourceSpinner.Prompt="Select a TileSource"
   TileSourceSpinner.SelectedIndex=TileSourceSpinner.IndexOf(TileSource)
   TileSourceSelect_ItemClick(TileSourceSpinner.SelectedIndex, TileSourceSpinner.SelectedItem)
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   '   save the FocusedMarker
   '   the FocusesMarker is the Marker whose Balloon is being displayed or Null if no Balloon is being displayed
   FocusedMarker=MarkersBalloonOverlay1.FocusedMarker
   MapCenter=MapView1.GetCenter
   TileSource=MapView1.GetTileSource
   ZoomLevel=MapView1.Zoom
End Sub

Sub FitMap_Click
   MapView1.FitMapToBoundingBox(MarkersBalloonOverlay1.GetBoundingBox)
End Sub

Sub TileSourceSelect_ItemClick (Position As Int, Value As Object)
   MapView1.SetTileSource(Value)
End Sub

When the Activity is first created, the Markers are created.
Four Markers are created, one with a long Description, one with no Description, one with no Title and another that has both Title and (short) Description.
The map loads and centers on the four Markers.
Click any Marker and the Balloon displays.
Click the Balloon close icon to close the Balloon or alternatively click the Marker again to toggle the Balloon visibilty.
The Balloon will display Title if not an empty String "", Description if not an empty String "" and the 'close Balloon icon'.

When the Activity is paused - on orientation change for example - the Marker whose Balloon is being displayed is saved as Process_Global FocusedMarker.
If no Balloon is being displayed then FocusedMarker will be set to Null.

Now when the Activity is (re)created (FirstTime is False) the MarkersBalloonOverlay1 FocusedMarker property is set to the previously saved FocusedMarker value if not Null.

The MarkersBalloonOverlay FocusedMarker is the Marker whose Balloon is being displayed - or Null if no Balloon is being displayed.

The result is a map that saves and restores it's Balloon state on orientation change.

The Balloon layout

B4X:
MarkersBalloonOverlay1.Initialize(MapView1, "", "balloon_overlay")

The Balloon layout is defined in the XML file Objects\res\layout\balloon_overlay.xml

By default the layout is structured like this:
B4X:
******************************************
* Title                     * Close Icon *
*****************************            *
* Description               *            *
******************************************
The maximum width of the Balloon is the screen width minus 20 pixels left and 20 pixels right for padding.
The balloon height is limited to the screen height.

If you use the MarkersBalloonOverlay in a B4A project then you must add the drawable-nodpi drawables and layout folders from the library to your project - this example code download includes those folders already.

Any changes to the default Balloon layout must be done in the balloon_overlay.xml file.
You can change many values and add values to set the Description (snippet) text color for example.

As long as that XML file defines a layout with at least three Views with ids of: balloon_item_title, balloon_item_snippet and close_img_button then it should work with no problems in OSMDroid.

The Balloon is very customisable though - i'll cover that in the next example that i upload.

Martin.

I have updated this example to make it compatible with version 3.10 of OSMDroid - check out this thread: http://www.b4x.com/forum/additional...s/16309-osmdroid-mapview-b4a-4.html#post96419
 

Attachments

  • MarkersBalloonOverlayDemo.zip
    45.1 KB · Views: 900
Last edited:

warwound

Expert
Licensed User
Longtime User
MarkersBalloonOverlay - Customise the Balloon

You can customise the appearance and functionality of the Balloon.

To get started take the code from the MarkersBalloonOverlay example, add an EventName to the MarkersBalloonOverlay1 Initialize call and add these two Subs:

B4X:
MarkersBalloonOverlay1.Initialize(MapView1, "MarkersBalloonOverlay1", "balloon_overlay1")

B4X:
Sub MarkersBalloonOverlay1_BalloonClick(ViewTag As String)
   Log("The Balloon View with Tag of '"&ViewTag&"' was Clicked")
End Sub

Sub MarkersBalloonOverlay1_BalloonLongClick(ViewTag As String)
   Log("The Balloon View with Tag of '"&ViewTag&"' was LongClicked")
End Sub

BalloonClick and BalloonLongClick are the two events that MarkersBalloonOverlay can generate.

Add those two Subs to the previous example and run the project.
Open a Balloon and tap within it, look at the B4A log - no events are generated.

We need to modify the (read-only) balloon_overlay.xml file.
So make that file writeable and open it in a text editor.
We want to add a tag attribute to a View:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content"
   android:orientation="horizontal" 
   android:paddingBottom="35dip"
   android:paddingLeft="10dip" 
   android:minWidth="200dip" 
   android:id="@+id/balloon_main_layout"
   android:background="@drawable/balloon_overlay_bg_selector" 
   android:paddingTop="0dip"
   android:paddingRight="0dip">
   <LinearLayout 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content"
      android:orientation="vertical" 
      android:layout_weight="1"
      android:paddingTop="10dip" 
      android:id="@+id/balloon_inner_layout">
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_title"
         android:text="balloon_item_title" 
         android:textSize="16dip"
         android:textColor="#FF000000">
      </TextView>
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_snippet"
         android:text="balloon_item_snippet" 
         android:textSize="12dip"
         android:tag="MyDescriptionTag">
      </TextView>
   </LinearLayout>
   <ImageView android:layout_width="wrap_content"
      android:layout_height="wrap_content" 
      android:src="@drawable/balloon_overlay_close"
      android:id="@+id/close_img_button" 
      android:paddingLeft="10dip"
      android:paddingBottom="10dip" 
      android:paddingRight="8dip"
      android:paddingTop="8dip">
   </ImageView>
</LinearLayout>

I've simply added android:tag="MyDescriptionTag" to the TextView with an id of balloon_item_snippet.

Save the XML file, make it read-only and run the project.
Open a Balloon and tap the description text, look at the log and you'll see the BalloonClick event is now generated.
A long tap will cause the BalloonLongClick event to be generated.
Both event handler Subs are passed the String which you assigned to the View as it's tag attribute.

Log output:
The Balloon View with Tag of 'MyDescriptionTag' was Clicked
The Balloon View with Tag of 'MyDescriptionTag' was LongClicked

Now let's do something more useful, add a new TextView to the Balloon layout:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content"
   android:orientation="horizontal" 
   android:paddingBottom="35dip"
   android:paddingLeft="10dip" 
   android:minWidth="200dip" 
   android:id="@+id/balloon_main_layout"
   android:background="@drawable/balloon_overlay_bg_selector" 
   android:paddingTop="0dip"
   android:paddingRight="0dip">
   <LinearLayout 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content"
      android:orientation="vertical" 
      android:layout_weight="1"
      android:paddingTop="10dip" 
      android:id="@+id/balloon_inner_layout">
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_title"
         android:text="balloon_item_title" 
         android:textSize="16dip"
         android:textColor="#FF000000">
      </TextView>
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_snippet"
         android:text="balloon_item_snippet" 
         android:textSize="12dip"
         android:tag="MyDescriptionTag">
      </TextView>
      
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:text="Click here for more information" 
         android:textSize="14dip"
         android:tag="MoreInfoTag"
         android:textColor="#F00">
      </TextView>
      
   </LinearLayout>
   <ImageView android:layout_width="wrap_content"
      android:layout_height="wrap_content" 
      android:src="@drawable/balloon_overlay_close"
      android:id="@+id/close_img_button" 
      android:paddingLeft="10dip"
      android:paddingBottom="10dip" 
      android:paddingRight="8dip"
      android:paddingTop="8dip">
   </ImageView>
</LinearLayout>

I've added a new TextView with a tag of MoreInfoTag, it displays the text Click here for more information and the text is colored red.

Save the XML file, make it read-only and run the project.
Open a Balloon and there's the 'Click here for more information' text underneath the description text.
Click the new text and look at the log - the BalloonClick event is generated and the 'MoreInfoTag' String passed to the event handler Sub.
The Balloon View with Tag of 'MoreInfoTag' was Clicked

So how can we make use of that event?
We need to know which Marker's information was being displayed, we already have a FocusedMarker variable but that's only currently updated in Activity_Pause.
Let's update FocusedMarker in the BalloonClick Sub and use a Select code block to process the ViewTag:

B4X:
Sub MarkersBalloonOverlay1_BalloonClick(ViewTag As String)
   Log("The Balloon View with Tag of '"&ViewTag&"' was Clicked")
   FocusedMarker=MarkersBalloonOverlay1.FocusedMarker
   Select ViewTag
      Case "MyDescriptionTag"
         Log("Do something")
      Case "MoreInfoTag"
         Log("Show more information for FocusedMarker")
   End Select
End Sub

So that's the basic theory, lets do something useful.
Start with the original balloon_overlay.xml file - discard the previous changes.

We'll add a new TextView much like the 'Click here for more information' TextView but this time we'll create a 'Zoom in' function:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content"
   android:orientation="horizontal" 
   android:paddingBottom="35dip"
   android:paddingLeft="10dip" 
   android:minWidth="200dip" 
   android:id="@+id/balloon_main_layout"
   android:background="@drawable/balloon_overlay_bg_selector" 
   android:paddingTop="0dip"
   android:paddingRight="0dip">
   <LinearLayout 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content"
      android:orientation="vertical" 
      android:layout_weight="1"
      android:paddingTop="10dip" 
      android:id="@+id/balloon_inner_layout">
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_title"
         android:text="balloon_item_title" 
         android:textSize="14dip"
         android:textColor="#FF000000">
      </TextView>
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_snippet"
         android:text="balloon_item_snippet" 
         android:textSize="12dip"
         android:textColor="#CCC">
      </TextView>
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:text="Zoom in" 
         android:textSize="14dip"
         android:tag="ZoomIn"
         android:textColor="#F00">
      </TextView>
   </LinearLayout>
   <ImageView android:layout_width="wrap_content"
      android:layout_height="wrap_content" 
      android:src="@drawable/balloon_overlay_close"
      android:id="@+id/close_img_button" 
      android:paddingLeft="10dip"
      android:paddingBottom="10dip" 
      android:paddingRight="8dip"
      android:paddingTop="8dip">
   </ImageView>
</LinearLayout>

I've changed the balloon_item_title textSize to 14dip, the balloon_item_snippet textColor to grey (#CCC) and added the new Zoom in TextView.

Update the BalloonClick Sub:

B4X:
Sub MarkersBalloonOverlay1_BalloonClick(ViewTag As String)
   FocusedMarker=MarkersBalloonOverlay1.FocusedMarker
   Select ViewTag
      Case "ZoomIn"
         If MapView1.Zoom<MapView1.GetMaxZoomLevel Then
            MapView1.Zoom=MapView1.GetMaxZoomLevel
            MapView1.AnimateTo3(FocusedMarker.GeoPoint)
         End If
   End Select
End Sub

Make sure your new XML file is saved and read-only then run the project.
Open a Balloon and try out the new Zoom in function.

Say you want a 'Zoom in' ImageView instead of a 'Zoom in' TextView.
There is a drawable named center.png included in the drawable-nodpi folder, it not the same size as the balloon_overlay_close.png but let's not worry about that for now:

B4X:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   android:layout_width="wrap_content" 
   android:layout_height="wrap_content"
   android:orientation="horizontal" 
   android:paddingBottom="35dip"
   android:paddingLeft="10dip" 
   android:minWidth="200dip" 
   android:id="@+id/balloon_main_layout"
   android:background="@drawable/balloon_overlay_bg_selector" 
   android:paddingTop="0dip"
   android:paddingRight="0dip">
   <LinearLayout 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content"
      android:orientation="vertical" 
      android:layout_weight="1"
      android:paddingTop="10dip" 
      android:id="@+id/balloon_inner_layout">
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_title"
         android:text="balloon_item_title" 
         android:textSize="14dip"
         android:textColor="#FF000000">
      </TextView>
      <TextView android:layout_height="wrap_content"
         android:layout_width="fill_parent" 
         android:id="@+id/balloon_item_snippet"
         android:text="balloon_item_snippet" 
         android:textSize="12dip"
         android:textColor="#CCC">
      </TextView>
   </LinearLayout>
   <LinearLayout 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content"
      android:orientation="vertical">
   <ImageView android:layout_width="wrap_content"
      android:layout_height="wrap_content" 
      android:src="@drawable/balloon_overlay_close"
      android:id="@+id/close_img_button" 
      android:paddingLeft="10dip"
      android:paddingBottom="4dip" 
      android:paddingRight="8dip"
      android:paddingTop="8dip">
   </ImageView>
   <ImageView android:layout_width="wrap_content"
      android:layout_height="wrap_content" 
      android:src="@drawable/center"
      android:paddingLeft="10dip"
      android:paddingBottom="8dip" 
      android:paddingRight="8dip"
      android:paddingTop="4dip"
      android:tag="ZoomIn">
   </ImageView>
   </LinearLayout>
</LinearLayout>

Enclosing the original balloon__overlay_close ImageView in a new LinearLayout with vertical orientation enables us to add the ZoomIn ImageView so it displays under the balloon_overlay_close ImageView.

No changes are required to the Balloon_Click Sub, save the new XML file and run the project - you can now zoom in using the center.png drawable instead of the previous TextView.

So to summarise:
  • MarkersBalloonOverlay will add Click and LongClick event listeners to any View in balloon layout file that has a tag defined, but:
    • Click event listeners will only be added if your Activity contains the related BalloonClick Sub.
    • LongClick event listeners will only be added if your Activity contains the related BalloonLongClick Sub.
  • You must not remove the balloon_item_title, balloon_item_snippet or close_img_button Views from the balloon layout.
  • The MarkersBalloonOverlay FocusedMarker property can be used to get the Marker whose Balloon is currently being displayed and the same property can be used to programmatically open a Marker's Balloon.
  • You can use any valid Layouts and Widgets in the balloon layout file - your imagination is the limit!

With the release of version 3.00 of OSMDroid i feel that it is now capable of producing professional quality maps.
I am working on a couple of apps that extensively use OSMDroid and as i develop these apps i shall no doubt add new features to the library and fix any bugs...

Have fun and feel free to share any custom balloon layouts that you create.

Martin.
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Is there an example to add a new tile source.

Cheers.

I have not yet implemented the classes to enable you to add a new tile source.

The native Android OSMDroid library contains various classes that i can create wrappers for to enable creating and adding new TileSource objects BUT they all require that the tiles are named according to the Slippy map tilename convention.

  • Each zoom level is a directory, each column is a subdirectory, and each tile in that column is a file.
  • Filename(url) format is /zoom/x/y.png

That Wiki article does state that tiles must also be 256 pixels square and by default OSMDroid does expect tiles to be 256 pixels square.

Take a look at the documentation for the native Android OSMDroid library classes that can be used to add new tile sources: OSMdroid Android 3.0.7 API.

The class to create a wrapper for to enable you to add a new tile source to the B4A library is probably OnlineTileSourceBase or XYTileSource.
Both of those allow you to set the tile size, so in theory you can add tiles of any size as long as they are square.
The built in CloudMadeSmallTiles tile source uses tiles that are smaller than 256 pixels square and i've found that tile source to be very unreliable in use - tiles not loading and the map behaving erratically for example.

So the new tile source that you wish to add should ideally have tiles that are 256 pixels square as well as being named using Slippy map tilename convention.

If your new tile source satisfies those criteria then a wrapper for either the OnlineTileSourceBase or XYTileSource class is simple to write, you can then create your own TileSource object.
I'd then just need to add a new method to the MapView class such as AddTileSource (NewTileSource As TileSource) and everything should work fine.

If however your new tile source tiles are NOT named using the Slippy map tilename convention then you require a customised (Sub class) of either the OnlineTileSourceBase or XYTileSource class...

The MapsForFree tile source included in the B4A library is not part of the native Android library - i added that myself.
MapsForFree is a public domain tile source, more details available from here: Free Relief Layers for Google Maps.
Click the Imprint/Tutorial button top right on that page and then select Tutorial in the popup window.

B4X:
function getAdminLayer() {
   var layer = new GTileLayer(copyrightCollection, min, max);
   layer.getTileUrl = function (a, b) {
      return "http://maps-for-free.com/layer/admin/z" + b + "/row" + a.y + "/" + b + "_" + a.x + "-" + a.y + ".gif";
   };
   return layer;
}

You can see that the MapsForFree tiles are NOT named using the Slippy map tilename convention, so i had to Subclass the OnlineTileSourceBase class.
The Subclass effectively converting the OSMDroid Slippy map tilename to the format used by MapsForFree.

So even if your new tiles source tiles are not named according to the Slippy map tilename convention it is still possible to add them to your MapView but you'd require me to create a custom Subclass just for you - create a Subclass and send it to you BUT NOT add that custom Subclass to the B4A library that is available to all users.
(Unless of course you wanted your new tile source to be available to all B4A library users??).

You also have the option to pack some or all of the tiles from your new tilesource into your B4A application so they are available as offline tiles - no data connection would be required to use those packed tiles.

That sums everything up - sorry for the long post!

Let me have some details on the tile source you want to add and i'll see what i can do.

Thanks.

Martin.
 

Azlan

Member
Licensed User
Longtime User
Hi Martin,

Thanks for the reply.

I have been for the last several years writing mapping engines for desktop and web applications.

I had downloaded the source for OsmDroid some time back with the idea of adding additional tile providers for a upcoming project until I came across your excellent overlay additions for OsmDroid.

Most tile providers use the Slippy map tilename convention or a variation thereof.

I have knocked up a rough demo program (.Net 2.0) for you that gets a single 256x256 tile at various lat/lng and zoom levels for several tile providers and it is posted here.

PM me you email address and I will send you the source as a VS project that has all the URL generation as will as tile x,y and zoom calculations.

You are welcome to use the source in your library as you see fit and I can give you some input about optimization etc.

Cheers

David
 

Attachments

  • MapUrlTest.zip
    13.9 KB · Views: 691

warwound

Expert
Licensed User
Longtime User
Hi.

I can see now which new tile sources you want to use.

A week or so back i found a webpage that detailed how Ovi, Bing, Yahoo and Google tilenames were formatted, but i can't find that webpage now.

If a new tile source doesn't use the default Slippy map tile naming convention then it's relatively simple to override the OSMDroid tile request String and convert it from Slippy map to whatever the new tile source uses.
(As i posted - i did exactly this with the MapsForFree tile source).

The big problem is that Ovi, Bing, Yahoo and Google are not free to use tile providers.

They pay large amounts of money to licence the tiles that their maps display and do not allow others to (legally) access those tiles to display on their own maps.

You could add their tiles to an OSMDroid map and it work work - you can access the tiles - but if the tile provider found out they'd do all they can to block ongoing access to their tiles from your OSMDroid map and in extreme cases would threaten legal action if you didn't stop using their tiles!

In the past few years i've seen a few mobile apps that displayed Google tiles and the apps were very popular but as soon as their popularity grew and Google found out, the app developers were tracked down by Google and forced to remove the Google tiles from their app - the apps tended to die a death after the Google tiles were removed!

A search on Google today shows what seems like more than a few people are still using tiles from these big companies with no permission.

Look here: Get offline maps images from bing maps, google maps, yahoo maps, openstreetmap, nearmap, ovi maps.
That website is even selling Windows apps that will mass download Google, Yahoo, Ovi and Bing maps tiles to enable you to use their tiles in your own application in an offline mode!
I'm surprised they haven't had their arse busted yet lol.

OSMdroid has an add-on class that allows you to add Bing maps as a tile source: /trunk/osmdroid-third-party/src/main/java/org/osmdroid/tileprovider/tilesource/bing/ - osmdroid - OpenStreetMap-Tools for Android - Google Project Hosting.
It requires a Bing API key which makes it look ok but i suspect that a Bing API key allows you to display Bing map tiles in a Bing map and not in a third party app.

OSMDroid also has an add-on class that does much the same with Google's tiles: /trunk/osmdroid-third-party/src/main/java/org/osmdroid/google/overlay/ - osmdroid - OpenStreetMap-Tools for Android - Google Project Hosting.

I think whether or not you still want to use these tile sources is entirely up to you.
If you create an app for your own personal use and add these tile sources you will probably find there is no problem - the number of tiles your app downloads is unlikely to raise the attention of the tile source provider.

BUT if you publish an app with one or more of these tile sources and that app becomes popular then there is a good chance that the tile downloads will attract the attention of the tile source provider who will then come looking for you...

Let me have your comments.

Thanks.

Martin.
 

warwound

Expert
Licensed User
Longtime User
Version 3.10 of OSMDroid introduces support for adding custom TileSources to your MapView

Please read the above thread for more details.

Here's an example of how to create a new TileSource and add it to a MapView.

The TileSource is one i created myself, i googled and found this large GIF image of the world.
I used a tile cutter program to cut that large image into map tiles for zoom levels 0 to 5, all tiles being 256 pixels square.

The tile cutter named the tiles using the Slippy map tile naming convention.

You can only add a new tile source provider to the MapView if that tile source provider uses the Slippy map tile naming convention.

Then i uploaded the tiles to my webspace.

So i now have a new slippy map tile source, let's add it to a MapView:

B4X:
Sub Process_Globals
   Dim MapCenter As GeoPoint
   Dim TileSource As String
   Dim ZoomLevel As Int
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim TileSourceSpinner As Spinner
End Sub

Sub Activity_Create(FirstTime As Boolean)
   MapView1.Initialize("")
   
   If FirstTime Then
      MapCenter.Initialize(0, 0)
      ZoomLevel=2
      
      Dim CurrentTilesSources As List
      CurrentTilesSources=MapView1.GetTileSources
      If CurrentTilesSources.IndexOf("myxytilesource")=-1 Then
         '   the "myxytilesource" has not yet been added to the MapView
         Dim MyXYTileSource As XYTileSource
         
         '   this test TileSource has 256 pixel square JPG tiles covering zoom levels 0 to 5
         '   the tiles do not cover the entire world at all zoom levels
         '   as you zoom in the MapView may make requests for non-existent tiles
         '   404 file not found errors will occur
         '   (this is the default OSMDroid behaviour until it is 'fixed')
         
         MyXYTileSource.Initialize("myxytilesource", 0, 5, 256, ".jpg", "http://android.martinpearman.co.uk/b4a/osmdroid/misc/myxytilesource/")
         MapView1.AddXYTileSource(MyXYTileSource)
         
         '   note that after Activity_Pause (orientation change for example) the added XYTileSource will still exist in the MapView
         '   the added XYTileSorce will exist until the activity process is destroyed
         '   trying to add an XYTileSource more than once will not cause problems however - the AddXYTileSource method ensures that the tile source is only added once
      End If
      
      '   set the default initial TileSource
      TileSource="myxytilesource"
   End If
   
   TileSourceSpinner.Initialize("TileSourceSelect")
   Activity.AddView(TileSourceSpinner, 0, 0, 100%x, 48dip)
   
   TileSourceSpinner.AddAll(MapView1.GetTileSources)
   TileSourceSpinner.Prompt="Select a TileSource"
   TileSourceSpinner.SelectedIndex=TileSourceSpinner.IndexOf(TileSource)
   '   manually call the Spinner ItemClick Sub to sync the MapView TileSource with the spinner SelectedIndex
   TileSourceSelect_ItemClick(TileSourceSpinner.SelectedIndex, TileSourceSpinner.SelectedItem)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   MapView1.Zoom=ZoomLevel
   MapView1.SetCenter3(MapCenter)
   Activity.AddView(MapView1, 0, 48dip, 100%x, 100%y-48dip)
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   MapCenter=MapView1.GetCenter
   ZoomLevel=MapView1.Zoom
   
   '   save the currently selected MapView TileSource
   TileSource=MapView1.GetTileSource
End Sub

Sub TileSourceSelect_ItemClick (Position As Int, Value As Object)
   '   set the MapView TileSource
   MapView1.SetTileSource(Value)
End Sub

It's important that the MapView TileSource is set before the MapView Center and Zoom are set, it's also important to set the TileSource before adding the MapView to the Activity.

See this outstanding issue with the native Android OSMDroid library.

Setting the MapView TileSource before it's center and zoom level and before adding the MapView to the Activity seems to have worked around this issue.

The code comments explain everything i think, the only point to note is that my new tile source imagery doesn't align with the underlying map coordinates.
That's just because i took a large image and chopped it into tiles - that image was not aligned or projected so that it was suitable for map tiles BUT it simply demonstrates how to use the new XYTileSource.

If you add a proper slippy map tile source the tiles will be properly aligned.

Examples for the other new version 3.10 features will be uploaded later.

Martin.
 

Attachments

  • XYTileSourceDemo.zip
    6.5 KB · Views: 798

warwound

Expert
Licensed User
Longtime User
BalloonMarker is a new object introduced with version 3.10 of OSMDroid

Please read the above thread for more details.

Version 3.10 includes various updates to the MarkersBalloonOverlay which was introduced in version 3.00.

It was important to abstract some functionality - the MarkersBalloonOverlay XML layout file was hardcoded to balloon_overlay.xml.

With version 3.10 the MarkersBalloonOverlay Initialize method is changed:

Initialize(aMapView As MapView, EventName As String, BalloonLayoutName As String)

The Initialize method now requires a parameter BalloonLayoutName .

Only 31 B4A users have downloaded version 3.00 todate so i think it reasonable to change that method - the increased functionality it provides more than outweighs the effort it will take those users to update any version 3.00 project code.

Next is a new object the BalloonMarker.

Let's say you create a MarkersBalloonOverlay and don't want to use the same balloon layout for every Marker on that Overlay...

If you want a Marker to display a balloon layout other than the balloon layout defined in the MarkersBalloonOverlay Initialize method then that Marker should be a BalloonMarker and NOT a Marker.

BalloonMarker is the same as Marker BUT has one new property LayoutName:

B4X:
MyBalloonMarker.LayoutName="my_custom_balloon_layout"

If MyBalloonMarker is clicked then the layout file my_custom_balloon_layout.xml is used to create it's balloon layout.

If a BallonMarker's LayoutName is not defined then the default balloon layout will be displayed.

Note that a BalloonMarker is the same basic object as a Marker but has the extra LayoutName property.
You can add a Marker or a BalloonMarker to a MarkersFocusOverlay or a MarkersBalloonOverlay.

BalloonMarker is only useful though if used with a MarkersBalloonOverlay as that overlay is the only object that makes use of the new LayoutName property.

Here's the example code:

B4X:
Sub Process_Globals
   '    a Marker is not an Activity object so can be created as a Process_Global
   '    the Markers will need to be declared as Process_Globals so we can identify and restore the open Balloon on orientation change
   Dim FocusedMarker, Marker0, Marker1, Marker2, Marker3 As Marker
   
   '   we'll use the new BalloonMarker object for Marker4
   Dim Marker4 As BalloonMarker
   
   Dim MapCenter As GeoPoint
   Dim TileSource As String
   Dim ZoomLevel As Int
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim TileSourceSpinner As Spinner
   
   '   create the MarkersBalloonOverlay
   Dim MarkersBalloonOverlay1 As MarkersBalloonOverlay
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.AddMenuItem("Fit Map to Markers", "FitMap")
   '   MapView initialized with no EventName as we'll not be listening for MapView events
   MapView1.Initialize("")
   Activity.AddView(MapView1, 0, 48dip, 100%x, 100%y-48dip)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   
   '   initialize the MarkersBalloonOverlay and add it to the MapView
   '   no EventName is passed as we'll not listen for MarkersBalloonOverlay events in this example
   '   new with version 3.10 of the library is the *requirement* to set the default balloon overlay layout name
   '   i'm using "balloon_overlay", in this project's Objects/res/layout folder the layout file is "balloon_overlay.xml"
   MarkersBalloonOverlay1.Initialize(MapView1, "", "balloon_overlay")
   
   MapView1.AddOverlay(MarkersBalloonOverlay1)
   
   If FirstTime Then
      '   create and initialize the Markers
      
      Marker0.Initialize("", "Description but no title", 52.768, 0.194, Null)
      
      '   for Marker1 i'll use a custom icon
      Dim Icon As BitmapDrawable
      Icon.Initialize(LoadBitmap(File.DirAssets, "my_icon.png"))
      Marker1.Initialize("Home sweet home", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi egestas suscipit convallis. Etiam pellentesque gravida est, quis luctus nunc commodo at. Nam et risus orci. Integer malesuada lorem dolor. Maecenas vestibulum cursus enim, tincidunt luctus libero placerat non. In vitae metus tellus, nec euismod nibh. Phasellus ut quam vitae justo sagittis auctor. Sed vel sapien dolor. Etiam ut sem id dolor iaculis ullamcorper. Aenean eget sem nibh, a tempor augue. Nulla interdum luctus molestie.", 52.75610, 0.39748, Icon)
      '   Marker2 will display the default OSMDroid icon
      '   the default icon is used if Null is passed as the Icon parameter
      Marker2.Initialize("Elsewhere", "Downham Market", 52.60801, 0.39047, Null)
      Marker3.Initialize("Title but no description", "", 52.6959, 0.606, Null)
      
      '   Marker4 is a BalloonMarker NOT a Marker
      Marker4.Initialize("Sunny Hunstanton", "The seaside resort of Hunstanton is very popular with the tourists.", 52.9379, 0.4833, Null)
      '   a BalloonMarker has a property LayoutName which IF SET defines a balloon layout file to be used for this BalloonMarker's balloon
      '   if NOT SET then the BalloonMarker behaves the same as a Marker - it's balloon layout will be the balloon layout defined in the MarkersBalloonOverlay Initialize method
      '   in this project's Objects/res/layout folder i have created "custom_balloon_overlay.xml"
      Marker4.LayoutName="custom_balloon_overlay"
   End If
   
   '   create a List and initialize it with the 3 Markers
   Dim Markers As List
   Markers.Initialize2(Array As Object(Marker0, Marker1, Marker2, Marker3, Marker4))

   '   add the List of Markers to the MarkersBalloonOverlay
   MarkersBalloonOverlay1.AddMarkers(Markers)
   
   If FirstTime Then
      TileSource="Mapnik"
      
      '   fit the MapView to the MarkersBalloonOverlay
      MapView1.FitMapToBoundingBox(MarkersBalloonOverlay1.GetBoundingBox)
   Else
      '   restore saved zoom level and map center
      MapView1.Zoom=ZoomLevel
      MapView1.SetCenter3(MapCenter)
      If FocusedMarker<>Null Then
         '   restore the Balloon if was open before the orientation change (or Activity_Pause)
         MarkersBalloonOverlay1.FocusedMarker=FocusedMarker
      End If
   End If
   
   TileSourceSpinner.Initialize("TileSourceSelect")
   Activity.AddView(TileSourceSpinner, 0, 0, 100%x, 48dip)
   
   TileSourceSpinner.AddAll(MapView1.GetTileSources)
   TileSourceSpinner.Prompt="Select a TileSource"
   TileSourceSpinner.SelectedIndex=TileSourceSpinner.IndexOf(TileSource)
   TileSourceSelect_ItemClick(TileSourceSpinner.SelectedIndex, TileSourceSpinner.SelectedItem)
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   '   save the FocusedMarker
   '   the FocusesMarker is the Marker whose Balloon is being displayed or Null if no Balloon is being displayed
   FocusedMarker=MarkersBalloonOverlay1.FocusedMarker
   MapCenter=MapView1.GetCenter
   TileSource=MapView1.GetTileSource
   ZoomLevel=MapView1.Zoom
End Sub

Sub FitMap_Click
   MapView1.FitMapToBoundingBox(MarkersBalloonOverlay1.GetBoundingBox)
End Sub

Sub TileSourceSelect_ItemClick (Position As Int, Value As Object)
   MapView1.SetTileSource(Value)
End Sub

The comments explain it all.

The default balloon layout defined in MarkersBaloonOverlay1's Initialize method is used for all Marker objects.

Marker4 is actually a BalloonMarker with it's LayoutName set to "custom_balloon_overlay", when Marker4 is clicked the balloon layout displayed is the layout defined in file "Objects/res/layout/custom_balloon_overlay.xml".

Martin.
 

Attachments

  • BalloonMarkersDemo.zip
    45.6 KB · Views: 758

marcick

Well-Known Member
Licensed User
Longtime User
Hi Martin,
your tutorial is great, many thanks !
I'ma beginner and I see OSMDroid is very user-friendly, respect to understand googleMap API ..
before to decide the way to take in my project, I still have to understand this:

  1. is it possible with OSMDroid to have geocoding ? (showing a specific address on the map)
  2. Is it possible to reverse geocode ? ( show the full address o f a specific couple of coordinate )
  3. Is it possible to calculate a route from 2 points and have navigation instruction ?

Thank you.
Marco
 

warwound

Expert
Licensed User
Longtime User
Hi Marco.

The library has no built in geocoding, reverse geocoding or get route capabilities whatsoever.

I think that's primarily because the developers of the original Android library needed to create a free open source library with no commitments to use of 3rd party services.

So your options are to find 3rd party web services that you can use to get the data which you can then display using OSMDroid.

Google have the services that you require BUT their terms and conditions of use state that the data obtained from their web services MUST be displayed on a Google map - not on a 3rd party map such as OSMDroid.

See: https://developers.google.com/maps/terms#section_10_12

The Yahoo web service has the same condition - you must display the data from the web service on a Yahoo map, so i expect the other major providers such as Bing will be just as strict.

Are there any free to use 3rd party web services?
A Google search brings many results but you'd have to look through them to find the ones that provide the services you need and then look at the terms and conditions of use.

Android seems to have a built in Geocoder class which looks like it would be straightforward to wrap in a new library.
The Geocoder class has no find a route methods it seems but can geocode and reverse geocode.
The documentation states:
The Geocoder class requires a backend service that is not included in the core android framework. The Geocoder query methods will return an empty list if there no backend service in the platform. Use the isPresent() method to determine whether a Geocoder implementation exists.
If you use the Geocoder class then there's no guarantee that all devices will have the necessary built in framework to make it work.

Martin.
 

warwound

Expert
Licensed User
Longtime User
I have now created a Geocoder library, see here: http://www.b4x.com/forum/additional-libraries-official-updates/17115-geocoder-library.html#post97857.

The library uses the device's geocoding and reverse geocoding capabilities so there are no problems with terms and conditions of use.

But do read that thread and pay attention to the part that explains some devices will have no inbuilt geocoding and reverse geocoding capabilities.

@Marco - now all you need is a free to use web service that will give you a route between locations.

Martin.
 

marcick

Well-Known Member
Licensed User
Longtime User
Super !!
many many thanks for jour job. I'll play with the library this week.
Marco
 

warwound

Expert
Licensed User
Longtime User
Version 3.20 of OSMDroid adds support for custom TileSources that are served from more than one domain

Many online tile providers serve tiles from more than one domain.
Often serving tiles from sub-domains.
The HTTP connection standard defines that no more than two connections may be made to a single domain at any one times.
So tile providers can serve tile from a number of sub-domains, the limit of2 connections applies to each sub-domain and this effectively means a client can request up to 2 tiles from each sub-domain - speeding up the serving of tiles to the client.

The new XYTileSource method allows you to take advantage of such tile providers:

Initialize2 (Name As String, MinZoomLevel As Int, MaxZoomLevel As Int, TileSize As Int, TileFilenameEnding As String, BaseUrls() As String)

An Array of Strings is passed for the BaseUrls parameter.

This sample code adds a Historic map tile provider to the MapView.
The tile provider is The National Library of Scotland, and you are required to display an attribution in your application if you use this tile provider, see NLS Maps API - Historic Map for use in Mashups - National Library of Scotland.

Note that the Historic tiles only cover the United Kingdom and only cover zoom levels from 0 to 14.

Here's the example code:

B4X:
Sub Process_Globals
   Dim MapCenter As GeoPoint
   Dim TileSource As String
   Dim ZoomLevel As Int
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim TileSourceSpinner As Spinner
End Sub

Sub Activity_Create(FirstTime As Boolean)
   MapView1.Initialize("")
   
   If FirstTime Then
      MapCenter.Initialize(52.75191, 0.40533)
      ZoomLevel=9
      
      Dim CurrentTilesSources As List
      CurrentTilesSources=MapView1.GetTileSources
      If CurrentTilesSources.IndexOf("Historic")=-1 Then
      '   the "Historic" TileSourcehas not yet been added to the MapView
         Dim MyXYTileSource As XYTileSource
         
         '   The Historic map data is provided by <a href="http://geo.nls.uk/maps/api/">The National Library of Scotland</a>, and this map data is licensed under the terms and conditions of the <a href="http://creativecommons.org/licenses/by-sa/3.0/">Creative Commons Attribution 3.0 Unported Licence</a>
         
         '   if you use this tile source in an application you are required to display an attribution, see this webpage for details: http://geo.nls.uk/maps/api/#licence
         
         MyXYTileSource.Initialize2("Historic", 0, 14, 256, ".jpg", Array As String("http://t0.uk.tileserver.com/_os1/r0/", "http://t1.uk.tileserver.com/_os1/r0/", "http://t2.uk.tileserver.com/_os1/r0/", "http://t3.uk.tileserver.com/_os1/r0/", "http://t4.uk.tileserver.com/_os1/r0/"))
      MapView1.AddXYTileSource(MyXYTileSource)
         
         '   note that after Activity_Pause (orientation change for example) the added XYTileSource will still exist in the MapView
         '   the added XYTileSorce will exist until the activity process is destroyed
         '   trying to add an XYTileSource more than once will not cause problems however - the AddXYTileSource method ensures that the tile source is only added once
      End If
      
      '   set the default initial TileSource
      TileSource="Historic"
   End If
   
   TileSourceSpinner.Initialize("TileSourceSelect")
   Activity.AddView(TileSourceSpinner, 0, 0, 100%x, 48dip)
   
   TileSourceSpinner.AddAll(MapView1.GetTileSources)
   TileSourceSpinner.Prompt="Select a TileSource"
   TileSourceSpinner.SelectedIndex=TileSourceSpinner.IndexOf(TileSource)
   '   manually call the Spinner ItemClick Sub to sync the MapView TileSource with the spinner SelectedIndex
   TileSourceSelect_ItemClick(TileSourceSpinner.SelectedIndex, TileSourceSpinner.SelectedItem)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   MapView1.Zoom=ZoomLevel
   MapView1.SetCenter3(MapCenter)
   Activity.AddView(MapView1, 0, 48dip, 100%x, 100%y-48dip)
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   MapCenter=MapView1.GetCenter
   ZoomLevel=MapView1.Zoom
   
   '   save the currently selected MapView TileSource
   TileSource=MapView1.GetTileSource
End Sub

Sub TileSourceSelect_ItemClick (Position As Int, Value As Object)
   '   set the MapView TileSource
   MapView1.SetTileSource(Value)
End Sub

All very much the same as the previous XYTileSource example code.

Sample project attached.

Martin.
 

Attachments

  • HistoricMap.zip
    6.6 KB · Views: 642

warwound

Expert
Licensed User
Longtime User
Version 3.20 of OSMDroid allows you to to position a ScaleBarOverlay top-left or bottom-left of a MapView

I've added a new method to the ScaleBarOverlay:

SetPosition (Position As Int)

Set the position to display the scale bar within the MapView.
Possible values are:
ScaleBarOverlay.BOTTOM_LEFT
ScaleBarOverlay.TOP_LEFT

Default value is ScaleBarOverlay.TOP_LEFT

Prior to this update the ScaleBarOverlay was always positioned at the top-left of the MapView, you can now position it bottom-left if desired.

Some example code:

B4X:
Sub Process_Globals
   Dim MapCenter As GeoPoint
   Dim TileSource As String
   Dim ZoomLevel As Int
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim TileSourceSpinner As Spinner
   
   '   create a ScaleBarOverlay
   Dim ScaleBarOverlay1 As ScaleBarOverlay
End Sub

Sub Activity_Create(FirstTime As Boolean)
   MapView1.Initialize("")
   Activity.AddView(MapView1, 0, 48dip, 100%x, 100%y-48dip)
   
   MapView1.SetMultiTouchEnabled(True)
   MapView1.SetZoomEnabled(True)
   
   If FirstTime Then
      MapCenter.Initialize(52.75192, 0.40505)
      TileSource="Mapnik"
      ZoomLevel=14
   End If
   
   MapView1.Zoom=ZoomLevel
   MapView1.SetCenter3(MapCenter)
   
   TileSourceSpinner.Initialize("TileSourceSelect")
   Activity.AddView(TileSourceSpinner, 0, 0, 100%x, 48dip)
   
   TileSourceSpinner.AddAll(MapView1.GetTileSources)
   TileSourceSpinner.Prompt="Select a TileSource"
   TileSourceSpinner.SelectedIndex=TileSourceSpinner.IndexOf(TileSource)
   TileSourceSelect_ItemClick(TileSourceSpinner.SelectedIndex, TileSourceSpinner.SelectedItem)
   
   '   i'll add an activity menuitem so we can change various properties at run-time
   Activity.AddMenuItem("Do something", "DoSomething")
   
   '   initialize the ScaleBarOverlay and add it to the MapView
   '   the default top-left position is where the ScaleBarOverlay will be positioned
   ScaleBarOverlay1.Initialize(MapView1)
   MapView1.AddOverlay(ScaleBarOverlay1)
   
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
   MapCenter=MapView1.GetCenter
   TileSource=MapView1.GetTileSource
   ZoomLevel=MapView1.Zoom
End Sub

Sub DoSomething_Click
   '   change some ScaleBarOverlay defaults
   ScaleBarOverlay1.SetLineColor(Colors.Red)
   ScaleBarOverlay1.SetLineWidth(4)
   ScaleBarOverlay1.SetOffset(40, 60)
   ScaleBarOverlay1.SetScaleUnit(ScaleBarOverlay1.SCALE_UNIT_IMPERIAL)
   ScaleBarOverlay1.SetTextColor(Colors.Gray)
   ScaleBarOverlay1.SetTextSize(18dip)
   
   '   use the new ScaleBarOverlay SetPosition method to position the ScaleBarOverlay bottom-left
   ScaleBarOverlay1.SetPosition(ScaleBarOverlay1.BOTTOM_LEFT)
   
   '   it is required that the MapView Invalidate method is called to force the MapView to update it's display
   MapView1.Invalidate
End Sub

Sub TileSourceSelect_ItemClick (Position As Int, Value As Object)
   MapView1.SetTileSource(Value)
End Sub

Handy if you want to put some buttons or other Views at the top of the MapView.

Sample project attached.

Martin.
 

Attachments

  • ScaleBarOverlayPosition.zip
    6.2 KB · Views: 661
Top