diff --git a/README.md b/README.md index 290c09f..9d4d9ec 100644 --- a/README.md +++ b/README.md @@ -1,40 +1,129 @@ BRouter ======= -BRouter is a configurable OSM offline router with elevation awareness, Java + Android. Designed to be multi-modal with a particular emphasis on bicycle routing. +BRouter is a configurable OSM offline router with elevation awareness, Java + +Android. Designed to be multi-modal with a particular emphasis on bicycle +and energy-based car routing. -For more infos see http://brouter.de/brouter +For more infos see [http://brouter.de/brouter](http://brouter.de/brouter). + + +## BRouter on Android + +You can install the BRouter app on your Android device from +[F-Droid](https://f-droid.org/packages/btools.routingapp) or [Google Play +Store](https://play.google.com/store/apps/details?id=btools.routingapp). You +can also [build BRouter](#build-and-install) yourself. You can find detailed +documentation of the BRouter Android app in +[`misc/readmes/readme.txt`](misc/readmes/readme.txt). Get it on F-Droid Get it on Google Play +### Android with Locus -## Build and Install +You can use BRouter as the offline routing engine for [Locus +Map](https://www.locusmap.eu/) on your Android device. This is currently the +most featureful and maintained solutions for using BRouter on your Android +device. -Compile with (Java 6!): - -> mvn clean install -Dandroid.sdk.path= - -To skip building for Android, add ``-pl '!brouter-routing-app'``. - -Next, download one or more [data file(s)](http://brouter.de/brouter/segments4/) (rd5) into the ``misc/segments4`` directory. - -## Run - -On Linux: -> ./misc/scripts/standalone/server.sh - -On Windows (using Bash): -> ./misc/scripts/standalone/server.sh - -On Windows (using CMD): -> misc\scripts\standalone\server.cmd +A full documentation on how to set this up is available at +[https://www.locusmap.eu/locus-map-can-navigate-offline/](https://www.locusmap.eu/locus-map-can-navigate-offline/). -Related Projects -================ +### Android with OSMAnd -* https://github.com/nrenner/brouter-web -* https://github.com/poutnikl/Brouter-profiles/wiki +Alternatively, you can also use BRouter as the offline routing engine for +[OSMAnd](https://osmand.net/) on your Android device. + +A full documentation on how to set this up is available at +[`misc/readmes/osmand/README.md`](misc/readmes/osmand/README.md). + + +## BRouter on Windows/Linux/Mac OS + +### Build and Install + +To compile BRouter (including the BRouter Android app), use + +``` +mvn clean install -Dandroid.sdk.path= +``` + +If you only want to compile BRouter and the server part (skipping the Android +app), use + +``` +mvn clean install -pl '!brouter-routing-app' +``` + +You can use `-Dmaven.javadoc.skip=true` to skip the JavaDoc processing and +`-DskipTests` to skip running the unitary tests. + + +### Get the required segments (data) files + +Routing data files are organised as 5*5 degree files, +with the filename containing the south-west corner +of the square, which means: + +- You want to route near West48/North37 -> you need `W50_N35.rd5` +- You want to route near East7/North47 -> you need `E5_N45.rd5` + +These data files, called "segments" across BRouter, are generated from +[OpenStreetMap](https://www.openstreetmap.org/) data and stored in a custom +binary format (rd5) for improved efficiency of BRouter routing. + + +#### Download them from brouter.de + +Segments files from the whole planet are generated weekly at +[http://brouter.de/brouter/segments4/](http://brouter.de/brouter/segments4/). + +You can download one or more segments files, covering the area of the planet +your want to route, into the `misc/segments4` directory. + +#### Generate your own segments files + +You can also generate the segments files you need directly from a planet dump +of OpenStreetMap data (or a [GeoFabrik extract](https://download.geofabrik.de/)). + +More documentation of this is available in the +[`misc/readmes/mapcreation.md`](misc/readmes/mapcreation.md) file. + + +### Run the BRouter HTTP server + +Helpers scripts are provided in `misc/scripts/standalone` to quickly spawn a +BRouter HTTP server for various platforms. + +* Linux/Mac OS: `./misc/scripts/standalone/server.sh` +* Windows (using Bash): `./misc/scripts/standalone/server.sh` +* Windows (using CMD): `misc\scripts\standalone\server.cmd` + +The API endpoints exposed by this HTTP server are documented in the +[`brouter-server/src/main/java/btools/server/request/ServerHandler.java`](brouter-server/src/main/java/btools/server/request/ServerHandler.java) +file. + + +## Documentation + +More documentation is available in the [`misc/readmes`](misc/readmes) folder. + + +## Related Projects + +* [nrenner/BRouter-web](https://github.com/nrenner/brouter-web), a web interface on + top of the BRouter HTTP server. An online instance is available at + [http://brouter.de/brouter-web/](http://brouter.de/brouter-web/). +* [poutnikl/Brouter-profiles](https://github.com/poutnikl/Brouter-profiles/wiki), + a collection of BRouter profiles. +* [Phyks/BRouterTesting](https://github.com/Phyks/BrouterTesting), a + collection of test cases for helping develop new BRouter profiles. + + +## License + +BRouter is released under an [MIT License](LICENSE). diff --git a/brouter-codec/src/main/java/btools/codec/MicroCache.java b/brouter-codec/src/main/java/btools/codec/MicroCache.java index 2cb4a8c..8c01988 100644 --- a/brouter-codec/src/main/java/btools/codec/MicroCache.java +++ b/brouter-codec/src/main/java/btools/codec/MicroCache.java @@ -314,4 +314,56 @@ public class MicroCache extends ByteDataWriter } return null; } + + public void calcDelta( MicroCache mc1, MicroCache mc2 ) + { + int idx1 = 0; + int idx2 = 0; + + while( idx1 < mc1.size || idx2 < mc2.size ) + { + int id1 = idx1 < mc1.size ? mc1.faid[idx1] : Integer.MAX_VALUE; + int id2 = idx2 < mc2.size ? mc2.faid[idx2] : Integer.MAX_VALUE; + int id; + if ( id1 >= id2 ) + { + id = id2; + int start2 = idx2 > 0 ? mc2.fapos[idx2 - 1] : 0; + int len2 = mc2.fapos[idx2++] - start2; + + if ( id1 == id2 ) + { + // id exists in both caches, compare data + int start1 = idx1 > 0 ? mc1.fapos[idx1 - 1] : 0; + int len1 = mc1.fapos[idx1++] - start1; + if ( len1 == len2 ) + { + int i = 0; + while( i do nothing + } + } + } + write( mc2.ab, start2, len2 ); + } + else + { + idx1++; + id = id1; // deleted node + } + fapos[size] = aboffset; + faid[size] = id; + size++; + } + } + } diff --git a/brouter-codec/src/main/java/btools/codec/MicroCache2.java b/brouter-codec/src/main/java/btools/codec/MicroCache2.java index b4178fe..8cd8f30 100644 --- a/brouter-codec/src/main/java/btools/codec/MicroCache2.java +++ b/brouter-codec/src/main/java/btools/codec/MicroCache2.java @@ -87,10 +87,15 @@ public final class MicroCache2 extends MicroCache // future escapes (turn restrictions?) short trExceptions = 0; - for(;;) + int featureId = bc.decodeVarBits(); + if ( featureId == 13 ) + { + fapos[n] = aboffset; + validBits[ n >> 5 ] |= 1 << n; // mark dummy-node valid + continue; // empty node escape (delta files only) + } + while( featureId != 0 ) { - int featureId = bc.decodeVarBits(); - if ( featureId == 0 ) break; int bitsize = bc.decodeNoisyNumber( 5 ); if ( featureId == 2 ) // exceptions to turn-restriction @@ -113,6 +118,7 @@ public final class MicroCache2 extends MicroCache { for( int i=0; i< bitsize; i++ ) bc.decodeBit(); // unknown feature, just skip } + featureId = bc.decodeVarBits(); } writeBoolean( false ); @@ -147,7 +153,8 @@ public final class MicroCache2 extends MicroCache TagValueWrapper wayTags = wayTagCoder.decodeTagValueSet(); - if ( wayTags != null ) + boolean linkValid = wayTags != null || wayValidator == null; + if ( linkValid ) { int startPointer = aboffset; sizeoffset = writeSizePlaceHolder(); @@ -162,7 +169,7 @@ public final class MicroCache2 extends MicroCache finaldatasize += 1 + aboffset-startPointer; // reserve place for reverse validBits[ nodeIdx >> 5 ] |= 1 << nodeIdx; // mark target-node valid } - writeModeAndDesc( isReverse, wayTags.data ); + writeModeAndDesc( isReverse, wayTags == null ? null : wayTags.data ); } if ( !isReverse ) // write geometry for forward links only @@ -200,7 +207,7 @@ public final class MicroCache2 extends MicroCache } if ( matcher != null ) matcher.end(); } - if ( wayTags != null ) + if ( linkValid ) { injectSize( sizeoffset ); } @@ -375,6 +382,12 @@ public final class MicroCache2 extends MicroCache int ilon = (int)(id64 >> 32); int ilat = (int)(id64 & 0xffffffff); + if ( aboffset == aboffsetEnd ) + { + bc.encodeVarBits( 13 ); // empty node escape (delta files only) + continue; + } + // write turn restrictions while( readBoolean() ) { @@ -430,7 +443,10 @@ public final class MicroCache2 extends MicroCache readFully( description ); } - boolean isInternal = isInternal( ilonlink, ilatlink ); + long link64 = ((long)ilonlink)<<32 | ilatlink; + Integer idx = idMap.get( Long.valueOf( link64 ) ); + boolean isInternal = idx != null; + if ( isReverse && isInternal ) { if ( dodebug ) System.out.println( "*** NOT encoding link reverse=" + isReverse + " internal=" + isInternal ); @@ -442,9 +458,6 @@ public final class MicroCache2 extends MicroCache if ( isInternal ) { - long link64 = ((long)ilonlink)<<32 | ilatlink; - Integer idx = idMap.get( Long.valueOf( link64 ) ); - if ( idx == null ) throw new RuntimeException( "ups: internal not found?" ); int nodeIdx = idx.intValue(); if ( dodebug ) System.out.println( "*** target nodeIdx=" + nodeIdx ); if ( nodeIdx == n ) throw new RuntimeException( "ups: self ref?" ); diff --git a/brouter-codec/src/main/java/btools/codec/TagValueCoder.java b/brouter-codec/src/main/java/btools/codec/TagValueCoder.java index a733085..1118000 100644 --- a/brouter-codec/src/main/java/btools/codec/TagValueCoder.java +++ b/brouter-codec/src/main/java/btools/codec/TagValueCoder.java @@ -62,6 +62,11 @@ public final class TagValueCoder { if ( ++pass == 3 ) { + if ( identityMap.size() == 0 ) + { + TagValueSet dummy = new TagValueSet(); + identityMap.put( dummy, dummy ); + } PriorityQueue queue = new PriorityQueue(2*identityMap.size(), new TagValueSet.FrequencyComparator()); queue.addAll(identityMap.values()); while (queue.size() > 1) diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java new file mode 100644 index 0000000..f6d5bee --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/Rd5DiffTool.java @@ -0,0 +1,144 @@ +/** + * Proof of concept for delta rd5's + * + * @author ab + */ +package btools.mapaccess; + +import java.io.File; + +import btools.codec.DataBuffers; +import btools.codec.MicroCache; +import btools.codec.MicroCache2; +import btools.codec.StatCoderContext; + +final public class Rd5DiffTool +{ + public static void main( String[] args ) throws Exception + { + diff2files( new File( args[0] ),new File( args[1] ) ); + } + + /** + * Compute the delta between 2 RD5 files and + * show statistics on the expected size of the delta file + */ + public static void diff2files( File f1, File f2 ) throws Exception + { + byte[] abBuf1 = new byte[10 * 1024 * 1024]; + byte[] abBuf2 = new byte[10 * 1024 * 1024]; + + int nodesTotal = 0; + int nodesDiff = 0; + int diffedTiles = 0; + + long bytesDiff = 0L; + long diffedTileSize = 0L; + + PhysicalFile pf1 = null; + PhysicalFile pf2 = null; + try + { + DataBuffers dataBuffers = new DataBuffers(); + pf1 = new PhysicalFile( f1, dataBuffers, -1, -1 ); + pf2 = new PhysicalFile( f2, dataBuffers, -1, -1 ); + int div = pf1.divisor; + for ( int lonDegree = 0; lonDegree < 5; lonDegree++ ) // does'nt really matter.. + { + for ( int latDegree = 0; latDegree < 5; latDegree++ ) // ..where on earth we are + { + OsmFile osmf1 = new OsmFile( pf1, lonDegree, latDegree, dataBuffers ); + OsmFile osmf2 = new OsmFile( pf2, lonDegree, latDegree, dataBuffers ); + for ( int lonIdx = 0; lonIdx < div; lonIdx++ ) + { + for ( int latIdx = 0; latIdx < div; latIdx++ ) + { + int lonIdxDiv = lonDegree * div + lonIdx; + int latIdxDiv = latDegree * div + latIdx; + + + MicroCache mc1 = osmf1.hasData() ? + osmf1.createMicroCache( lonIdxDiv, latIdxDiv, dataBuffers, null, null, true, null ) + : MicroCache.emptyCache(); + MicroCache mc2 = osmf2.hasData() ? + osmf2.createMicroCache( lonIdxDiv, latIdxDiv, dataBuffers, null, null, true, null ) + : MicroCache.emptyCache(); + + MicroCache mc = new MicroCache2( mc1.getSize() + mc2.getSize(), abBuf2, lonIdxDiv, latIdxDiv, div ); + mc.calcDelta( mc1, mc2 ); + + nodesTotal += mc2.getSize(); + + if ( mc.getSize() > 0 ) + { + int len = mc.encodeMicroCache( abBuf1 ); + byte[] bytes = new byte[len]; + System.arraycopy( abBuf1, 0, bytes, 0, len ); + + bytesDiff += len; + nodesDiff += mc.getSize(); + diffedTiles++; + diffedTileSize += mc2.size(); + + // cross-check the encoding: decode again + MicroCache mcCheck = new MicroCache2( new StatCoderContext( bytes ), new DataBuffers( null ), lonIdxDiv, latIdxDiv, div, null, null ); + + // due to link-order ambiguity, for decoded we can only compare node-count and datasize + if ( mc.size() != mcCheck.size() ) + { + throw new IllegalArgumentException( "re-decoded data-size mismatch!" ); + } + if ( mc.getSize() != mcCheck.getSize() ) + { + throw new IllegalArgumentException( "re-decoded node-count mismatch!" ); + } + + // .... so re-encode again + int len2 = mcCheck.encodeMicroCache( abBuf1 ); + byte[] bytes2 = new byte[len2]; + System.arraycopy( abBuf1, 0, bytes2, 0, len2 ); + + // and here we can compare byte-by-byte + if ( len != len2 ) + { + throw new IllegalArgumentException( "decoded size mismatch!" ); + } + for( int i=0; i +Get it on F-Droid + +Get it on Google Play + + +## Downloading BRouter segments + +Then, launch the BRouter app and choose "Download Manager". Zoom in and select +the areas you want to route in. Then click "Start Download" and BRouter will +start downloading the [segments](http://brouter.de/brouter/segments4/) files +for the selected areas. + +Main menu of BRouter android app + +Grid selection of segments to download + +Note that you will have to repeat this step periodically, whenever you want to have an +updated version of the OSM data used for the routing. + + +## Selecting profiles to use + +Once this is done, start again the BRouter app and choose the "BRouter App" +entry on the main menu. Select the routing profile you want to use and click +"Server-Mode". Then, tick the boxes for the routing modes you want to use this +profile for. You can use two different profiles per transportation mode, which +will be mapped to the "shortest" and "fastest" presets (these are just +labelling) in OSMAnd. + +Profiles selection + +Profiles selection summary + + +## Configure OSMAnd to make use of BRouter offline navigation + +You can now create an "Application profile" in OSMAnd which will be using +BRouter for offline routing. Go to Settings -> Application profiles -> Add and +create a new profile based on the base profile of your choice (cycling here, +for bicycle routing), with a custom name of your choice ("BRouter" on the +screenshot below) and making use of "BRouter (offline)" for navigation. + +BRouter configuration in OSMAnd
+application profiles + +The BRouter app should be launched before OSMAnd for this specific entry to +appear in OSMAnd. Therefore, if you cannot find "BRouter (offline)" navigation +option, you should force quit OSMAnd and restart it. diff --git a/misc/readmes/osmand/brouter-grid.png b/misc/readmes/osmand/brouter-grid.png new file mode 100644 index 0000000..0d97c68 Binary files /dev/null and b/misc/readmes/osmand/brouter-grid.png differ diff --git a/misc/readmes/osmand/brouter-main.png b/misc/readmes/osmand/brouter-main.png new file mode 100644 index 0000000..3541654 Binary files /dev/null and b/misc/readmes/osmand/brouter-main.png differ diff --git a/misc/readmes/osmand/brouter-osmand.png b/misc/readmes/osmand/brouter-osmand.png new file mode 100644 index 0000000..3586cc2 Binary files /dev/null and b/misc/readmes/osmand/brouter-osmand.png differ diff --git a/misc/readmes/osmand/brouter-profiles-summary.png b/misc/readmes/osmand/brouter-profiles-summary.png new file mode 100644 index 0000000..d849a9e Binary files /dev/null and b/misc/readmes/osmand/brouter-profiles-summary.png differ diff --git a/misc/readmes/osmand/brouter-profiles.png b/misc/readmes/osmand/brouter-profiles.png new file mode 100644 index 0000000..e795b53 Binary files /dev/null and b/misc/readmes/osmand/brouter-profiles.png differ diff --git a/misc/scripts/mapcreation/process_pbf_planet.sh b/misc/scripts/mapcreation/process_pbf_planet.sh index 36b635e..dd6d97d 100755 --- a/misc/scripts/mapcreation/process_pbf_planet.sh +++ b/misc/scripts/mapcreation/process_pbf_planet.sh @@ -1,6 +1,19 @@ #!/bin/bash set -e -wget -N http://planet.openstreetmap.org/pbf/planet-latest.osm.pbf +cd "$(dirname "$0")" + +# Fetch OSM planet dump if no planet file is specified +if [ -z "$PLANET_FILE" ]; then + if [ -x "$(command -v osmupdate)" ] && [[ -f "./planet-latest.osm.pbf" ]]; then + # Prefer running osmupdate to update the planet file if available + mv "./planet-latest.osm.pbf" "./planet-latest.old.osm.pbf" + osmupdate "planet-latest.old.osm.pbf" "./planet-latest.osm.pbf" + rm "./planet-latest.old.osm.pbf" + else + # Otherwise, download it again + wget -N http://planet.openstreetmap.org/pbf/planet-latest.osm.pbf + fi +fi if test lastmaprun.date -nt planet-latest.osm.pbf; then echo "no osm update, exiting" @@ -20,7 +33,7 @@ OSMOSIS_JAR=$(realpath "../../pbfparser/osmosis.jar") PROTOBUF_JAR=$(realpath "../../pbfparser/protobuf.jar") PBFPARSER_JAR=$(realpath "../../pbfparser/pbfparser.jar") -PLANET_FILE=$(realpath "./planet-latest.osm.pbf") +PLANET_FILE=${PLANET_FILE:-$(realpath "./planet-latest.osm.pbf")} # Download SRTM zip files from # https://cgiarcsi.community/data/srtm-90m-digital-elevation-database-v4-1/ # (use the "ArcInfo ASCII" version) and put the ZIP files directly in this