When I pass by an area that does not get much love on Openstreetmap, I end up fixing some things myself. If I stay there for a while though, my offline maps on OsmAnd become outdated. OsmAnd maps only get an update once a month, so my map fixes often will not be visible till after I have left the area. After a few days of improving the map though, it would be handy if what I have done so far is actually visible in my offline map. Then I can further improve on that instead of on outdated information. To get around this limitation, I had a look at how to generate the OsmAnd map files (.obf) myself.

Seems to be simple enough. Go to Geofabrik to download the area of your choice. In my particular case: Pennsylvania, USA. Download the .osm.pbf file. Next go to the OsmAnd website and download OsmAndMapCreator, currently on the right side column of that downloads page. It is a Java .jar file with its libraries and some wrapper scripts to run the .jar file.

Open OsmAndMapCreator with the provided bash script OsmAndMapCreator.sh. A window will appear. Open the menu “File → Create .obf from osm file…”. Select the .osm.pbf file you downloaded before and it will start processing it. So far so good!

… and then it crashed on me at 67% processed.

java.lang.OutOfMemoryError: Java heap space
	at gnu.trove.map.hash.TLongObjectHashMap.rehash(TLongObjectHashMap.java:168)
	at gnu.trove.impl.hash.THash.postInsertHook(THash.java:387)
	at gnu.trove.map.hash.TLongObjectHashMap.doPut(TLongObjectHashMap.java:269)
	at gnu.trove.map.hash.TLongObjectHashMap.put(TLongObjectHashMap.java:240)
	at net.osmand.obf.preparation.OsmDbCreator.getConvertId(OsmDbCreator.java:222)
	at net.osmand.obf.preparation.OsmDbCreator.convertId(OsmDbCreator.java:122)
	at net.osmand.obf.preparation.OsmDbCreator.acceptEntityToLoad(OsmDbCreator.java:368)
	at net.osmand.osm.io.OsmBaseStorage.acceptEntityToLoad(OsmBaseStorage.java:313)
	at net.osmand.osm.io.OsmBaseStoragePbf$1.registerEntity(OsmBaseStoragePbf.java:43)
	at net.osmand.osm.io.OsmBaseStoragePbf$1.parseWays(OsmBaseStoragePbf.java:195)
	at crosby.binary.BinaryParser.parse(BinaryParser.java:104)
	at crosby.binary.BinaryParser.handleBlock(BinaryParser.java:51)
	at crosby.binary.file.FileBlock.process(FileBlock.java:120)
	at crosby.binary.file.BlockInputStream.process(BlockInputStream.java:15)
	at net.osmand.osm.io.OsmBaseStoragePbf.parseOSMPbf(OsmBaseStoragePbf.java:214)
	at net.osmand.obf.preparation.IndexCreator.extractOsmToNodesDB(IndexCreator.java:297)
	at net.osmand.obf.preparation.IndexCreator.initDbAccessor(IndexCreator.java:385)
	at net.osmand.obf.preparation.IndexCreator.generateIndexes(IndexCreator.java:618)
	at net.osmand.obf.preparation.IndexCreator.generateIndexes(IndexCreator.java:538)
	at net.osmand.swing.OsmExtractionUI$18.run(OsmExtractionUI.java:819)
	at net.osmand.swing.ProgressDialog$WorkerThread.run(ProgressDialog.java:87)

I tried fiddling with the memory mentioned in the .sh file.

JAVA_OPTS="-Xms256m -Xmx4096m"

To no avail. It keeps on crashing at 67% of Pennsylvania processed.

I added the -XX:+PrintFlagsFinal flag to the java call to help debug. Adding this flag spews out all the configuration keys and their values after starting up. I used it to check whether the heapsize (uintx MaxHeapSize) was correctly at 4GB. It seemed to be. I upped it to 8GB, still crashing. Then I noticed it was not increasing in the -XX:PrintFlagsFinal output though. The $JAVA_OPTS variable was correctly being set, but Java seemed to not pick up on it. Perhaps 4GB had simply been the default. I instead edited OsmAndMapCreator.sh so that the flags were directly in the java command. Can’t ignore the flag then, surely. That indeed seemed to do the trick and OsmAndMapCreator went through processing all of the Pennsylvania file.

For reference, the original .osm.pbf is 207 MB, the final .obf file is 420 MB. I don’t know what size is enough for the heap, but 4 GB was not enough.

For further reference, this is what my OsmAndMapCreator.sh file looked like in the end. Most important part is on the final line where I added the heap size flags directly.

DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
if [ -z "$JAVA_OPTS" ]; then
	JAVA_OPTS="-Xms256m -Xmx8192m"
java -Xms256m -Xmx8192m -XX:+PrintFlagsFinal -Djava.util.logging.config.file="$DIR/logging.properties" -jar "$DIR/OsmAndMapCreator.jar"

With the .obf file generated, it is just a matter of putting it on your phone. Find the file in ~/osmand/ and copy it to your phone. Specifically it needs to end up in the folder /Android/data/net.osmand.plus/files/ on your phone. I make it override the existing file for Pennsylvania (Us_pennsylvania_northamerica.obf). I did not test whether that was required, but I figured this was a sure fire way for OsmAnd to pick up on it. I had closed OsmAnd entirely (swiping it out of the active applications), I do not know if that is required. When I opened it up again, it correctly had my updated map area though. Great success!

Yet to find out: will it pick up on the official OsmAnd update of the PA file still?

Later update: Maybe JAVA_OPTS is not some special environment variable to begin with and I should have just explicitly added it in the java command? Oh well.

Still later update: I probably should have checked the OSM wiki beforehand. Of course they have a page about OsmAndMapCreator already. Guess this was just a learning experience for me.