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,809
Last edited:

Jonas

Member
Licensed User
Longtime User
Im just started looking into making a map software.
I need to be able to rotate the map so the orientation will be heading up, not north up as it is now.

Is that possible with OSMDroid? Or is it a planned feature?

/Jonas
 

warwound

Expert
Licensed User
Longtime User
Im just started looking into making a map software.
I need to be able to rotate the map so the orientation will be heading up, not north up as it is now.

Is that possible with OSMDroid? Or is it a planned feature?

/Jonas

Take a look at this recent post: http://www.b4x.com/forum/additional...16309-osmdroid-mapview-b4a-10.html#post124309

Currently it's not possible and unless the native Android OSMDroid library is updated with this functionality then it's not something i can implement myself.

Martin.
 

warwound

Expert
Licensed User
Longtime User
What about this?

MapView.java - osmdroid - OpenStreetMap-Tools for Android - Google Project Hosting


Seems there are some work done on rotating the map after all.
Dont know if this is implanted into the library or not.

/Jonas

That looks possible but an updated MapView class file is not enough information to start making any changes.
I think i saw this mentioned in the OSMDroid issues or on the OSMDroid group but can't find a link to it now.

Have you got a link to where this updated MapView was originally posted and how to implement it?

That is:
Must any other classes be changed or is it just the MapView class that needs changing.
Once updated we'd just have two new methods getMapOrientation and setMapOrientation would we?
How stable is this update? I don't want to add an update to the library (from an unknown source) if it causes any stability problems.

Do you have any more info about who created this update and full implementation details?

Martin.
 

warwound

Expert
Licensed User
Longtime User
Ok i've put together a library update BUT it's far from perfect...

To start with the MapView source updates that you linked to reference a mMapOverlay setFilterBitmap(), mMapOverlay has no such method.

It's mentioned in the thread you linked to and the poster simply replied:

mReverse is a matrix and don't care about setFilterBitmap

The poster must have copied the MapView source update from elsewhere and not realised that the mMapOverlay class needed updating too.
So the posted update is incomplete...

The attached OSMDroid alpha (!) MapView has a new property named MapOrientation.
You can get or set the MapOrientation in degrees.

And the attached B4A project displays a MapView rotated by 45 degrees.

The attached screenshot shows why i'm not going to officially add this update to the B4A library!

I remember now reading threads about map rotation and OSMDroid and the blank corner you see in the screenshot shows why you can't simply rotate the MapView Canvas object and expect everything to work perfectly.
OSMDroid is written so that it creates a square Canvas the size of the device screen and fills that Canvas with map imagery.

Rotating the Canvas will have unpredictable results as there is no guarantee that the rotated Canvas will have map imagery in areas of the Canvas that would not have been visible if the Canvas had not been rotated.

Anyway - try the attached demo and library files and report back.
Let us know if the rotated map is generally useable or whether the blank blank parts of the Canvas make the map unusable.

Martin.
 

Attachments

  • rotated_mapview.png
    rotated_mapview.png
    92 KB · Views: 591
Last edited:

Jonas

Member
Licensed User
Longtime User
Thank you for updating the library

Ive been testing some now, both with your test sample and with some of your tutorial samples.

I expected the black corner already before your update.
What I did to almost remove it is to Initialize the mapview with about 1.5 times outside the screen resolution. For my Razr that would be:
Screen resolution: 960x540
B4X:
Activity.AddView(MapView1, -180, -320, 900, 1600)

I know this is not the way to go but it fixes the black corner issue 99% of the time. And most of the time the black corner will disappear if the map is move just a little bit. If there were an option to tell OSMDroid to download tiles and draw the mapview larger then the screen the problem would disappear with the consequence of higher data and memory usage.

But for my application it really doesn't matter because I'm going to use a blank black map and throw out navigation beacons as markers.

So at the moment its just prefect for me.
But, I have no problem that a marker is rotating with the map but the marker text dialog is also rotating. Try tutorial 10 - OverlayMapMarkersFocusOverlay and click a marker.
If it was possible to reposition the text dialog so the text always would be readable would be great!
But I assume there is no code for that in the OSMDroid lib code.

Thank again and I can now really start making my navigation app!

/Jonas
 

Jonas

Member
Licensed User
Longtime User
I was about to place my markers on the map when I discovered that its not possible to choose where the center in the image should be. My research show that bottom center is default.
But there is an option in the OSMDroid library called SetMarkerHotspot() that is not yet implanted in the B4A version.

The Hotspots are:
CUSTOM, // indicates the mMarker is set by the user
CENTER,
BOTTOM_CENTER, // default
TOP_CENTER,
RIGHT_CENTER,
LEFT_CENTER,
UPPER_RIGHT_CORNER,
LOWER_RIGHT_CORNER,
UPPER_LEFT_CORNER,
LOWER_LEFT_CORNER

The custom option sounds very interesting.

I figured that if I can determine which markers that are visible I can make a bitmap with the icon and text dialog and place the marker onto the map. When the map is then rotated I redraw the bitmap so the text is rotated and replace the marker on the map. Then the text will always be readable.

I hope this future is not to hard to implant

/Jonas
 

warwound

Expert
Licensed User
Longtime User
OSMDroid version 3.40 allows you to rotate the MapView

This update is not perfect, the MapView is a square canvas and rotating it around it's center sometimes means that a part of the MapView will display as blank, see this thread for more info:http://www.b4x.com/forum/basic4andr...droid-mapview-b4a-tutorial-15.html#post126163.
This is not something that i can fix, but i've added the feature to the library as 'experimental' for those that wish to experiment.

Here's a sample project that starts by displaying the map rotated 45 degrees clockwise.
You can use the device Menu to rotate the map clockwise or anti-clockwise in 10 degree increments.

B4X:
Sub Process_Globals
End Sub

Sub Globals
   Dim MapView1 As MapView
   Dim Orientation As Int
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
   
   MapView1.Initialize("")
   Activity.AddView(MapView1, 0, 0, 100%x, 100%y)
   MapView1.SetZoomEnabled(True)
   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)
   
   Orientation=45
   MapView1.MapOrientation=Orientation
   
   Activity.AddMenuItem("Rotate-", "Menu")
   Activity.AddMenuItem("Rotate+", "Menu")
   
End Sub

Sub Activity_Resume
End Sub

Sub Activity_Pause (UserClosed As Boolean)
End Sub

Sub Menu_Click
   Dim Action As String
   Action=Sender
   Select Action
      Case "Rotate-"
         Orientation=Orientation-10
         If Orientation<0 Then
            Orientation=Orientation+360
         End If
         MapView1.MapOrientation=Orientation
      Case "Rotate+"
         Orientation=Orientation+10
         If Orientation>360 Then
            Orientation=Orientation-360
         End If
         MapView1.MapOrientation=Orientation
   End Select
End Sub

Have fun but please don't ask me if i can fix the blank areas that sometimes appear - that would require a lot of time and involve making major changes to the underlying code.

Martin.
 

Attachments

  • MapOrientation.zip
    5.8 KB · Views: 518

warwound

Expert
Licensed User
Longtime User
OSMDroid version 3.40 allows you to set the MarkerHotspot

The MarkerHotspot is the offset where the Marker will be anchored to the MapView.
Possible values are:
  • CENTER
  • BOTTOM_CENTER (default)
  • TOP_CENTER
  • RIGHT_CENTER
  • LEFT_CENTER
  • UPPER_RIGHT_CORNER
  • LOWER_RIGHT_CORNER
  • UPPER_LEFT_CORNER
  • LOWER_LEFT_CORNER
  • NONE

By default a Marker will be anchored to the map so that it's GeoPoint is at the center and bottom of it's icon.

Some sample code that displays a single Marker with the default MarkerHotspot of BOTTOM_CENTER, use the device Menu to change the MarkerHotspot to TOP_CENTER.

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 Marker1 As Marker
   Dim MarkersOverlay1 As MarkersOverlay
   Dim TileSourceSpinner As Spinner
End Sub

Sub Activity_Create(FirstTime As Boolean)
   Activity.AddMenuItem("Change Marker1 Hotspot", "Menu")
   
   '   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 MarkersOverlay and add it to the MapView
   '   an EventName is required as we will listen for the two events that MarkersOverlay generates
   MarkersOverlay1.Initialize(MapView1, "MarkersOverlay1")
   MapView1.AddOverlay(MarkersOverlay1)
   
   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)
   
   Dim Markers As List
   Markers.Initialize2(Array As Object(Marker1))
   
   '   add the List of Markers to the MarkersOverlay
   MarkersOverlay1.AddMarkers(Markers)
   
   If FirstTime Then
      MapCenter=Marker1.GeoPoint
      TileSource="Mapnik"
      ZoomLevel=MapView1.GetMaxZoomLevel
   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)
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 MarkersOverlay's two events

Sub MarkersOverlay1_Click(Title As String, Description As String, Point As GeoPoint)
   Log("MarkersOverlay1_Click")
   ToastMessageShow(Title&": "&Description, True)
End Sub

Sub MarkersOverlay1_LongClick(Title As String, Description As String, Point As GeoPoint)
   Log("MarkersOverlay1_LongClick")
   ToastMessageShow(Title&": Latitude="&Point.Latitude&", Longitude="&Point.Longitude, True)
   
   '   zoom the map in and center (with animation) on the LongClicked Marker
   MapView1.Zoom=MapView1.GetMaxZoomLevel-1
   MapView1.AnimateTo3(Point)
End Sub

Sub Menu_Click
   Dim Action As String
   Action=Sender
   Select Action
      Case "Change Marker1 Hotspot"
         Marker1.MarkerHotspot=Marker1.HOTSPOT_PLACE_TOP_CENTER
         MapView1.Invalidate
   End Select
End Sub

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

@Jonas - You mentioned a CUSTOM MarkerHotspot option but i can't find that in any of the OSMDroid classes, if you can point me to where it is documented i'll try and add that as an option too.

Martin.
 

Attachments

  • MarkerHotspot.zip
    39.4 KB · Views: 609

Jonas

Member
Licensed User
Longtime User

Jonas

Member
Licensed User
Longtime User
I encountered a problem when adding markers.
My app is working on all devices I have around me but when I tried it on my friends phone (HTC Sensation XL, ICS 4.0.3) the program crashes with following error message:

"An error has occurred in sub:
main_createvormarkers (java line : 412)
android.content.res.
Resources$NotFoundException: Resource ID #0x0"

In main.java at line 412 it looks like this:
B4X:
405 public static String  _createvormarkers(boolean _fromcsv) throws Exception{
406 anywheresoftware.b4a.objects.drawable.BitmapDrawable _icon = null;
407 int _i = 0;
408 String[] _cells = null;
409 uk.co.martinpearman.b4a.osmdroid.views.overlays.wrappers.MyOverlayItemWrapper _marker1 = null;
410 //BA.debugLineNum = 130;BA.debugLine="Sub CreateVORMarkers(FromCSV As Boolean)";
411 //BA.debugLineNum = 131;BA.debugLine="VORMarkers.Initialize";
412 mostCurrent._vormarkers.Initialize();
413 //BA.debugLineNum = 132;BA.debugLine="MarkersOverlay2.Initialize(MapView1, \"MarkersOverlay2\")";
414 mostCurrent._markersoverlay2.Initialize(mostCurrent.activityBA,(org.osmdroid.views.MapView)(mostCurrent._mapview1.getObject()),"MarkersOverlay2");
415 //BA.debugLineNum = 133;BA.debugLine="MapView1.AddOverlay(MarkersOverlay2)";
416 mostCurrent._mapview1.AddOverlay((org.osmdroid.views.overlay.Overlay)(mostCurrent._markersoverlay2.getObject()));
417 //BA.debugLineNum = 135;BA.debugLine="Dim Icon As BitmapDrawable";
418 _icon = new anywheresoftware.b4a.objects.drawable.BitmapDrawable();
419 //BA.debugLineNum = 138;BA.debugLine="Dim i As Int";

VORMarkers is Dimmed in Sub Globals

Anyone who knows what this can be?

/Jonas
 
Last edited:

warwound

Expert
Licensed User
Longtime User
I would have guessed that you've not included some resources from the library archive in your app but that wouldn't explain why your app works on some devices but not on others.
The library archive contains two library_resource sub-folders library_default_resources and markers_balloon_overlay_resources.

A resource id of zero indicates that some code has tried to get a resource id and failed - trying to get a resource id for a resource that does not exist will return zero.

If your app works on other devices and you're sure that all of your app's resources are included then it might be that some code is trying to access a native Android resource and the Sensation (for some reason) hasn't got that resource included in it's firmware.

Can you post your Activity so i can see exactly what objects are being used and what resources are required?

Martin.
 

Jonas

Member
Licensed User
Longtime User
Im not sure what you mean by "post your Activity"


Btw, did you take a look on the link to the Custom option?
 

warwound

Expert
Licensed User
Longtime User
Im not sure what you mean by "post your Activity"


Btw, did you take a look on the link to the Custom option?

Can you export your project as a zip file and upload it to this thread?
If you'd rather not then just upload the activity file where this error occurs.
(That'll be your project's .b4a file if the error is in the Main activity otherwise one of your project's .bas files).

Haven't had time to look at the custom marker hotspot yet - i'll hopefully get around to that in the next couple of days.

Martin.
 

Jonas

Member
Licensed User
Longtime User
I managed to get it running on the HTC SXL. I added all the library resources and it worked. I just find it strange it works on all my devices without the library resources.

Now its full steam ahead!

Thanks for the assistance
/Jonas
 

Jonas

Member
Licensed User
Longtime User
I'm trying to optimize PathOverlay.

I have a long list of coordinates of separate paths.
I put those coordinates into a list of Double.
I then make a loop going through the list and addpoints to the PathOverlay object and re-Dim and re-Initialize the object each time I'm going to add a new path. ATM I have 970 paths I want to add.

I figured I could make a list of Geopoints by doing like this:
B4X:
Dim GeoList as List

GeoList.Initialize

For i = 0 to ListOfCoords.Size -1
   Dim CoordDouble()  as Double
   Dim GPoint as GeoPoint
   CoordDouble = ListOfCoords.Get(i)
   GPoint.Initialize(CoordDouble(0),CoordDouble(1))
   GeoList.Add(GPoint)
Next

And then let the PathOverlay read the list by using:
B4X:
PathOverlay1.AddPoints(GeoList)

But I end up with an Exception (java.lang.ClassCastException: org.osmdroid.util.GeoPoint)

But if I add all points with:
B4X:
PathOverlay1.AddPoint(CoordDouble(0),CoordDouble(1))

and then save the Geolist:

B4X:
GeoList = PathOverlay1.GetAllPoints

I can then read back the GeoList without any problem with PathOverlay.

So is it possible to create a GeoPoint List that is readable by PathOverlay and is not created by the PathOverlay?

And what is the best practice to deal with multiple paths? I'm thinking of hiding and showing the path layer. Now I have to MapView1.RemoveOverlays and then loop through my list of coordinates and add new PathOverlay objects to the MapView when I want to show the paths again.

/Jonas
 

warwound

Expert
Licensed User
Longtime User
Hi again.

I've attached an update that i hope will fix your problem.
Can you test it and let me know if it works?

If the update works then i think i'd suggest that when you first create a path, that you use the (now working) List of GeoPoints method and keep a global reference to that List.
Maybe you could create a global Map object where the Map keys are some sort of unique id for each path and each key's value is a List of GeoPoints.
When you want to display a path check if that path's unique id exists as a key in the Map.
If it does then add the already created List of GeoPoints to the PathOverlay.
If that path's key does not exist then create a List of GeoPoints, add them to the Map and add them to the PathOverlay.
(Cache each List of GeoPoints as you create them - if you run into out of memory problems caching lots of Lists then you'll need a solution to discard some cached Lists).

Martin.
 

Attachments

  • osmdroid_v3.41_test.zip
    316.1 KB · Views: 607

mortenplaya

Member
Licensed User
Longtime User
Auto rotate map

Hi,
Martin, thanks for adding new and exiting functionality to the OSMdroid lib.
I have been testing the new "alpha" Maprotation method to auto rotate the tile canvas in real time using the magnetic compass bearing.
This method is working fine with tile sources and simple map overlays except in combination with the ScaleBarOverlay and MiniMapOverlay.
I have also tested the map orientation in combination with the navigation and MyLocationOverlay enabling the compass and GPS sensor. The compass icon redraws correctly, however the navigation icon behaves a little bit odd. It seems like the canvas of the navigation icon is getting randomly scaled when real time map rotation and "MyLocation" is enabled. Although the location of the icon on the map seems be correct.
Have other members of the forum been testing real time navigation and map rotation functionality?

Example code:
B4X:
Sub Process_Globals
Dim Compass As PhoneSensors
...
Sub Globals
Dim AngleStep As Int : AngleStep = 15 'Minimum change of magnetic bearing before rotating map
Dim MagneticDeclination As Int: MagneticDeclination = 0' Location dependent magnetic declination http://magnetic-declination.com/
'http://en.wikipedia.org/wiki/Magnetic_declination
...
Sub Activity_Resume
Compass.StartListening("Orientation")
...
Sub Orientation_SensorChanged(Values() As Float)
   Dim degrees As Float, x As Int
   x=360-((720+Values(0))) Mod 360-MagneticDeclination
   If Compassbearing <> x Then
      Compassbearing = x
      If  Abs(Compassbearing-MapView1.MapOrientation)>AngleStep Then
         MapView1.MapOrientation=Compassbearing
      End If   
   End If
End Sub


Morten
 
Top