Android Question OSMdroid Mapview and Text overlays

Kevin Hartin

Active Member
Licensed User
I am close to releasing my Tour Samoa app and would like to overlay village names on my Map. This is necessary for my search capability, but also for ease of navigation for tourists.

I currently have about 6 types of Points Of Interest each with a different icon, which is great, but none of the maps I have available has a comprehensive list of villages in their tiles. What I would like to do is place them like markers, but instead of icons, have a small text marker instead. I would have to enable them in groups based on zoom levels and available space on the map.

The alternative is to put in a heap of transparent PNGs, but with more than 100 villages it becomes a bit cumbersome and a pain to produce all the images.

Thanks,
Kev
 

emexes

Expert
Licensed User
I haven't done maps with B4A yet, but I imagine the markers are a list of icon/graphics and their longitude/latitude, which get overlaid by OSMDroid on to the map.

You could create the transparent images from your text list of villages by Canvas.DrawText'ing the village name to a new BitMap (icon?), and passing the result plus longitude/latitude to OSMDroid.

Still mildly cumbersome, but no longer a pain.
 
Upvote 0

emexes

Expert
Licensed User
It'll certainly make nice bitmaps with a transparency channel that you should be able to pass to OSMDroid, and I expect that they can be saved as PNG files (but I haven't done it).
 
Upvote 0

emexes

Expert
Licensed User
So canvas.bitmap can create an image from text and then I can save it as a transparent png?

The sample code of the post Add a MarkersOverlay layer to the MapView suggests that you can just pass the Bitmap to OSMDroid, no need to write it to a file and read it back (although in this example, he is loading the Bitmap from a file rather than creating the Bitmap programmatically).
B4X:
'   for Marker1 i'll use a custom icon
Dim Icon As BitmapDrawable
Icon.Initialize(LoadBitmap(File.DirAssets, "my_icon.png"))
 
Dim Marker1 As Marker
Marker1.Initialize("Home sweet home", "Lorem ... molestie.", 52.75610, 0.39748, Icon)
edit: it also suggests that the author is a mad soccer fanatic
 
Upvote 0

emexes

Expert
Licensed User
So canvas.bitmap can create an image from text and then I can save it as a transparent png?

There was something recently about the trips and traps of measuring text size, and I've mucked around a little, and... yeah, the MeasureStringXxx routines are not pixel-accurate.

So I've gotten as far as painting the text to a BitMap, and then scanning the BitMap to find the outermost non-background pixels, and then drawn a red border two pixels out from that (one pixel padding and one pixel border width), and it looks like this:

TempTextMeasurement.png


but it's Thursday night = Professional Gamblers Dinner Night = I gotta get moving ;-)

Between the sample code above and below, and knowing that there is a Canvas.DrawBitmap method, you should be on the home straight with respect to generating markers in memory to pass to OSMDroid.

B4X:
Sub MakeTextBitmap(S As String)

    Log("MakeTextBitmap " & S)
   
    Dim B As Bitmap
    B.InitializeMutable(300,300)
   
    Dim C As Canvas
    C.Initialize2(B)
   
    Dim R As Rect
    R.Initialize(0, 0, 300, 300)
   
    C.DrawRect(R, Colors.ARGB(64,128,128,64),True,0)
   
    Dim TextWidth As Float = C.MeasureStringWidth(S,Typeface.SANS_SERIF, 15)
    Dim TextHeight As Float = C.MeasureStringHeight(S, Typeface.SANS_SERIF, 15)
   
    Log(TextWidth & " " & TextHeight)
   
    C.DrawText(S, 5, 5+TextHeight, Typeface.SANS_SERIF, 15, Colors.Yellow, "LEFT")
   
    Dim MinX As Int = 9999
    Dim MaxX As Int = -9999
    Dim MinY As Int = 9999
    Dim MaxY As Int = -9999
   
    Dim BlankPixel As Int = B.GetPixel(0, 0)
    For X = 0 To B.Width - 1
        For Y = 0 To B.Height - 1
            If B.GetPixel(X, Y) <> BlankPixel Then
                If X < MinX Then MinX = X
                If X > MaxX Then MaxX = X
                If Y < MinY Then MinY = Y
                If Y > MaxY Then MaxY = Y
            End If
        Next
    Next
   
    Log (MinX & " " & MinY & " " & MaxX & " " & MaxY)
   
    R.Initialize(MinX - 2, MinY - 2, MaxX + 2, MaxY + 2)
    C.DrawRect(R, Colors.Red, False, 1)
   
    Dim IV As ImageView
    IV.Initialize("")
    IV.Bitmap = B
    IV.Visible = True
    Activity.AddView(IV,100, 100, B.Width, B.Height)
   
End Sub
 
Upvote 0

Kevin Hartin

Active Member
Licensed User
OK, been playing with the canvas bitmap thing and it works exactly how I want it to.

HOWEVER....

My list of villages is over 200 at the moment and will get to over 300 eventually, so I need to be able to get them from my Db and dynamically loop through creating each Bitmap and then each marker.

I can read the DB easily and have done so for other markers, which all share the same Bitmap Icon, however I am stuck as to how to do this dynamically without Dim IconName as Bitmap for every one of the 200 plus villages.

I am sure it an easy thing, but as a newbie hack programmer, I am stuck...

Attached is a test project with the DB

Thanks,
kev
 

Attachments

  • TextMarkers.zip
    43.2 KB · Views: 476
Upvote 0

emexes

Expert
Licensed User
That is looking pretty good. I think you'd be better off without having the global Bitmap B though, ie just have the Make function return what it's made:
B4X:
Sub MakeTextBitmap(S As String) As Bitmap
 
    Log("MakeTextBitmap " & S)

    dim C as Canvas    'used twice below

    Dim TempBitmap As Bitmap
    TempBitmap.InitializeMutable(1dip, 1dip)    'I am pretty sure that size doesn't matter here
    C.Initialize2(TempBitmap)
 
    Dim TextWidth As Float = C.MeasureStringWidth(S, Typeface.SANS_SERIF, 4)
    Dim TextHeight As Float = C.MeasureStringHeight(S, Typeface.SANS_SERIF, 4)
 
    Log("TEXT SIZE: " & TextWidth & " " & TextHeight)
 
    Dim NewBitMap As Bitmap
    NewBitMap.InitializeMutable(TextWidth + 4, TextHeight + 8)
    C.Initialize2(NewBitMap)
 
    C.DrawText(S, 2, 2 + TextHeight, Typeface.SANS_SERIF, 4, Colors.Black, "LEFT")
 
    Return NewBitMap
 
End Sub
and then refactoring your code correspondingly:
B4X:
' ### VILLAGES
Dim RS1 As ResultSet
RS1 = SQL1.ExecQuery("SELECT village, latlon FROM villages")

Dim MarkersList As List    'create list once, outside of loop
MarkersList.Initialize

Do While RS1.NextRow
    Dim VillageName As String = RS1.GetString("village")
    Dim VillageLatLon As String = RS1.GetString("latlon")
 
    If VillageLatLon.Contains(",") = False Then
        Log("WTF 58 - latlon no comma")
    End If
 
    Dim LatLon() As String = Regex.Split(",", VillageLatLon)
    Dim Lat As Double = LatLon(0)    'make sure these don't have N/S in them
    Dim Lon As Double = LatLon(1)    'make sure these don't have E/W in them

    Log(VillageName & "|" & VillageLatLon & "|" & Lat & "|" & Lon)
        
    Dim NewMarker As Marker
    NewMarker.Initialize("", "", Lat, Lon, MakeTextBitmap(VillageName))
 
    MarkersList.Add(NewMarker)    'add marker to list
Loop
RS1.Close

MarkersOverlay1.AddMarkers(MarkersList)
Disclaimer: I haven't actually run this code, but hey: what could possibly go wrong?!?!
 
Last edited:
Upvote 0

emexes

Expert
Licensed User
I remember something about a list can be used like an array, or maybe it was an array can be used like a list, in which case you could dispense with MarkersList and just add the NewMarker directly to MarkersOverlay1 eg:
B4X:
Do ...

    Dim NewMarker as Marker
    NewMarker.Initialize(...)

    MarkersOverlay1.AddMarkers(Array as Marker(NewMarker))
Loop
or maybe more simply but less explicitly as:
B4X:
Do ...

    Dim NewMarker(1) as Marker    'NewMarker is an ARRAY of one marker
    NewMarker(0).Initialize(...)

    MarkersOverlay1.AddMarkers(NewMarker)
Loop
Again: what could possibly go wrong?!?!?!

;-)
 
Last edited:
Upvote 0

Kevin Hartin

Active Member
Licensed User
emexes, you are a bloody champion. 99% right!!!

Dim NewMarker As Marker
Dim TextIcon As BitmapDrawable
TextIcon.Initialize(MakeTextBitmap(VillageName))

NewMarker.Initialize("", "", Lat, Lon, TextIcon)

Your sub returned a Bitmap object, when a BitMapDrawable was needed, so I added the stuff in Red.

The TextMarkers populate really fast, but at Zoom level 14 and below they are a mess on top of each other, so I'll group them into the main Villages for Zoom 10-12, a few more from 12-14 and the whole lot for 14+.

Now I just got to stop dreaming up features to add and do a tidy up so I can release 1.0 asap.

Thanks again,
Kev
 
Upvote 0

emexes

Expert
Licensed User
99% right!!!
or perhaps I like to leave at least one typo in each sample code to test whether it's being used or not ;-)

so I'll group them into the main Villages for Zoom 10-12, a few more from 12-14 and the whole lot for 14+
I was thinking you could add them in order from highest-to-lowest population, and for each marker make sure that no previous marker is nearby, but I think your idea of giving each village a minimum-zoom-level-required might work better, given that each zoom level is a specific pixels-to-ground scale (I think... starts with zoom 0 = 256 pixels = 180 degrees of earth surface, something like that, wasn't it? or maybe I'm confusing it with something else)

Now I just got to stop dreaming up features to add and do a tidy up so I can release 1.0 asap.
You're not alone with that "fault" ;-)

I went looking for the Steve Jobs "Real Artists Ship" quote, found this instead, seems apt given we're talking maps:

quote-steve-jobs-all-over-the-map-focus.jpg
 
Upvote 0

emexes

Expert
Licensed User
Hey, I was just thinking about ways to get the village and other POI data for your app, and you're probably already on to this, but just in case: it's pretty easy to edit and add to OpenStreetMap, eg bike-friendly repair and coffee shops, rest areas, toilets, lookouts, bike traps and tricks, etc. And you can add info like village population and alternate/local names, shop phone numbers and hours and services, etc.

I downloaded some data (is XML = bulky but readable). Somebody called Palolo has been hard at work. The data for a random village looks like:

B4X:
<node id="1446311758" visible="true" version="2" changeset="64601338" timestamp="2018-11-17T17:08:33Z" user="pizzaiolo" uid="1772368" lat="-13.5242459" lon="-172.3058240">
  <tag k="name" v="Patamea"/>
  <tag k="place" v="village"/>
  <tag k="wikidata" v="Q16896450"/>
 </node>

and that wikidata value leads to this page which looks like a set of data that might contain useful stuff eg link to Wikipedia entry. But perhaps this is all stuff to leave for Version 2.
 
Upvote 0

Kevin Hartin

Active Member
Licensed User
Haha, guilty as charged.

HOWEVER...

I got all excited prematurely . My test app used OSMDroid 3.0.8??? While my app uses 4.??.

It seems that the application of icons to markers is different and at first glance, somewhat more restrictive with an icon being set to the marker sets rather to each marker that makes up the set.

Need to look further into.it or revert to OSMDroid 3.??.

Tomorrow... it's beer o'clock in Samoa. I know I got one of them restless nights sleep ahead thinking about this stuff.

Kev
 
Upvote 0

emexes

Expert
Licensed User
And this page here has two relevant items:


Marker using a text label instead of an icon. The marker class can render a simple text label for icons without an icon.

Say what?!?! Perhaps the text-to-icons phase is not necessary (although it's been fun ;-)


Fast Overlay - The fast overlay is great if you have a huge number points to render and they all share the same icon.

The "same icon" stipulation suggests that non-fast overlays can use different icons.


edit: although I'm not sure which version/port of OSMdroid of this is; the samples in Java don't bode well for it being the B4A instance
 
Upvote 0

Kevin Hartin

Active Member
Licensed User
Not sure if the Text option is available in the OSMdroid libraries.

Anyway, I spent an hour this morning reverting back to 3.0.8 and have got both Text and Icons going just fine.

HOWEVER...

My "Static" Pin icons are stretched to be huge, despite being only 30x30 pixels. currently searching for a fix to the huge icons.

And my offline maps are not being read now. GRRR!
 
Upvote 0

Kevin Hartin

Active Member
Licensed User
Got OSMdroid 4.xx going. There is a separate method??? needed to allocate a bitmap to the marker before it get added to the list.

Interestingly, OSMd 3.xx has the text nicely sized at 4pt but my 30x30 icons get stretched, while OSMd 4.xx I had to increase the text to 10pt, while my icons seem to be crips at 30x30.

Anyway, enough of the new features, time to clean up and publish.

Thanks for all the help,
kev
 
Upvote 0
Top