B4A Library GoogleMapsExtras

warwound

Expert
Licensed User
Longtime User

Is anyone else using the MapsForgeTileProvider and has a need to use the newer versions of the RenderTheme?
I have updated MapsForgeTileProvider to use the very latest MapsForge source so it now supports the latest version of RenderTheme.
I'm using this update in an android studio project so have not wrapped it into a b4a library.
If anyone is interested then let me know and i'll wrap it.
 

Almora

Well-Known Member
Licensed User
Longtime User

I removed it from my app because it didn't open v5 maps. but if there is such support, I will use it again.
thanks..
 

warwound

Expert
Licensed User
Longtime User
Here is the updated MapsForgeTileProvider.
Compiled with the latest version of MapsForge android source, so supports the latest version of RenderTheme.

MapsForgeTileProvider_v5
Author:
Martin Pearman
Version: 2.0
  • MapsForgeTileProvider
    Methods:
    • Close As void
      Close all resources being used by this tile provider.
    • IsInitialized As boolean
    • Initialize (MapsForgeTileProviderOptions1 As MapsForgeTileProviderOptions)
  • MapsForgeTileProviderOptions
    Methods:
    • LabelsOnly (LabelsOnly As boolean)
      Set whether to render labels only.
      Default value is False.
    • MapDataStore (Folder As java.lang.String, Filename As java.lang.String)
      Set the path to the map database file.
    • RenderLabels (RenderLabels As boolean)
      Set whether to render labels on the tiles.
      Default value is True.
    • CacheLabels (CacheLabels As boolean)
      Set whether labels should be cached.
      Default value is True.
    • IsInitialized As boolean
    • Initialize As void
    • TextScale (TextScale As float)
      Set the scale of text drawn on tiles.
      Default value is 1.0.
    • Transparent (Transparent As boolean)
      Set whether tiles should be rendered as transparent.
      Default value is False.
    • RenderTheme (Folder As java.lang.String, Filename As java.lang.String)
      Set the path to the XML render theme file.
      Default value is to use MapsForge built in render theme.
    • TileCacheCapacity (Capacity As int)
      Set the capacity of the TileCache.
      Default value is 32.
    • NoTileTile (NoTileTile As com.google.android.gms.maps.model.Tile)
      Set a tile to use where there is no coverage from the map database file.
      Default value is the built in Google 'blank tile'.

An example b4a project:

B4X:
Sub Process_Globals
    Private RuntimePermissions1 As RuntimePermissions
End Sub

Sub Globals
    Private GoogleMap1 As GoogleMap
    Private GoogleMapsExtras1 As GoogleMapsExtras
    Private MapFragment1 As MapFragment
End Sub

Sub Activity_Create(FirstTime As Boolean)
    Activity.LoadLayout("Main")
End Sub

Sub Activity_PermissionResult (Permission As String, Result As Boolean)
    If Result Then
       
        If Not(File.Exists(File.DirDefaultExternal, "norfolk_prow.map")) Then
            File.Copy(File.DirAssets, "norfolk_prow.map", File.DirDefaultExternal, "norfolk_prow.map")
        End If
        If Not(File.Exists(File.DirDefaultExternal, "prow_render_theme.xml")) Then
            File.Copy(File.DirAssets, "prow_render_theme.xml", File.DirDefaultExternal, "prow_render_theme.xml")
        End If
       
        Dim MapsForgeTileProviderOptions1 As MapsForgeTileProviderOptions
        MapsForgeTileProviderOptions1.Initialize
        MapsForgeTileProviderOptions1.MapDataStore(File.DirDefaultExternal, "norfolk_prow.map")
        MapsForgeTileProviderOptions1.RenderTheme(File.DirDefaultExternal, "prow_render_theme.xml")
        MapsForgeTileProviderOptions1.Transparent(True)
   
        Dim MapsForgeTileProvider1 As MapsForgeTileProvider
        MapsForgeTileProvider1.Initialize(MapsForgeTileProviderOptions1)
   
        Dim TileOverlayOptions1 As TileOverlayOptions
        TileOverlayOptions1.Initialize
        TileOverlayOptions1.SetTileProvider(MapsForgeTileProvider1)
   
        Dim TileOverlay1 As TileOverlay=GoogleMapsExtras1.AddTileOverlay(GoogleMap1, TileOverlayOptions1)
   
        Dim CameraPosition1 As CameraPosition
        CameraPosition1.Initialize(52.75, 0.44, 10)
        GoogleMap1.MoveCamera(CameraPosition1)
    Else
        Log("PERMISSION_WRITE_EXTERNAL_STORAGE not granted")
    End If
End Sub

Sub MapFragment1_Ready
    GoogleMap1 = MapFragment1.GetMap
    RuntimePermissions1.CheckAndRequest(RuntimePermissions1.PERMISSION_WRITE_EXTERNAL_STORAGE)
End Sub

Sub Activity_Resume
End Sub

The example loads the default GoogleMap MapType and then displays a transparent tile overlay showing public rights of way in Norfolk, UK.
Here's a screenshot:



Library files and example project are too large to upload to the forum so i'm hosting them on my server:
MapsForgeTileProvider_v5_library_files_v2.10.zip
MapsForgeTileProvider_v5_b4a_demo.zip

Be sure to update the demo project's GoogleMaps API key before compiling.

Martin.
 
Last edited:

warwound

Expert
Licensed User
Longtime User
Demo project not compile - require androidsvg-1.4. Something is missing in library or on my side?

If you look at the original demo project you'll see (commented out):

B4X:
'    #AdditionalJar: androidsvg-1.4
'    #AdditionalJar: kxml2-2.3.0
    #AdditionalJar: MapsForgeTileProvider_v5.aar

I wondered if these 2 jar libs were required, commented them out and the demo still compiled and worked for me, so i assumed that these 2 libs were not required.

I've now uncommented those lines from the demo and added the 2 jar libs to the 'MapsForgeTileProvider_v5_library_files_v2.0.zip' archive.
So download both demo and library files archives again and try to compile.
 

M6SOFT

Member
Licensed User
Longtime User
Now works fine. Thanks a lot. One question: is it neccesary to set up RenderTheme? In my tests without theme map is blank. Looks like there is no default theme.
 

warwound

Expert
Licensed User
Longtime User
Now works fine. Thanks a lot. One question: is it neccesary to set up RenderTheme? In my tests without theme map is blank. Looks like there is no default theme.

The default render theme is bundled in MapsForgeTileProvider_v5.aar and should be compiled into your b4a apk.
If no render theme is set in your MapsForgeTileProviderOptions then InternalRenderTheme.OSMARENDER is used:

B4X:
DEFAULT("/assets/mapsforge/default.xml"),
OSMARENDER("/assets/mapsforge/osmarender.xml");

These xml render theme files are probably (!) located at "/assets/default.xml" and "/assets/osmarender.xml" in your apk so are not found by InternalRenderTheme, i failed to put the files in a subfolder named 'mapsforge'.
I've fixed that now and uploaded the fixed MapsForgeTileProvider_v5_library_files_v2.0.zip to my server.
So download that archive again and see if the default render theme is now found.
 

M6SOFT

Member
Licensed User
Longtime User
So download that archive again and see if the default render theme is now found.
Default theme works now without problems. But there is one more weird problem. Roads, symbols, borders of buildings are very thick. Please take a look on screenshots. First is from PC (old mapsforge tile provider render similar), same map file, default theme, same area. Looks like problem with scaling, DPI or something similar. I tested v4 and v5 maps with different themes. Results are the same.
Screenshot from PC: https://gyazo.com/fdb5dfb45b79380b037d07eefa11eb97
Screenshot form Android: https://gyazo.com/5a6c7f0b491e1753fff14413fdc04240
 

warwound

Expert
Licensed User
Longtime User

I noticed the scaling issue too but as i'm only using this library to display ways i simply reduced the stroke-width in the render theme and everything looked ok.
I'm not sure where to look to debug this - a google search is probably best to see if others have reported a similar issue.
 

M6SOFT

Member
Licensed User
Longtime User
OK, i will search and will let know if find something. Thanks for Your effort, really appreciate
 

warwound

Expert
Licensed User
Longtime User

I'm not sure if either of those two posts have a solution - it's Monday and the start of my working week so i probably won't have much time to look at this until the weekend.

Meanwhile i'll post the source code of the MapsforgeTileProvider:

B4X:
package org.mapsforge.android.maps;

import android.app.Application;
import android.content.Context;
import android.util.Log;

import com.google.android.gms.maps.model.TileProvider;

import org.mapsforge.core.graphics.TileBitmap;
import org.mapsforge.core.model.Tile;
import org.mapsforge.map.android.graphics.AndroidGraphicFactory;
import org.mapsforge.map.datastore.MapDataStore;
import org.mapsforge.map.layer.cache.InMemoryTileCache;
import org.mapsforge.map.layer.cache.TileCache;
import org.mapsforge.map.layer.hills.HillsRenderConfig;
import org.mapsforge.map.layer.labels.TileBasedLabelStore;
import org.mapsforge.map.layer.renderer.DatabaseRenderer;
import org.mapsforge.map.layer.renderer.RendererJob;
import org.mapsforge.map.model.DisplayModel;
import org.mapsforge.map.model.FixedTileSizeDisplayModel;
import org.mapsforge.map.rendertheme.InternalRenderTheme;
import org.mapsforge.map.rendertheme.XmlRenderTheme;
import org.mapsforge.map.rendertheme.rule.RenderThemeFuture;

import java.io.ByteArrayOutputStream;
import java.io.IOException;

public class MapsforgeTileProvider implements TileProvider {

   private static final float MAPSFORGE_TILE_PROVIDER_VERSION=1f;

   private static final String TAG= MapsforgeTileProvider.class.getSimpleName();
   private static final int TILE_SIZE=256;

   private static boolean androidGraphicFactoryCreated=false;

   private final ByteArrayOutputStream byteArrayOutputStream;
   private final boolean cacheLabels;
   private final DatabaseRenderer databaseRenderer;
   private final DisplayModel displayModel;
   private final TileCache inMemoryTileCache;
   private final boolean isTransparent;
   private final boolean labelsOnly;
   private final MapDataStore mapDataStore;
   private final com.google.android.gms.maps.model.Tile noTileTile;
   private final RenderThemeFuture renderThemeFuture;
   private final float textScale;

   public MapsforgeTileProvider(Context context, MapsforgeTileProviderOptions mapsForgeTileProviderOptions){

      if(!androidGraphicFactoryCreated){
         AndroidGraphicFactory.createInstance((Application) context.getApplicationContext());
         androidGraphicFactoryCreated=true;
      }

      byteArrayOutputStream=new ByteArrayOutputStream(mapsForgeTileProviderOptions.outputStreamCapacity);
      cacheLabels=mapsForgeTileProviderOptions.cacheLabels;
      displayModel = new FixedTileSizeDisplayModel(TILE_SIZE);
      inMemoryTileCache = new InMemoryTileCache(mapsForgeTileProviderOptions.tileCacheCapacity);
      isTransparent=mapsForgeTileProviderOptions.isTransparent;
      labelsOnly=mapsForgeTileProviderOptions.labelsOnly;
      this.mapDataStore=mapsForgeTileProviderOptions.mapDataStore;
      noTileTile=mapsForgeTileProviderOptions.noTileTile;
      textScale=mapsForgeTileProviderOptions.textScale;

      renderThemeFuture = new RenderThemeFuture(
            AndroidGraphicFactory.INSTANCE,
            mapsForgeTileProviderOptions.xmlRenderTheme,
            displayModel);
      Thread renderThemeFutureThread = new Thread(renderThemeFuture);
      renderThemeFutureThread.start();

      TileBasedLabelStore tileBasedLabelStore = new TileBasedLabelStore(inMemoryTileCache.getCapacityFirstLevel());

      databaseRenderer = new DatabaseRenderer(
            mapDataStore,
            AndroidGraphicFactory.INSTANCE,
            inMemoryTileCache,
            tileBasedLabelStore,
            mapsForgeTileProviderOptions.renderLabels,
            cacheLabels,
            mapsForgeTileProviderOptions.hillsRenderConfig);

   }

   public void close(){
      try {
         byteArrayOutputStream.close();
      } catch (IOException e) {
         Log.e(TAG, null, e);
      }
      renderThemeFuture.cancel(true);    // needed or useful?
      inMemoryTileCache.destroy();
      mapDataStore.close();
   }

   @Override
   public synchronized com.google.android.gms.maps.model.Tile getTile(int x, int y, int zoom) {
      com.google.android.gms.maps.model.Tile googleTile;

      final org.mapsforge.core.model.Tile mapsforgeTile = new Tile(x, y, (byte) zoom, TILE_SIZE);
      if(mapDataStore.supportsTile(mapsforgeTile)){
         final RendererJob rendererJob = new RendererJob(mapsforgeTile, mapDataStore, renderThemeFuture, displayModel, textScale, isTransparent, labelsOnly);

         try {
            final TileBitmap tileBitmap = databaseRenderer.executeJob(rendererJob);
            inMemoryTileCache.put(rendererJob, tileBitmap);
            tileBitmap.compress(byteArrayOutputStream);
            googleTile=new com.google.android.gms.maps.model.Tile(TILE_SIZE, TILE_SIZE, byteArrayOutputStream.toByteArray());
            byteArrayOutputStream.reset();
         } catch (IOException e) {
            Log.e(TAG, null, e);
            googleTile=null;
         }
      } else {
         googleTile=noTileTile;
      }

      return googleTile;
   }

   public static class MapsforgeTileProviderOptions {

      private static final boolean CACHE_LABELS=true;
      private static final HillsRenderConfig HILLS_RENDER_CONFIG=null;
      private static final boolean IS_TRANSPARENT=true;
      private static final boolean LABELS_ONLY=false;
      private static final int OUTPUT_STREAM_CAPACITY=(256*1024);
      private static final boolean RENDER_LABELS=true;
      private static final float TEXT_SCALE=1f;
      private static final int TILE_CACHE_CAPACITY=32;

      protected boolean cacheLabels=CACHE_LABELS;
      protected HillsRenderConfig hillsRenderConfig=HILLS_RENDER_CONFIG;
      protected boolean isTransparent=IS_TRANSPARENT;
      protected boolean labelsOnly=LABELS_ONLY;
      protected MapDataStore mapDataStore=null;
      protected com.google.android.gms.maps.model.Tile noTileTile=com.google.android.gms.maps.model.TileProvider.NO_TILE;
      protected int outputStreamCapacity=OUTPUT_STREAM_CAPACITY;
      protected boolean renderLabels=RENDER_LABELS;
      protected float textScale=TEXT_SCALE;
      protected int tileCacheCapacity=TILE_CACHE_CAPACITY;
      protected XmlRenderTheme xmlRenderTheme=InternalRenderTheme.OSMARENDER;

      public void setCacheLabels(boolean cacheLabels){
         this.cacheLabels=cacheLabels;
      }

      public void setLabelsOnly(boolean labelsOnly) {
         this.labelsOnly = labelsOnly;
      }

      public void setMapDataStore(MapDataStore mapDataStore) {
         this.mapDataStore = mapDataStore;
      }

      public void setNoTileTile(com.google.android.gms.maps.model.Tile noTileTile){
         this.noTileTile=noTileTile;
      }

      public void setOutputStreamCapacity(int outputStreamCapacity) {
         this.outputStreamCapacity = outputStreamCapacity;
      }

      public void setRenderLabels(boolean renderLabels) {
         this.renderLabels = renderLabels;
      }

      public void setRenderTheme(XmlRenderTheme xmlRenderTheme){
         this.xmlRenderTheme=xmlRenderTheme;
      }

      public void setTextScale(float textScale) {
         this.textScale = textScale;
      }

      public void setTileCacheCapacity(int tileCacheCapacity) {
         this.tileCacheCapacity = tileCacheCapacity;
      }

      public void setTransparent(boolean transparent) {
         isTransparent = transparent;
      }
   }
}
 

M6SOFT

Member
Licensed User
Longtime User
I'm not sure if either of those two posts have a solution - it's Monday and the start of my working week so i probably won't have much time to look at this until the weekend.
Same on me. Maybe someone else will find solution
 

warwound

Expert
Licensed User
Longtime User
Same on me. Maybe someone else will find solution

The only place I see where device scale is handled is in the AndroidGraphicFactory.
My library calls the (static) AndroidGraphicFactory method createInstance() the first time it is initialized.
That creates an instance of AndroidGraphicFactory where this code is executed:
B4X:
private AndroidGraphicFactory(Application app) {
    this.application = app;
    if (app != null) {
        // the scaledDensity is an approximate scale factor for the device
        DisplayModel.setDeviceScaleFactor(app.getResources().getDisplayMetrics().scaledDensity);
    }
}
And that should ensure that anything related to device scale is handled correctly.

I'll look into this more at the weekend.
 

M6SOFT

Member
Licensed User
Longtime User
One observation. On phone tile size isn't 256. I think it is 768 on my device (if i correctly recognizing tile borders in render process). Maybe this is related to wrong scaling.
And i found "FixedTileSizeDisplayModel that always delivers a fixed tile size, mostly for testing. ". Maybe this line in wrapper "displayModel = new FixedTileSizeDisplayModel(TILE_SIZE);" is not correct way on current mapsforge version.
 
Last edited:

warwound

Expert
Licensed User
Longtime User

Backup your existing copy of 'MapsForgeTileProvider_v5.aar' and download this (zipped) version: http://b4a.martinpearman.co.uk/googlemapsextras/MapsForgeTileProvider_v5_tilesize_768.zip
Unzip and overwrite your existing 'MapsForgeTileProvider_v5.aar' then recompile and test.
This version of the library has TILESIZE set to 768.
 

M6SOFT

Member
Licensed User
Longtime User
Yes this did the trick. Map looks correct. I think mapsforge determine tile size according to device. Maybe instead of displayModel = new FixedTileSizeDisplayModel(TILE_SIZE); we need to create displayModel = new DisplayModel(); and not set tile size explicite. But i don't know java and mapsforge lib
 
Cookies are required to use this site. You must accept them to continue using the site. Learn more…