Merge branch 'master' into docs

This commit is contained in:
Manuel Fuhr 2022-05-17 06:40:31 +02:00
commit a75570a027
47 changed files with 1924 additions and 2342 deletions

View file

@ -12,6 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: BRouter
permissions: permissions:
contents: read contents: read
packages: write packages: write
@ -27,10 +28,19 @@ jobs:
server-id: github # Value of the distributionManagement/repository/id field of the pom.xml server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
settings-path: ${{ github.workspace }} # location for the settings.xml file settings-path: ${{ github.workspace }} # location for the settings.xml file
- name: Setup keystore
env:
BROUTER_KEYSTORE_BASE64: ${{ secrets.BROUTER_KEYSTORE_BASE64 }}
run: |
echo $BROUTER_KEYSTORE_BASE64 | base64 -di > ${{ github.workspace }}/brouter.jks
- name: Build with Gradle - name: Build with Gradle
env:
ORG_GRADLE_PROJECT_RELEASE_STORE_FILE: ${{ secrets.BROUTER_KEYSTORE_FILE }}
ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }}
ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }}
ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }}
run: gradle build run: gradle build
# The USERNAME and TOKEN need to correspond to the credentials environment variables used in # The USERNAME and TOKEN need to correspond to the credentials environment variables used in
# the publishing section of your build.gradle # the publishing section of your build.gradle
- name: Publish to GitHub Packages - name: Publish to GitHub Packages

View file

@ -13,7 +13,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
environment: BRouter
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Set up JDK 11 - name: Set up JDK 11
@ -24,7 +24,17 @@ jobs:
cache: gradle cache: gradle
- name: Create local.properties - name: Create local.properties
run: touch local.properties run: touch local.properties
- name: Setup keystore
env:
BROUTER_KEYSTORE_BASE64: ${{ secrets.BROUTER_KEYSTORE_BASE64 }}
run: |
echo $BROUTER_KEYSTORE_BASE64 | base64 -di > ${{ github.workspace }}/brouter.jks
- name: Build with Gradle - name: Build with Gradle
env:
ORG_GRADLE_PROJECT_RELEASE_STORE_FILE: ${{ secrets.BROUTER_KEYSTORE_FILE }}
ORG_GRADLE_PROJECT_RELEASE_KEY_ALIAS: ${{ secrets.BROUTER_KEY_ALIAS }}
ORG_GRADLE_PROJECT_RELEASE_KEY_PASSWORD: ${{ secrets.BROUTER_KEY_PASSWORD }}
ORG_GRADLE_PROJECT_RELEASE_STORE_PASSWORD: ${{ secrets.BROUTER_STORE_PASSWORD }}
run: ./gradlew build run: ./gradlew build
- name: Upload ZIP - name: Upload ZIP
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2

View file

@ -98,7 +98,7 @@ Segments files from the whole planet are generated weekly at
[https://brouter.de/brouter/segments4/](http://brouter.de/brouter/segments4/). [https://brouter.de/brouter/segments4/](http://brouter.de/brouter/segments4/).
You can download one or more segments files, covering the area of the planet You can download one or more segments files, covering the area of the planet
your want to route, into the `misc/segments4` directory. you want to route, into the `misc/segments4` directory.
#### Generate your own segments files #### Generate your own segments files

View file

@ -15,7 +15,7 @@ public final class MicroCache2 extends MicroCache
private int latBase; private int latBase;
private int cellsize; private int cellsize;
public MicroCache2( int size, byte[] databuffer, int lonIdx, int latIdx, int divisor ) throws Exception public MicroCache2( int size, byte[] databuffer, int lonIdx, int latIdx, int divisor )
{ {
super( databuffer ); // sets ab=databuffer, aboffset=0 super( databuffer ); // sets ab=databuffer, aboffset=0
@ -34,7 +34,7 @@ public final class MicroCache2 extends MicroCache
return b; return b;
} }
public MicroCache2( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher ) throws Exception public MicroCache2( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher )
{ {
super( null ); super( null );
cellsize = 1000000 / divisor; cellsize = 1000000 / divisor;

View file

@ -255,8 +255,8 @@ abstract class OsmPath implements OsmLinkHolder
if ( nsection == 0 && rc.considerTurnRestrictions && !detailMode&& !isStartpoint ) if ( nsection == 0 && rc.considerTurnRestrictions && !detailMode&& !isStartpoint )
{ {
if ( rc.inverseDirection if ( rc.inverseDirection
? TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon2, lat2, lon0, lat0, rc.bikeMode, rc.carMode ) ? TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon2, lat2, lon0, lat0, rc.bikeMode || rc.footMode, rc.carMode )
: TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon0, lat0, lon2, lat2, rc.bikeMode, rc.carMode ) ) : TurnRestriction.isTurnForbidden( sourceNode.firstRestriction, lon0, lat0, lon2, lat2, rc.bikeMode || rc.footMode, rc.carMode ) )
{ {
cost = -1; cost = -1;
return; return;

View file

@ -33,8 +33,8 @@ import btools.util.StringUtils;
public final class OsmTrack public final class OsmTrack
{ {
final public static String version = "1.6.2"; final public static String version = "1.6.3";
final public static String versionDate = "10102021"; final public static String versionDate = "21122021";
// csv-header-line // csv-header-line
private static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy"; private static final String MESSAGES_HEADER = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost\tNodeCost\tInitialCost\tWayTags\tNodeTags\tTime\tEnergy";
@ -591,8 +591,10 @@ public final class OsmTrack
if ( turnInstructionMode == 2 ) if ( turnInstructionMode == 2 )
{ {
sb.append( " <extensions><locus:rteComputeType>" ).append( "" + voiceHints.getLocusRouteType() ).append( "</locus:rteComputeType></extensions>\n" ); sb.append( " <extensions>\n" );
sb.append( " <extensions><locus:rteSimpleRoundabouts>1</locus:rteSimpleRoundabouts></extensions>\n" ); sb.append( " <locus:rteComputeType>" ).append( "" + voiceHints.getLocusRouteType() ).append( "</locus:rteComputeType>\n" );
sb.append( " <locus:rteSimpleRoundabouts>1</locus:rteSimpleRoundabouts>\n" );
sb.append( " </extensions>\n" );
} }
sb.append( " <trkseg>\n" ); sb.append( " <trkseg>\n" );

View file

@ -147,8 +147,8 @@ public final class RoutingContext
waypointCatchingRange = expctxGlobal.getVariableValue( "waypointCatchingRange", 250.f ); waypointCatchingRange = expctxGlobal.getVariableValue( "waypointCatchingRange", 250.f );
// turn-restrictions used per default for car profiles // turn-restrictions not used per default for foot profiles
considerTurnRestrictions = 0.f != expctxGlobal.getVariableValue( "considerTurnRestrictions", 1.f ); considerTurnRestrictions = 0.f != expctxGlobal.getVariableValue( "considerTurnRestrictions", footMode ? 0.f : 1.f );
// process tags not used in the profile (to have them in the data-tab) // process tags not used in the profile (to have them in the data-tab)
processUnusedTags = 0.f != expctxGlobal.getVariableValue( "processUnusedTags", 0.f ); processUnusedTags = 0.f != expctxGlobal.getVariableValue( "processUnusedTags", 0.f );

View file

@ -0,0 +1,66 @@
package btools.router;
import java.util.Map;
public class SuspectInfo
{
public static final int TRIGGER_DEAD_END = 1;
public static final int TRIGGER_DEAD_START = 2;
public static final int TRIGGER_NODE_BLOCK = 4;
public static final int TRIGGER_BAD_ACCESS = 8;
public static final int TRIGGER_UNK_ACCESS = 16;
public static final int TRIGGER_SHARP_EXIT = 32;
public static final int TRIGGER_SHARP_ENTRY = 64;
public static final int TRIGGER_SHARP_LINK = 128;
public static final int TRIGGER_BAD_TR = 256;
public int prio;
public int triggers;
public static void addSuspect( Map<Long,SuspectInfo> map, long id, int prio, int trigger )
{
Long iD = Long.valueOf( id );
SuspectInfo info = map.get( iD );
if ( info == null )
{
info = new SuspectInfo();
map.put( iD, info );
}
info.prio = Math.max( info.prio, prio );
info.triggers |= trigger;
}
public static SuspectInfo addTrigger( SuspectInfo old, int prio, int trigger )
{
if ( old == null )
{
old = new SuspectInfo();
}
old.prio = Math.max( old.prio, prio );
old.triggers |= trigger;
return old;
}
public static String getTriggerText( int triggers )
{
StringBuilder sb = new StringBuilder();
addText( sb, "dead-end" , triggers, TRIGGER_DEAD_END );
addText( sb, "dead-start" , triggers, TRIGGER_DEAD_START );
addText( sb, "node-block" , triggers, TRIGGER_NODE_BLOCK );
addText( sb, "bad-access" , triggers, TRIGGER_BAD_ACCESS );
addText( sb, "unkown-access", triggers, TRIGGER_UNK_ACCESS );
addText( sb, "sharp-exit" , triggers, TRIGGER_SHARP_EXIT );
addText( sb, "sharp-entry" , triggers, TRIGGER_SHARP_ENTRY );
addText( sb, "sharp-link" , triggers, TRIGGER_SHARP_LINK );
addText( sb, "bad-tr" , triggers, TRIGGER_BAD_TR );
return sb.toString();
}
private static void addText( StringBuilder sb, String text, int mask, int bit )
{
if ( ( bit & mask ) == 0 ) return;
if ( sb.length() > 0 ) sb.append( "," );
sb.append( text );
}
}

View file

@ -622,8 +622,8 @@ public abstract class BExpressionContext implements IByteArrayUnifier
if ( lookupData2 != null ) if ( lookupData2 != null )
{ {
// do not create unknown value for external data array, // do not create unknown value for external data array,
// record as 'other' instead // record as 'unknown' instead
lookupData2[inum] = 0; lookupData2[inum] = 1; // 1 == unknown
if (bFoundAsterix) { if (bFoundAsterix) {
// found value for lookup * // found value for lookup *
//System.out.println( "add unknown " + name + " " + value ); //System.out.println( "add unknown " + name + " " + value );

View file

@ -260,30 +260,11 @@ public class OsmCutter extends MapCreatorBase
@Override @Override
public void nextRestriction( RelationData r, long fromWid, long toWid, long viaNid ) throws Exception public void nextRestriction( RelationData r, long fromWid, long toWid, long viaNid ) throws Exception
{ {
if ( fromWid == 0 || toWid == 0 || viaNid == 0 )
{
return;
}
String type = r.getTag( "type" ); String type = r.getTag( "type" );
if ( type == null || !"restriction".equals( type ) ) if ( type == null || !"restriction".equals( type ) )
{ {
return; return;
} }
String restriction = r.getTag( "restriction" );
if ( restriction == null )
{
return;
}
boolean isPositive = true;
if ( restriction.startsWith( "no_" ) )
{
isPositive = false;
}
else if ( !restriction.startsWith( "only_" ) )
{
return;
}
short exceptions = 0; short exceptions = 0;
String except = r.getTag( "except" ); String except = r.getTag( "except" );
if ( except != null ) if ( except != null )
@ -296,9 +277,17 @@ public class OsmCutter extends MapCreatorBase
exceptions |= toBit( "hgv" , 4, except ); exceptions |= toBit( "hgv" , 4, except );
} }
// System.out.println( "restriction id = " + r.rid + " isPositive=" + isPositive + " fromWid = " + fromWid + " toWid = " + toWid+ " viaNid = " + viaNid ); for( String restrictionKey : r.getTagsOrNull().keySet() )
{
if ( !( restrictionKey.equals( "restriction" ) || restrictionKey.startsWith( "restriction:" ) ) )
{
continue;
}
String restriction = r.getTag( restrictionKey );
RestrictionData res = new RestrictionData(); RestrictionData res = new RestrictionData();
res.isPositive = isPositive; res.restrictionKey = restrictionKey;
res.restriction = restriction;
res.exceptions = exceptions; res.exceptions = exceptions;
res.fromWid = fromWid; res.fromWid = fromWid;
res.toWid = toWid; res.toWid = toWid;
@ -312,7 +301,7 @@ public class OsmCutter extends MapCreatorBase
{ {
restrictionCutter.nextRestriction( res ); restrictionCutter.nextRestriction( res );
} }
}
} }
private static short toBit( String tag, int bitpos, String s ) private static short toBit( String tag, int bitpos, String s )

View file

@ -175,11 +175,11 @@ public class OsmNodeP extends OsmLinkP
RestrictionData r = getFirstRestriction(); RestrictionData r = getFirstRestriction();
while( r != null ) while( r != null )
{ {
if ( r.fromLon != 0 && r.toLon != 0 ) if ( r.isValid() && r.fromLon != 0 && r.toLon != 0 )
{ {
mc.writeBoolean( true ); // restriction follows mc.writeBoolean( true ); // restriction follows
mc.writeShort( r.exceptions ); mc.writeShort( r.exceptions );
mc.writeBoolean( r.isPositive ); mc.writeBoolean( r.isPositive() );
mc.writeInt( r.fromLon ); mc.writeInt( r.fromLon );
mc.writeInt( r.fromLat ); mc.writeInt( r.fromLat );
mc.writeInt( r.toLon ); mc.writeInt( r.toLon );

View file

@ -1,9 +1,14 @@
package btools.mapcreator; package btools.mapcreator;
import java.io.BufferedWriter;
import java.io.DataInputStream; import java.io.DataInputStream;
import java.io.DataOutputStream; import java.io.DataOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.TreeSet;
import btools.util.LongList; import btools.util.CheapAngleMeter;
/** /**
* Container for a turn restriction * Container for a turn restriction
@ -12,26 +17,125 @@ import btools.util.LongList;
*/ */
public class RestrictionData extends MapCreatorBase public class RestrictionData extends MapCreatorBase
{ {
public boolean isPositive; public String restrictionKey;
public String restriction;
public short exceptions; public short exceptions;
public long fromWid; public long fromWid;
public long toWid; public long toWid;
public long viaNid; public long viaNid;
public RestrictionData next; public RestrictionData next;
public int viaLon;
public int viaLat;
public int fromLon; public int fromLon;
public int fromLat; public int fromLat;
public int toLon; public int toLon;
public int toLat; public int toLat;
public boolean badWayMatch;
private static HashMap<String,String> names = new HashMap<>();
private static TreeSet<Long> badTRs = new TreeSet<>();
public RestrictionData() public RestrictionData()
{ {
} }
public boolean isPositive()
{
return restriction.startsWith( "only_" );
}
public boolean isValid()
{
boolean valid = fromLon != 0 && toLon != 0 && ( restriction.startsWith( "only_" ) || restriction.startsWith( "no_" ) );
if ( (!valid) || badWayMatch || !(checkGeometry()) )
{
synchronized( badTRs )
{
badTRs.add( ( (long) viaLon ) << 32 | viaLat );
}
}
return valid && "restriction".equals( restrictionKey );
}
private boolean checkGeometry()
{
double a = (new CheapAngleMeter()).calcAngle( fromLon, fromLat, viaLon, viaLat, toLon, toLat );
String t;
if ( restriction.startsWith( "only_" ) )
{
t = restriction.substring( "only_".length() );
}
else if ( restriction.startsWith( "no_" ) )
{
t = restriction.substring( "no_".length() );
}
else throw new RuntimeException( "ups" );
if ( restrictionKey.endsWith( ":conditional" ) )
{
int idx = t.indexOf( '@' );
if ( idx >= 0 )
{
t = t.substring(0, idx ).trim();
}
}
if ( "left_turn".equals( t ) )
{
return a < -5. && a > -175.;
}
if ( "right_turn".equals( t ) )
{
return a > 5. && a < 175.;
}
if ( "straight_on".equals( t ) )
{
return a > -85. && a < 85.;
}
if ( "u_turn".equals( t ) )
{
return a < - 95. || a > 95.;
}
return "entry".equals( t ) || "exit".equals( t );
}
private static String unifyName( String name )
{
synchronized( names )
{
String n = names.get(name);
if ( n == null )
{
names.put( name, name );
n = name;
}
return n;
}
}
public static void dumpBadTRs()
{
try( BufferedWriter bw = new BufferedWriter( new FileWriter( "badtrs.txt" ) ) )
{
for( Long id : badTRs )
{
bw.write( "" + id + " 26\n" );
}
}
catch( IOException ioe )
{
throw new RuntimeException( ioe );
}
}
public RestrictionData( DataInputStream di ) throws Exception public RestrictionData( DataInputStream di ) throws Exception
{ {
isPositive = di.readBoolean(); restrictionKey = unifyName( di.readUTF() );
restriction = unifyName( di.readUTF() );
exceptions = di.readShort(); exceptions = di.readShort();
fromWid = readId( di ); fromWid = readId( di );
toWid = readId( di ); toWid = readId( di );
@ -40,7 +144,8 @@ public class RestrictionData extends MapCreatorBase
public void writeTo( DataOutputStream dos ) throws Exception public void writeTo( DataOutputStream dos ) throws Exception
{ {
dos.writeBoolean( isPositive ); dos.writeUTF( restrictionKey );
dos.writeUTF( restriction );
dos.writeShort( exceptions ); dos.writeShort( exceptions );
writeId( dos, fromWid ); writeId( dos, fromWid );
writeId( dos, toWid ); writeId( dos, toWid );

View file

@ -138,6 +138,9 @@ public class WayLinker extends MapCreatorBase implements Runnable
new WayLinker().process( new File( args[0] ), new File( args[1] ), new File( args[2] ), new File( args[3] ), new File( args[4] ), new File( args[5] ), new File( new WayLinker().process( new File( args[0] ), new File( args[1] ), new File( args[2] ), new File( args[3] ), new File( args[4] ), new File( args[5] ), new File(
args[6] ), args[7] ); args[6] ), args[7] );
System.out.println( "dumping bad TRs" );
RestrictionData.dumpBadTRs();
} }
public void process( File nodeTilesIn, File wayTilesIn, File borderFileIn, File restrictionsFileIn, File lookupFile, File profileFile, File dataTilesOut, public void process( File nodeTilesIn, File wayTilesIn, File borderFileIn, File restrictionsFileIn, File lookupFile, File profileFile, File dataTilesOut,
@ -287,6 +290,8 @@ public class WayLinker extends MapCreatorBase implements Runnable
nodesMap.put( res.viaNid, n ); nodesMap.put( res.viaNid, n );
} }
OsmNodePT nt = (OsmNodePT) n; OsmNodePT nt = (OsmNodePT) n;
res.viaLon = nt.ilon;
res.viaLat = nt.ilat;
res.next = nt.firstRestriction; res.next = nt.firstRestriction;
nt.firstRestriction = res; nt.firstRestriction = res;
ntr++; ntr++;
@ -351,37 +356,50 @@ public class WayLinker extends MapCreatorBase implements Runnable
// the leg according to the mapped direction // the leg according to the mapped direction
private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w ) private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w )
{ {
checkRestriction( n1, n2, w.wid, true ); checkRestriction( n1, n2, w, true );
checkRestriction( n2, n1, w.wid, false ); checkRestriction( n2, n1, w, false );
} }
private void checkRestriction( OsmNodeP n1, OsmNodeP n2, long wid, boolean checkFrom ) private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w, boolean checkFrom )
{ {
RestrictionData r = n2.getFirstRestriction(); RestrictionData r = n2.getFirstRestriction();
while ( r != null ) while ( r != null )
{ {
if ( r.fromWid == wid ) if ( r.fromWid == w.wid )
{ {
if ( r.fromLon == 0 || checkFrom ) if ( r.fromLon == 0 || checkFrom )
{ {
r.fromLon = n1.ilon; r.fromLon = n1.ilon;
r.fromLat = n1.ilat; r.fromLat = n1.ilat;
n1.bits |= OsmNodeP.DP_SURVIVOR_BIT; n1.bits |= OsmNodeP.DP_SURVIVOR_BIT;
if ( !isEndNode( n2, w ) )
{
r.badWayMatch = true;
} }
} }
if ( r.toWid == wid ) }
if ( r.toWid == w.wid )
{ {
if ( r.toLon == 0 || !checkFrom ) if ( r.toLon == 0 || !checkFrom )
{ {
r.toLon = n1.ilon; r.toLon = n1.ilon;
r.toLat = n1.ilat; r.toLat = n1.ilat;
n1.bits |= OsmNodeP.DP_SURVIVOR_BIT; n1.bits |= OsmNodeP.DP_SURVIVOR_BIT;
if ( !isEndNode( n2, w ) )
{
r.badWayMatch = true;
}
} }
} }
r = r.next; r = r.next;
} }
} }
private boolean isEndNode( OsmNodeP n, WayData w )
{
return n == nodesMap.get( w.nodes.get( 0 ) ) || n == nodesMap.get( w.nodes.get( w.nodes.size() - 1 ) );
}
@Override @Override
public void nextWay( WayData way ) throws Exception public void nextWay( WayData way ) throws Exception
{ {

View file

@ -20,7 +20,7 @@ public final class DirectWeaver extends ByteDataWriter
private int size = 0; private int size = 0;
public DirectWeaver( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher, OsmNodesMap hollowNodes ) throws Exception public DirectWeaver( StatCoderContext bc, DataBuffers dataBuffers, int lonIdx, int latIdx, int divisor, TagValueValidator wayValidator, WaypointMatcher waypointMatcher, OsmNodesMap hollowNodes )
{ {
super( null ); super( null );
int cellsize = 1000000 / divisor; int cellsize = 1000000 / divisor;

View file

@ -35,7 +35,7 @@ final class OsmFile
private int ncaches; private int ncaches;
private int indexsize; private int indexsize;
public OsmFile( PhysicalFile rafile, int lonDegree, int latDegree, DataBuffers dataBuffers ) throws Exception public OsmFile( PhysicalFile rafile, int lonDegree, int latDegree, DataBuffers dataBuffers ) throws IOException
{ {
this.lonDegree = lonDegree; this.lonDegree = lonDegree;
this.latDegree = latDegree; this.latDegree = latDegree;
@ -111,7 +111,7 @@ final class OsmFile
return idx == -1 ? indexsize : posIdx[idx]; return idx == -1 ? indexsize : posIdx[idx];
} }
public int getDataInputForSubIdx( int subIdx, byte[] iobuffer ) throws Exception public int getDataInputForSubIdx( int subIdx, byte[] iobuffer ) throws IOException
{ {
int startPos = getPosIdx( subIdx - 1 ); int startPos = getPosIdx( subIdx - 1 );
int endPos = getPosIdx( subIdx ); int endPos = getPosIdx( subIdx );
@ -128,7 +128,7 @@ final class OsmFile
} }
public MicroCache createMicroCache( int lonIdx, int latIdx, DataBuffers dataBuffers, TagValueValidator wayValidator, public MicroCache createMicroCache( int lonIdx, int latIdx, DataBuffers dataBuffers, TagValueValidator wayValidator,
WaypointMatcher waypointMatcher, boolean reallyDecode, OsmNodesMap hollowNodes ) throws Exception WaypointMatcher waypointMatcher, boolean reallyDecode, OsmNodesMap hollowNodes ) throws IOException
{ {
int subIdx = ( latIdx - divisor * latDegree ) * divisor + ( lonIdx - divisor * lonDegree ); int subIdx = ( latIdx - divisor * latDegree ) * divisor + ( lonIdx - divisor * lonDegree );

View file

@ -31,13 +31,12 @@ final public class PhysicalFile
{ {
MicroCache.debug = true; MicroCache.debug = true;
String message = checkFileIntegrity( new File( args[0] ) ); try {
checkFileIntegrity( new File( args[0] ) );
if ( message != null ) } catch (IOException e) {
{ System.err.println( "************************************" );
System.out.println( "************************************" ); e.printStackTrace();
System.out.println( message ); System.err.println( "************************************" );
System.out.println( "************************************" );
} }
} }
@ -46,7 +45,7 @@ final public class PhysicalFile
* *
* @return the error message if file corrupt, else null * @return the error message if file corrupt, else null
*/ */
public static String checkFileIntegrity( File f ) public static String checkFileIntegrity( File f ) throws IOException
{ {
PhysicalFile pf = null; PhysicalFile pf = null;
try try
@ -66,14 +65,6 @@ final public class PhysicalFile
} }
} }
} }
catch (IllegalArgumentException iae)
{
return iae.getMessage();
}
catch (Exception e)
{
return e.toString();
}
finally finally
{ {
if ( pf != null ) if ( pf != null )
@ -88,7 +79,7 @@ final public class PhysicalFile
return null; return null;
} }
public PhysicalFile( File f, DataBuffers dataBuffers, int lookupVersion, int lookupMinorVersion ) throws Exception public PhysicalFile( File f, DataBuffers dataBuffers, int lookupVersion, int lookupMinorVersion ) throws IOException
{ {
fileName = f.getName(); fileName = f.getName();
byte[] iobuffer = dataBuffers.iobuffer; byte[] iobuffer = dataBuffers.iobuffer;
@ -102,7 +93,7 @@ final public class PhysicalFile
short readVersion = (short)(lv >> 48); short readVersion = (short)(lv >> 48);
if ( i == 0 && lookupVersion != -1 && readVersion != lookupVersion ) if ( i == 0 && lookupVersion != -1 && readVersion != lookupVersion )
{ {
throw new IllegalArgumentException( "lookup version mismatch (old rd5?) lookups.dat=" throw new IOException( "lookup version mismatch (old rd5?) lookups.dat="
+ lookupVersion + " " + f. getAbsolutePath() + "=" + readVersion ); + lookupVersion + " " + f. getAbsolutePath() + "=" + readVersion );
} }
fileIndex[i] = lv & 0xffffffffffffL; fileIndex[i] = lv & 0xffffffffffffL;

View file

@ -8,8 +8,10 @@ package btools.mapaccess;
import java.io.BufferedInputStream; import java.io.BufferedInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException;
import java.security.DigestInputStream; import java.security.DigestInputStream;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
final public class Rd5DiffManager final public class Rd5DiffManager
{ {
@ -93,17 +95,16 @@ final public class Rd5DiffManager
} }
} }
public static String getMD5( File f ) throws Exception public static String getMD5( File f ) throws IOException
{ {
try {
MessageDigest md = MessageDigest.getInstance("MD5"); MessageDigest md = MessageDigest.getInstance("MD5");
BufferedInputStream bis = new BufferedInputStream( new FileInputStream( f ) ); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(f));
DigestInputStream dis = new DigestInputStream(bis, md); DigestInputStream dis = new DigestInputStream(bis, md);
byte[] buf = new byte[8192]; byte[] buf = new byte[8192];
for(;;) for (; ; ) {
{ int len = dis.read(buf);
int len = dis.read( buf ); if (len <= 0) {
if ( len <= 0 )
{
break; break;
} }
} }
@ -111,12 +112,14 @@ final public class Rd5DiffManager
byte[] bytes = md.digest(); byte[] bytes = md.digest();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
for (int j = 0; j < bytes.length; j++) for (int j = 0; j < bytes.length; j++) {
{
int v = bytes[j] & 0xff; int v = bytes[j] & 0xff;
sb.append( hexChar( v >>> 4 ) ).append( hexChar( v & 0xf ) ); sb.append(hexChar(v >>> 4)).append(hexChar(v & 0xf));
} }
return sb.toString(); return sb.toString();
} catch (NoSuchAlgorithmException e) {
throw new IOException("MD5 algorithm not available", e);
}
} }
private static char hexChar( int v ) private static char hexChar( int v )

View file

@ -12,6 +12,7 @@ import java.io.DataOutputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays; import java.util.Arrays;
import btools.codec.DataBuffers; import btools.codec.DataBuffers;
@ -49,9 +50,8 @@ final public class Rd5DiffTool implements ProgressListener
} }
@Override @Override
public void updateProgress( String progress ) public void updateProgress(String task, int progress) {
{ System.out.println(task + ": " + progress + "%");
System.out.println( progress );
} }
@Override @Override
@ -60,7 +60,7 @@ final public class Rd5DiffTool implements ProgressListener
return false; return false;
} }
private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws Exception private static long[] readFileIndex( DataInputStream dis, DataOutputStream dos ) throws IOException
{ {
long[] fileIndex = new long[25]; long[] fileIndex = new long[25];
for( int i=0; i<25; i++ ) for( int i=0; i<25; i++ )
@ -85,7 +85,7 @@ final public class Rd5DiffTool implements ProgressListener
return index[tileIndex]; return index[tileIndex];
} }
private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws Exception private static int[] readPosIndex( DataInputStream dis, DataOutputStream dos ) throws IOException
{ {
int[] posIndex = new int[1024]; int[] posIndex = new int[1024];
for( int i=0; i<1024; i++ ) for( int i=0; i<1024; i++ )
@ -105,7 +105,7 @@ final public class Rd5DiffTool implements ProgressListener
return idx == -1 ? 4096 : posIdx[idx]; return idx == -1 ? 4096 : posIdx[idx];
} }
private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws Exception private static byte[] createMicroCache( int[] posIdx, int tileIdx, DataInputStream dis, boolean deltaMode ) throws IOException
{ {
if ( posIdx == null ) if ( posIdx == null )
{ {
@ -125,7 +125,7 @@ final public class Rd5DiffTool implements ProgressListener
return ab; return ab;
} }
private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers ) throws Exception private static MicroCache createMicroCache( byte[] ab, DataBuffers dataBuffers )
{ {
if ( ab == null || ab.length == 0 ) if ( ab == null || ab.length == 0 )
{ {
@ -286,7 +286,7 @@ final public class Rd5DiffTool implements ProgressListener
} }
public static void recoverFromDelta( File f1, File f2, File outFile, ProgressListener progress /* , File cmpFile */ ) throws Exception public static void recoverFromDelta( File f1, File f2, File outFile, ProgressListener progress /* , File cmpFile */ ) throws IOException
{ {
if ( f2.length() == 0L ) if ( f2.length() == 0L )
{ {
@ -341,7 +341,7 @@ final public class Rd5DiffTool implements ProgressListener
int pct = (int)(100. * bytesProcessed / getTileEnd( fileIndex1, 24 ) + 0.5 ); int pct = (int)(100. * bytesProcessed / getTileEnd( fileIndex1, 24 ) + 0.5 );
if ( pct != lastPct ) if ( pct != lastPct )
{ {
progress.updateProgress( "Applying delta: " + pct + "%" ); progress.updateProgress("Applying delta", pct);
lastPct = pct; lastPct = pct;
} }
@ -468,7 +468,7 @@ final public class Rd5DiffTool implements ProgressListener
} }
} }
public static void copyFile( File f1, File outFile, ProgressListener progress ) throws Exception public static void copyFile( File f1, File outFile, ProgressListener progress ) throws IOException
{ {
boolean canceled = false; boolean canceled = false;
DataInputStream dis1 = new DataInputStream( new BufferedInputStream( new FileInputStream( f1 ) ) ); DataInputStream dis1 = new DataInputStream( new BufferedInputStream( new FileInputStream( f1 ) ) );
@ -489,7 +489,7 @@ final public class Rd5DiffTool implements ProgressListener
int pct = (int)( (100. * sizeRead) / (sizeTotal+1) + 0.5 ); int pct = (int)( (100. * sizeRead) / (sizeTotal+1) + 0.5 );
if ( pct != lastPct ) if ( pct != lastPct )
{ {
progress.updateProgress( "Copying: " + pct + "%" ); progress.updateProgress("Copying", pct);
lastPct = pct; lastPct = pct;
} }
int len = dis1.read( buf ); int len = dis1.read( buf );
@ -756,7 +756,7 @@ final public class Rd5DiffTool implements ProgressListener
this.dataBuffers = dataBuffers; this.dataBuffers = dataBuffers;
} }
public MicroCache readMC() throws Exception public MicroCache readMC() throws IOException
{ {
if (skips < 0 ) if (skips < 0 )
{ {
@ -775,7 +775,7 @@ final public class Rd5DiffTool implements ProgressListener
return mc; return mc;
} }
public void finish() throws Exception public void finish()
{ {
skips = -1; skips = -1;
} }

View file

@ -5,23 +5,25 @@ plugins {
} }
android { android {
compileSdkVersion 30 compileSdkVersion 31
defaultConfig { defaultConfig {
applicationId "btools.routingapp" applicationId "btools.routingapp"
versionCode 42 versionCode 45
versionName project.version versionName project.version
resValue('string', 'app_version', defaultConfig.versionName) resValue('string', 'app_version', defaultConfig.versionName)
setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName) setProperty("archivesBaseName", "BRouterApp." + defaultConfig.versionName)
minSdkVersion 14 minSdkVersion 14
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }
sourceSets.main.assets.srcDirs += new File(project.buildDir, 'assets') sourceSets.main.assets.srcDirs += new File(project.buildDir, 'assets')
if (project.hasProperty("RELEASE_STORE_FILE")) { if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) {
signingConfigs { signingConfigs {
// this uses a file ~/.gradle/gradle.properties // this uses a file ~/.gradle/gradle.properties
// with content: // with content:
@ -49,7 +51,7 @@ android {
release { release {
minifyEnabled false minifyEnabled false
debuggable false debuggable false
if (project.hasProperty("RELEASE_STORE_FILE")) { if (project.hasProperty("RELEASE_STORE_FILE") && RELEASE_STORE_FILE.length() > 0) {
signingConfig signingConfigs.release signingConfig signingConfigs.release
} }
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
@ -96,14 +98,21 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.appcompat:appcompat:1.3.1' implementation "androidx.constraintlayout:constraintlayout:2.1.3"
implementation 'androidx.work:work-runtime:2.7.1'
implementation 'com.google.android.material:material:1.5.0'
implementation project(':brouter-mapaccess') implementation project(':brouter-mapaccess')
implementation project(':brouter-core') implementation project(':brouter-core')
implementation project(':brouter-expressions') implementation project(':brouter-expressions')
implementation project(':brouter-util') implementation project(':brouter-util')
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation 'androidx.work:work-testing:2.7.1'
} }
task generateProfiles(type: Exec) { task generateProfiles(type: Exec) {

View file

@ -0,0 +1,47 @@
package btools.routingapp;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;
import android.os.Build;
import android.os.Environment;
import androidx.test.core.app.ActivityScenario;
import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.util.List;
@RunWith(AndroidJUnit4.class)
public class BRouterActivityTest {
@Rule
public ActivityScenarioRule<BRouterActivity> rule = new ActivityScenarioRule<>(BRouterActivity.class);
@Test
public void storageDirectories() {
ActivityScenario<BRouterActivity> scenario = rule.getScenario();
scenario.onActivity(activity -> {
List<File> storageDirectories = activity.getStorageDirectories();
// Before Android Q (10) legacy storage access is possible
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
assertThat(storageDirectories, hasItem(Environment.getExternalStorageDirectory()));
}
// When targeting older SDK we can access legacy storage on any android version
if (activity.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.Q) {
assertThat(storageDirectories, hasItem(Environment.getExternalStorageDirectory()));
}
assertThat(storageDirectories, not(empty()));
});
}
}

View file

@ -0,0 +1,71 @@
package btools.routingapp;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertThat;
import android.content.Context;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.work.Data;
import androidx.work.ListenableWorker.Result;
import androidx.work.testing.TestWorkerBuilder;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@RunWith(AndroidJUnit4.class)
public class DownloadWorkerTest {
private Context context;
private Executor executor;
@Before
public void setUp() {
context = ApplicationProvider.getApplicationContext();
executor = Executors.newSingleThreadExecutor();
}
@Test
public void testDownloadNewFile() {
Data inputData = new Data.Builder()
.putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"E105_N50"})
.build();
DownloadWorker worker =
TestWorkerBuilder.from(context, DownloadWorker.class, executor)
.setInputData(inputData)
.build();
Result result = worker.doWork();
assertThat(result, is(Result.success()));
}
@Test
public void testDownloadInvalidSegment() {
Data inputData = new Data.Builder()
.putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, new String[]{"X00"})
.build();
DownloadWorker worker =
TestWorkerBuilder.from(context, DownloadWorker.class, executor)
.setInputData(inputData)
.build();
Result result = worker.doWork();
assertThat(result, is(Result.failure()));
}
@Test
public void testDownloadNoSegments() {
DownloadWorker worker =
TestWorkerBuilder.from(context, DownloadWorker.class, executor)
.build();
Result result = worker.doWork();
assertThat(result, is(Result.failure()));
}
}

View file

@ -3,7 +3,9 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="btools.routingapp"> package="btools.routingapp">
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" /> tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
@ -11,17 +13,16 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application <application
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:label="@string/app_name"
android:allowBackup="false" android:allowBackup="false"
android:preserveLegacyExternalStorage="true"> android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:preserveLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_launcher_round"
android:theme="@style/Theme.App">
<activity <activity
android:name=".BRouterActivity" android:name=".BRouterActivity"
android:label="@string/app_name"
android:exported="true" android:exported="true"
android:screenOrientation="unspecified" android:screenOrientation="unspecified">
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
@ -29,20 +30,18 @@
</activity> </activity>
<activity <activity
android:name=".BInstallerActivity" android:name=".BInstallerActivity"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:launchMode="singleTask"
android:exported="true" android:exported="true"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"></activity> android:launchMode="singleTask"
android:screenOrientation="landscape" />
<activity <activity
android:name=".BImportActivity" android:name=".BImportActivity"
android:label="Import Profile"
android:exported="true" android:exported="true"
android:theme="@android:style/Theme.NoTitleBar"> android:label="Import Profile">
<!-- some apps (bluemail) do not recognize the .brf file extention, startactivity+intent is done for attachement with text/plain --> <!-- some apps (bluemail) do not recognize the .brf file extension, startactivity+intent is done for attachment with text/plain -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" /> <data android:scheme="file" />
<data android:scheme="content" /> <data android:scheme="content" />
<data android:mimeType="text/plain" /> <data android:mimeType="text/plain" />
@ -88,15 +87,10 @@
</activity> </activity>
<service <service
android:exported="true"
android:name=".BRouterService" android:name=".BRouterService"
android:enabled="true" android:enabled="true"
android:exported="true"
android:process=":brouter_service" /> android:process=":brouter_service" />
<service
android:name="btools.routingapp.DownloadService"
android:label="Download Service"
android:icon="@mipmap/ic_launcher"
android:enabled="true" />
</application> </application>
</manifest> </manifest>

View file

@ -13,4 +13,4 @@ profiles_url=https://brouter.de/brouter/profiles2/
# these are comma separated arrays # these are comma separated arrays
check_lookup=lookups.dat check_lookup=lookups.dat
check_profiles=car-eco.brf,car-eco-de.brf,car-eco-suspect_scan.brf,car-fast.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,fastbike-verylowtraffic.brf,hiking-beta.brf,moped.brf,rail.brf,river.brf,safety.brf,shortest.brf,trekking.brf,trekking-ignore-cr.brf,trekking-noferries.brf,trekking-nosteps.brf,trekking-steep.brf,vm-forum-liegerad-schnell.brf,vm-forum-velomobil-schnell.brf check_profiles=car-eco.brf,car-eco-de.brf,car-fast.brf,fastbike.brf,fastbike-asia-pacific.brf,fastbike-lowtraffic.brf,fastbike-verylowtraffic.brf,hiking-mountain.brf,moped.brf,rail.brf,river.brf,safety.brf,shortest.brf,trekking.brf,trekking-ignore-cr.brf,trekking-noferries.brf,trekking-nosteps.brf,trekking-steep.brf,vm-forum-liegerad-schnell.brf,vm-forum-velomobil-schnell.brf

View file

@ -1,6 +1,5 @@
package btools.routingapp; package btools.routingapp;
import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
@ -12,6 +11,7 @@ import android.widget.EditText;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
@ -20,7 +20,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
public class BImportActivity extends Activity { public class BImportActivity extends AppCompatActivity {
// profile size is generally < 30 kb, so set max size to 100 kb // profile size is generally < 30 kb, so set max size to 100 kb
private static final int MAX_PROFILE_SIZE = 100000; private static final int MAX_PROFILE_SIZE = 100000;
private EditText mTextFilename; private EditText mTextFilename;
@ -76,8 +76,8 @@ public class BImportActivity extends Activity {
try (Cursor cursor = this.getContentResolver().query(intent.getData(), new String[]{ try (Cursor cursor = this.getContentResolver().query(intent.getData(), new String[]{
OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null)) { OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE}, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
filename = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME));
filesize = cursor.getLong(cursor.getColumnIndex(OpenableColumns.SIZE)); filesize = cursor.getLong(cursor.getColumnIndexOrThrow(OpenableColumns.SIZE));
} }
} }
// is the file extention ".brf" in the file name // is the file extention ".brf" in the file name

View file

@ -1,112 +1,208 @@
package btools.routingapp; package btools.routingapp;
import java.util.HashSet; import static btools.routingapp.BInstallerView.MASK_CURRENT_RD5;
import java.util.Set; import static btools.routingapp.BInstallerView.MASK_DELETED_RD5;
import static btools.routingapp.BInstallerView.MASK_INSTALLED_RD5;
import static btools.routingapp.BInstallerView.MASK_SELECTED_RD5;
import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.res.Resources;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.os.StatFs; import android.os.StatFs;
import android.util.Log; import android.text.format.Formatter;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
public class BInstallerActivity extends Activity { import androidx.appcompat.app.AppCompatActivity;
import androidx.work.Constraints;
import androidx.work.Data;
import androidx.work.NetworkType;
import androidx.work.OneTimeWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
import androidx.work.WorkRequest;
public static final String DOWNLOAD_ACTION = "btools.routingapp.download"; import com.google.android.material.progressindicator.LinearProgressIndicator;
import java.io.File;
import java.util.ArrayList;
import java.util.Locale;
import btools.router.RoutingHelper;
public class BInstallerActivity extends AppCompatActivity {
private static final int DIALOG_CONFIRM_DELETE_ID = 1; private static final int DIALOG_CONFIRM_DELETE_ID = 1;
public static boolean downloadCanceled = false;
private File mBaseDir;
private BInstallerView mBInstallerView; private BInstallerView mBInstallerView;
private PowerManager mPowerManager; private Button mButtonDownload;
private WakeLock mWakeLock; private TextView mSummaryInfo;
private DownloadReceiver myReceiver; private LinearProgressIndicator mProgressIndicator;
public static long getAvailableSpace(String baseDir) {
StatFs stat = new StatFs(baseDir);
public class DownloadReceiver extends BroadcastReceiver { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
} else {
//noinspection deprecation
return (long) stat.getAvailableBlocks() * stat.getBlockSize();
}
}
@Override @Override
public void onReceive(Context context, Intent intent) {
if (intent.hasExtra("txt")) {
String txt = intent.getStringExtra("txt");
boolean ready = intent.getBooleanExtra("ready", false);
mBInstallerView.setState(txt, ready);
}
}
}
/**
* Called when the activity is first created.
*/
@Override
@SuppressWarnings("deprecation")
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
// Get an instance of the PowerManager
mPowerManager = (PowerManager) getSystemService(POWER_SERVICE);
// Create a bright wake lock
mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass()
.getName());
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
// instantiate our simulation view and set it as the activity's content setContentView(R.layout.activity_binstaller);
mBInstallerView = new BInstallerView(this); mSummaryInfo = findViewById(R.id.textViewSegmentSummary);
setContentView(mBInstallerView); mBInstallerView = findViewById(R.id.BInstallerView);
mBInstallerView.setOnSelectListener(
() -> {
updateDownloadButton();
}
);
mButtonDownload = findViewById(R.id.buttonDownload);
mButtonDownload.setOnClickListener(
view -> {
if (mBInstallerView.getSelectedTiles(MASK_DELETED_RD5).size() > 0) {
showConfirmDelete();
} else if (mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5).size() > 0) {
downloadSelectedTiles();
} else {
downloadInstalledTiles();
}
}
);
mProgressIndicator = findViewById(R.id.progressDownload);
mBaseDir = ConfigHelper.getBaseDir(this);
scanExistingFiles();
}
private String getSegmentsPlural(int count) {
Resources res = getResources();
return res.getQuantityString(R.plurals.numberOfSegments, count, count);
}
private void updateDownloadButton() {
final ArrayList<Integer> selectedTilesDownload = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5);
final ArrayList<Integer> selectedTilesUpdate = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5);
final ArrayList<Integer> selectedTilesDelete = mBInstallerView.getSelectedTiles(MASK_DELETED_RD5);
mSummaryInfo.setText("");
if (selectedTilesDelete.size() > 0) {
mButtonDownload.setText(getString(R.string.action_delete, getSegmentsPlural(selectedTilesDelete.size())));
mButtonDownload.setEnabled(true);
} else if (selectedTilesDownload.size() > 0) {
long tileSize = 0;
for (int tileIndex : selectedTilesDownload) {
tileSize += BInstallerSizes.getRd5Size(tileIndex);
}
mButtonDownload.setText(getString(R.string.action_download, getSegmentsPlural(selectedTilesDownload.size())));
mButtonDownload.setEnabled(true);
mSummaryInfo.setText(getString(R.string.summary_segments, Formatter.formatFileSize(this, tileSize), Formatter.formatFileSize(this, getAvailableSpace(mBaseDir.getAbsolutePath()))));
} else if (selectedTilesUpdate.size() > 0) {
mButtonDownload.setText(getString(R.string.action_update, getSegmentsPlural(selectedTilesUpdate.size())));
mButtonDownload.setEnabled(true);
} else {
mButtonDownload.setText(getString(R.string.action_select));
mButtonDownload.setEnabled(false);
}
}
private void deleteRawTracks() {
File modeDir = new File(mBaseDir, "brouter/modes");
String[] fileNames = modeDir.list();
if (fileNames == null) return;
for (String fileName : fileNames) {
if (fileName.endsWith("_rawtrack.dat")) {
File f = new File(modeDir, fileName);
f.delete();
}
}
}
public void downloadAll(ArrayList<Integer> downloadList) {
ArrayList<String> urlparts = new ArrayList<>();
for (Integer i : downloadList) {
urlparts.add(baseNameForTile(i));
}
downloadCanceled = false;
Data inputData = new Data.Builder()
.putStringArray(DownloadWorker.KEY_INPUT_SEGMENT_NAMES, urlparts.toArray(new String[0]))
.build();
Constraints constraints = new Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build();
WorkRequest downloadWorkRequest =
new OneTimeWorkRequest.Builder(DownloadWorker.class)
.setInputData(inputData)
.setConstraints(constraints)
.build();
WorkManager workManager = WorkManager.getInstance(getApplicationContext());
workManager.enqueue(downloadWorkRequest);
workManager
.getWorkInfoByIdLiveData(downloadWorkRequest.getId())
.observe(this, workInfo -> {
if (workInfo != null) {
if (workInfo.getState() == WorkInfo.State.ENQUEUED) {
Toast.makeText(this, "Download scheduled. Check internet connection if it doesn't start.", Toast.LENGTH_LONG).show();
mProgressIndicator.hide();
mProgressIndicator.setIndeterminate(true);
mProgressIndicator.show();
}
if (workInfo.getState() == WorkInfo.State.RUNNING) {
Data progress = workInfo.getProgress();
String segmentName = progress.getString(DownloadWorker.PROGRESS_SEGMENT_NAME);
int percent = progress.getInt(DownloadWorker.PROGRESS_SEGMENT_PERCENT, 0);
if (percent > 0) {
mProgressIndicator.setIndeterminate(false);
}
mProgressIndicator.setProgress(percent);
}
if (workInfo.getState().isFinished()) {
String result;
switch (workInfo.getState()) {
case FAILED:
result = "failed.";
break;
case CANCELLED:
result = "cancelled";
break;
case SUCCEEDED:
result = "succeeded";
break;
default:
result = "";
}
Toast.makeText(this, "Download " + result + ".", Toast.LENGTH_SHORT).show();
mProgressIndicator.hide();
scanExistingFiles();
}
}
});
deleteRawTracks(); // invalidate raw-tracks after data update
} }
@Override @Override
protected void onResume() {
super.onResume();
/*
* when the activity is resumed, we acquire a wake-lock so that the
* screen stays on, since the user will likely not be fiddling with the
* screen or buttons.
*/
mWakeLock.acquire();
IntentFilter filter = new IntentFilter();
filter.addAction(DOWNLOAD_ACTION);
myReceiver = new DownloadReceiver();
registerReceiver(myReceiver, filter);
// Start the download manager
mBInstallerView.startInstaller();
}
@Override
protected void onPause() {
super.onPause();
super.onPause();
mWakeLock.release();
}
@Override
public void onDestroy() {
super.onDestroy();
if (myReceiver != null) unregisterReceiver(myReceiver);
System.exit(0);
}
@Override
@SuppressWarnings("deprecation")
protected Dialog onCreateDialog(int id) { protected Dialog onCreateDialog(int id) {
AlertDialog.Builder builder; AlertDialog.Builder builder;
switch (id) { switch (id) {
@ -116,7 +212,7 @@ public class BInstallerActivity extends Activity {
.setTitle("Confirm Delete") .setTitle("Confirm Delete")
.setMessage("Really delete?").setPositiveButton("Yes", new DialogInterface.OnClickListener() { .setMessage("Really delete?").setPositiveButton("Yes", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
mBInstallerView.deleteSelectedTiles(); deleteSelectedTiles();
} }
}).setNegativeButton("No", new DialogInterface.OnClickListener() { }).setNegativeButton("No", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
@ -129,29 +225,76 @@ public class BInstallerActivity extends Activity {
} }
} }
@SuppressWarnings("deprecation")
public void showConfirmDelete() { public void showConfirmDelete() {
showDialog(DIALOG_CONFIRM_DELETE_ID); showDialog(DIALOG_CONFIRM_DELETE_ID);
} }
private Set<Integer> dialogIds = new HashSet<Integer>(); private void scanExistingFiles() {
mBInstallerView.clearAllTilesStatus(MASK_CURRENT_RD5 | MASK_INSTALLED_RD5 | MASK_DELETED_RD5 | MASK_SELECTED_RD5);
private void showNewDialog(int id) { scanExistingFiles(new File(mBaseDir, "brouter/segments4"));
if (dialogIds.contains(Integer.valueOf(id))) {
removeDialog(id); File secondary = RoutingHelper.getSecondarySegmentDir(new File(mBaseDir, "brouter/segments4"));
if (secondary != null) {
scanExistingFiles(secondary);
} }
dialogIds.add(Integer.valueOf(id));
showDialog(id);
} }
private void scanExistingFiles(File dir) {
String[] fileNames = dir.list();
if (fileNames == null) return;
String suffix = ".rd5";
for (String fileName : fileNames) {
if (fileName.endsWith(suffix)) {
String basename = fileName.substring(0, fileName.length() - suffix.length());
int tileIndex = tileForBaseName(basename);
mBInstallerView.setTileStatus(tileIndex, MASK_INSTALLED_RD5);
static public long getAvailableSpace(String baseDir) { long age = System.currentTimeMillis() - new File(dir, fileName).lastModified();
StatFs stat = new StatFs(baseDir); if (age < 10800000) mBInstallerView.setTileStatus(tileIndex, MASK_CURRENT_RD5); // 3 hours
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
return stat.getAvailableBlocksLong() * stat.getBlockSizeLong();
} else {
return stat.getAvailableBlocks() * stat.getBlockSize();
} }
} }
}
private void deleteSelectedTiles() {
ArrayList<Integer> selectedTiles = mBInstallerView.getSelectedTiles(MASK_DELETED_RD5);
for (int tileIndex : selectedTiles) {
new File(mBaseDir, "brouter/segments4/" + baseNameForTile(tileIndex) + ".rd5").delete();
}
scanExistingFiles();
}
private void downloadSelectedTiles() {
ArrayList<Integer> selectedTiles = mBInstallerView.getSelectedTiles(MASK_SELECTED_RD5);
downloadAll(selectedTiles);
mBInstallerView.clearAllTilesStatus(MASK_SELECTED_RD5);
}
private void downloadInstalledTiles() {
ArrayList<Integer> selectedTiles = mBInstallerView.getSelectedTiles(MASK_INSTALLED_RD5);
downloadAll(selectedTiles);
}
private int tileForBaseName(String basename) {
String uname = basename.toUpperCase(Locale.ROOT);
int idx = uname.indexOf("_");
if (idx < 0) return -1;
String slon = uname.substring(0, idx);
String slat = uname.substring(idx + 1);
int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt(slon.substring(1)) :
(slon.charAt(0) == 'E' ? Integer.parseInt(slon.substring(1)) : -1);
int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt(slat.substring(1)) :
(slat.charAt(0) == 'N' ? Integer.parseInt(slat.substring(1)) : -1);
if (ilon < -180 || ilon >= 180 || ilon % 5 != 0) return -1;
if (ilat < -90 || ilat >= 90 || ilat % 5 != 0) return -1;
return (ilon + 180) / 5 + 72 * ((ilat + 90) / 5);
}
private String baseNameForTile(int tileIndex) {
int lon = (tileIndex % 72) * 5 - 180;
int lat = (tileIndex / 72) * 5 - 90;
String slon = lon < 0 ? "W" + (-lon) : "E" + lon;
String slat = lat < 0 ? "S" + (-lat) : "N" + lat;
return slon + "_" + slat;
}
} }

View file

@ -1,18 +1,7 @@
package btools.routingapp; package btools.routingapp;
import java.io.File; import android.annotation.SuppressLint;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.Locale;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
@ -20,262 +9,37 @@ import android.graphics.Canvas;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Matrix; import android.graphics.Matrix;
import android.graphics.Paint; import android.graphics.Paint;
import android.os.AsyncTask;
import android.os.PowerManager;
import android.os.StatFs;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.DisplayMetrics; import android.view.GestureDetector;
import android.util.Log;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View; import android.view.View;
import android.widget.Toast;
import btools.mapaccess.PhysicalFile; import java.io.IOException;
import btools.mapaccess.Rd5DiffManager; import java.io.InputStream;
import btools.mapaccess.Rd5DiffTool; import java.util.ArrayList;
import btools.router.RoutingHelper;
import btools.util.ProgressListener;
public class BInstallerView extends View { public class BInstallerView extends View {
private static final int MASK_SELECTED_RD5 = 1; public static final int MASK_SELECTED_RD5 = 1;
private static final int MASK_DELETED_RD5 = 2; public static final int MASK_DELETED_RD5 = 2;
private static final int MASK_INSTALLED_RD5 = 4; public static final int MASK_INSTALLED_RD5 = 4;
private static final int MASK_CURRENT_RD5 = 8; public static final int MASK_CURRENT_RD5 = 8;
private static final float SCALE_GRID_VISIBLE = 3;
private int imgwOrig; private final Bitmap bmp;
private int imghOrig; private final float[] testVector = new float[2];
private float scaleOrig; private final int[] tileStatus;
private final Matrix mat;
private int imgw; private final GestureDetector mGestureDetector;
private int imgh; private final ScaleGestureDetector mScaleGestureDetector;
Paint paintGrid = new Paint();
private float lastDownX; Paint paintTiles = new Paint();
private float lastDownY;
private Bitmap bmp;
private float viewscale; private float viewscale;
private float[] testVector = new float[2];
private int[] tileStatus;
private boolean tilesVisible = false; private boolean tilesVisible = false;
private OnSelectListener mOnSelectListener;
private long availableSize; public BInstallerView(Context context, AttributeSet attrs) {
private File baseDir; super(context, attrs);
private File segmentDir;
private boolean isDownloading = false;
public static boolean downloadCanceled = false;
private long currentDownloadSize;
private String currentDownloadFile = "";
private volatile String currentDownloadOperation = "";
private String downloadAction = "";
private volatile String newDownloadAction = "";
private long totalSize = 0;
private long rd5Tiles = 0;
private long delTiles = 0;
Paint pnt_1 = new Paint();
Paint pnt_2 = new Paint();
Paint paint = new Paint();
Activity mActivity;
protected String baseNameForTile(int tileIndex) {
int lon = (tileIndex % 72) * 5 - 180;
int lat = (tileIndex / 72) * 5 - 90;
String slon = lon < 0 ? "W" + (-lon) : "E" + lon;
String slat = lat < 0 ? "S" + (-lat) : "N" + lat;
return slon + "_" + slat;
}
private int gridPos2Tileindex(int ix, int iy) {
return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2);
}
private int tileForBaseName(String basename) {
String uname = basename.toUpperCase(Locale.ROOT);
int idx = uname.indexOf("_");
if (idx < 0) return -1;
String slon = uname.substring(0, idx);
String slat = uname.substring(idx + 1);
int ilon = slon.charAt(0) == 'W' ? -Integer.parseInt(slon.substring(1)) :
(slon.charAt(0) == 'E' ? Integer.parseInt(slon.substring(1)) : -1);
int ilat = slat.charAt(0) == 'S' ? -Integer.parseInt(slat.substring(1)) :
(slat.charAt(0) == 'N' ? Integer.parseInt(slat.substring(1)) : -1);
if (ilon < -180 || ilon >= 180 || ilon % 5 != 0) return -1;
if (ilat < -90 || ilat >= 90 || ilat % 5 != 0) return -1;
return (ilon + 180) / 5 + 72 * ((ilat + 90) / 5);
}
public boolean isDownloadCanceled() {
return downloadCanceled;
}
private void toggleDownload() {
if (isDownloading) {
downloadCanceled = true;
downloadAction = "Canceling...";
return;
}
if (delTiles > 0) {
((BInstallerActivity) getContext()).showConfirmDelete();
return;
}
int tidx_min = -1;
int min_size = Integer.MAX_VALUE;
ArrayList<Integer> downloadList = new ArrayList<>();
// prepare download list
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy);
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
int tilesize = BInstallerSizes.getRd5Size(tidx);
downloadList.add(tidx);
if (tilesize > 0 && tilesize < min_size) {
tidx_min = tidx;
min_size = tilesize;
}
}
}
}
if (downloadList.size() > 0) {
isDownloading = true;
downloadAll(downloadList);
for (Integer i : downloadList) {
tileStatus[i.intValue()] ^= tileStatus[i.intValue()] & MASK_SELECTED_RD5;
}
downloadList.clear();
}
}
private void downloadAll(ArrayList<Integer> downloadList) {
ArrayList<String> urlparts = new ArrayList<>();
for (Integer i : downloadList) {
urlparts.add(baseNameForTile(i.intValue()));
}
currentDownloadOperation = "Start download ...";
downloadAction = "";
downloadCanceled = false;
isDownloading = true;
//final DownloadBackground downloadTask = new DownloadBackground(getContext(), urlparts, baseDir);
//downloadTask.execute( );
Intent intent = new Intent(mActivity, DownloadService.class);
intent.putExtra("dir", baseDir.getAbsolutePath() + "/brouter/");
intent.putExtra("urlparts", urlparts);
mActivity.startService(intent);
deleteRawTracks(); // invalidate raw-tracks after data update
}
public void downloadDone(boolean success) {
isDownloading = false;
if (success) {
scanExistingFiles();
toggleDownload(); // keep on if no error
}
invalidate();
}
public void setState(String txt, boolean b) {
currentDownloadOperation = txt;
downloadAction = "";
isDownloading = b;
if (!b) {
scanExistingFiles();
}
invalidate();
}
private int tileIndex(float x, float y) {
int ix = (int) (72.f * x / bmp.getWidth());
int iy = (int) (36.f * y / bmp.getHeight());
if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy);
return -1;
}
private void clearTileSelection(int mask) {
// clear selection if zooming out
for (int ix = 0; ix < 72; ix++)
for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy);
tileStatus[tidx] ^= tileStatus[tidx] & mask;
}
}
// get back the current image scale
private float currentScale() {
testVector[1] = 1.f;
mat.mapVectors(testVector);
return testVector[1] / viewscale;
}
private void deleteRawTracks() {
File modeDir = new File(baseDir, "brouter/modes");
String[] fileNames = modeDir.list();
if (fileNames == null) return;
for (String fileName : fileNames) {
if (fileName.endsWith("_rawtrack.dat")) {
File f = new File(modeDir, fileName);
f.delete();
}
}
}
private void scanExistingFiles() {
clearTileSelection(MASK_INSTALLED_RD5 | MASK_CURRENT_RD5);
scanExistingFiles(new File(baseDir, "brouter/segments4"));
File secondary = RoutingHelper.getSecondarySegmentDir(new File(baseDir, "brouter/segments4"));
if (secondary != null) {
scanExistingFiles(secondary);
}
availableSize = -1;
try {
availableSize = (long) ((BInstallerActivity) getContext()).getAvailableSpace(baseDir.getAbsolutePath());
//StatFs stat = new StatFs(baseDir.getAbsolutePath ());
//availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong();
} catch (Exception e) { /* ignore */ }
}
private void scanExistingFiles(File dir) {
String[] fileNames = dir.list();
if (fileNames == null) return;
String suffix = ".rd5";
for (String fileName : fileNames) {
if (fileName.endsWith(suffix)) {
String basename = fileName.substring(0, fileName.length() - suffix.length());
int tidx = tileForBaseName(basename);
tileStatus[tidx] |= MASK_INSTALLED_RD5;
long age = System.currentTimeMillis() - new File(dir, fileName).lastModified();
if (age < 10800000) tileStatus[tidx] |= MASK_CURRENT_RD5; // 3 hours
}
}
}
private Matrix mat;
private Matrix matText;
public void startInstaller() {
baseDir = ConfigHelper.getBaseDir(getContext());
segmentDir = new File(baseDir, "brouter/segments4");
try { try {
AssetManager assetManager = getContext().getAssets(); AssetManager assetManager = getContext().getAssets();
InputStream istr = assetManager.open("world.png"); InputStream istr = assetManager.open("world.png");
@ -286,56 +50,111 @@ public class BInstallerView extends View {
} }
tileStatus = new int[72 * 36]; tileStatus = new int[72 * 36];
scanExistingFiles();
float scaleX = imgwOrig / ((float) bmp.getWidth());
float scaley = imghOrig / ((float) bmp.getHeight());
viewscale = scaleX < scaley ? scaleX : scaley;
mat = new Matrix(); mat = new Matrix();
mat.postScale(viewscale, viewscale); mGestureDetector = new GestureDetector(context, new GestureListener());
tilesVisible = false; mScaleGestureDetector = new ScaleGestureDetector(context, new ScaleGestureListener());
} }
public BInstallerView(Context context) { public void setOnSelectListener(OnSelectListener listener) {
super(context); mOnSelectListener = listener;
mActivity = (Activity) context; }
DisplayMetrics metrics = new DisplayMetrics(); private void setRatio(float ratio, float focusX, float focusY) {
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); if (currentScale() * ratio >= 1) {
imgwOrig = metrics.widthPixels; mat.postScale(ratio, ratio, focusX, focusY);
imghOrig = metrics.heightPixels; fitBounds();
int im = imgwOrig > imghOrig ? imgwOrig : imghOrig; tilesVisible = currentScale() >= SCALE_GRID_VISIBLE;
scaleOrig = im / 480.f; invalidate();
}
}
matText = new Matrix(); private void setScale(float scale, float focusX, float focusY) {
matText.preScale(scaleOrig, scaleOrig); float ratio = scale / currentScale();
setRatio(ratio, focusX, focusY);
}
imgw = (int) (imgwOrig / scaleOrig); public void setTileStatus(int tileIndex, int tileMask) {
imgh = (int) (imghOrig / scaleOrig); tileStatus[tileIndex] |= tileMask;
if (mOnSelectListener != null) {
mOnSelectListener.onSelect();
}
invalidate();
}
totalSize = 0; public void toggleTileStatus(int tileIndex, int tileMask) {
rd5Tiles = 0; tileStatus[tileIndex] ^= tileMask;
delTiles = 0; if (mOnSelectListener != null) {
mOnSelectListener.onSelect();
}
invalidate();
}
public void clearAllTilesStatus(int tileMask) {
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tileIndex = gridPos2Tileindex(ix, iy);
tileStatus[tileIndex] ^= tileStatus[tileIndex] & tileMask;
}
}
if (mOnSelectListener != null) {
mOnSelectListener.onSelect();
}
invalidate();
}
public ArrayList<Integer> getSelectedTiles(int tileMask) {
ArrayList<Integer> selectedTiles = new ArrayList<>();
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tileIndex = gridPos2Tileindex(ix, iy);
if ((tileStatus[tileIndex] & tileMask) != 0 && BInstallerSizes.getRd5Size(tileIndex) > 0) {
selectedTiles.add(tileIndex);
}
}
}
return selectedTiles;
}
private int gridPos2Tileindex(int ix, int iy) {
return (35 - iy) * 72 + (ix >= 70 ? ix - 70 : ix + 2);
}
private int tileIndex(float x, float y) {
int ix = (int) (72.f * x / bmp.getWidth());
int iy = (int) (36.f * y / bmp.getHeight());
if (ix >= 0 && ix < 72 && iy >= 0 && iy < 36) return gridPos2Tileindex(ix, iy);
return -1;
}
// get back the current image scale
private float currentScale() {
testVector[0] = 1.f;
mat.mapVectors(testVector);
return testVector[0] / viewscale;
} }
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh); super.onSizeChanged(w, h, oldw, oldh);
}
private void toast(String msg) { int imgwOrig = getWidth();
Toast.makeText(getContext(), msg, Toast.LENGTH_LONG).show(); int imghOrig = getHeight();
float scaleX = imgwOrig / ((float) bmp.getWidth());
float scaleY = imghOrig / ((float) bmp.getHeight());
viewscale = Math.max(scaleX, scaleY);
mat.postScale(viewscale, viewscale);
tilesVisible = false;
} }
@Override @Override
protected void onDraw(Canvas canvas) { protected void onDraw(Canvas canvas) {
if (!isDownloading) {
canvas.setMatrix(mat); canvas.setMatrix(mat);
canvas.drawBitmap(bmp, 0, 0, null); canvas.drawBitmap(bmp, 0, 0, null);
}
// draw 5*5 lattice starting at scale=3 // draw 5*5 lattice starting at scale=3
int iw = bmp.getWidth(); int iw = bmp.getWidth();
@ -343,290 +162,164 @@ public class BInstallerView extends View {
float fw = iw / 72.f; float fw = iw / 72.f;
float fh = ih / 36.f; float fh = ih / 36.f;
boolean drawGrid = tilesVisible && !isDownloading; if (tilesVisible) {
paintGrid.setColor(Color.GREEN);
if (drawGrid) { paintGrid.setStyle(Paint.Style.STROKE);
for (int ix = 0; ix < 72; ix++) {
pnt_1.setColor(Color.GREEN); for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy);
for (int ix = 1; ix < 72; ix++) { int tilesize = BInstallerSizes.getRd5Size(tidx);
float fx = fw * ix; if (tilesize > 0) {
canvas.drawLine(fx, 0, fx, ih, pnt_1); canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, paintGrid);
}
for (int iy = 1; iy < 36; iy++) {
float fy = fh * iy;
canvas.drawLine(0, fy, iw, fy, pnt_1);
} }
} }
rd5Tiles = 0; }
delTiles = 0;
totalSize = 0;
int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5; int mask2 = MASK_SELECTED_RD5 | MASK_DELETED_RD5 | MASK_INSTALLED_RD5;
int mask3 = mask2 | MASK_CURRENT_RD5; int mask3 = mask2 | MASK_CURRENT_RD5;
pnt_2.setColor(Color.GRAY); paintTiles.setStyle(Paint.Style.STROKE);
pnt_2.setStrokeWidth(1); paintTiles.setColor(Color.GRAY);
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5, mask3, false, false, drawGrid); paintTiles.setStrokeWidth(1);
pnt_2.setColor(Color.BLUE); drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5, mask3);
pnt_2.setStrokeWidth(1); paintTiles.setColor(Color.BLUE);
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3, false, false, drawGrid); paintTiles.setStrokeWidth(1);
pnt_2.setColor(Color.GREEN); drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_INSTALLED_RD5 | MASK_CURRENT_RD5, mask3);
pnt_2.setStrokeWidth(2); paintTiles.setColor(Color.GREEN);
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_SELECTED_RD5, mask2, true, false, drawGrid); paintTiles.setStrokeWidth(2);
pnt_2.setColor(Color.YELLOW); drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5, mask2);
pnt_2.setStrokeWidth(2); paintTiles.setColor(Color.YELLOW);
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2, true, false, drawGrid); paintTiles.setStrokeWidth(2);
pnt_2.setColor(Color.RED); drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_SELECTED_RD5 | MASK_INSTALLED_RD5, mask2);
pnt_2.setStrokeWidth(2); paintTiles.setColor(Color.RED);
drawSelectedTiles(canvas, pnt_2, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2, false, true, drawGrid); paintTiles.setStrokeWidth(2);
drawSelectedTiles(canvas, paintTiles, fw, fh, MASK_DELETED_RD5 | MASK_INSTALLED_RD5, mask2);
canvas.setMatrix(matText);
paint.setColor(Color.RED);
long mb = 1024 * 1024;
if (isDownloading) {
String sizeHint = currentDownloadSize > 0 ? " (" + ((currentDownloadSize + mb - 1) / mb) + " MB)" : "";
paint.setTextSize(30);
canvas.drawText(currentDownloadOperation, 30, (imgh / 3) * 2 - 30, paint);
// canvas.drawText( currentDownloadOperation + " " + currentDownloadFile + sizeHint, 30, (imgh/3)*2-30, paint);
canvas.drawText(downloadAction, 30, (imgh / 3) * 2, paint);
}
if (!tilesVisible && !isDownloading) {
paint.setTextSize(35);
canvas.drawText("Touch region to zoom in!", 30, (imgh / 3) * 2, paint);
}
paint.setTextSize(20);
String totmb = ((totalSize + mb - 1) / mb) + " MB";
String freemb = availableSize >= 0 ? ((availableSize + mb - 1) / mb) + " MB" : "?";
canvas.drawText("Selected segments=" + rd5Tiles, 10, 25, paint);
canvas.drawText("Size=" + totmb + " Free=" + freemb, 10, 45, paint);
String btnText = null;
if (isDownloading) btnText = "Cancel Download";
else if (delTiles > 0) btnText = "Delete " + delTiles + " tiles";
else if (rd5Tiles > 0) btnText = "Start Download";
else if (tilesVisible &&
rd5Tiles == 0 &&
RoutingHelper.hasDirectoryAnyDatafiles(segmentDir)) btnText = "Update all";
if (btnText != null) {
canvas.drawLine(imgw - btnw, imgh - btnh, imgw - btnw, imgh - 2, paint);
canvas.drawLine(imgw - btnw, imgh - btnh, imgw - 2, imgh - btnh, paint);
canvas.drawLine(imgw - btnw, imgh - btnh, imgw - btnw, imgh - 2, paint);
canvas.drawLine(imgw - 2, imgh - btnh, imgw - 2, imgh - 2, paint);
canvas.drawLine(imgw - btnw, imgh - 2, imgw - 2, imgh - 2, paint);
canvas.drawText(btnText, imgw - btnw + 5, imgh - 10, paint);
} }
} }
int btnh = 40; private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask) {
int btnw = 160;
float tx, ty;
private void drawSelectedTiles(Canvas canvas, Paint pnt, float fw, float fh, int status, int mask, boolean doCount, boolean cntDel, boolean doDraw) {
for (int ix = 0; ix < 72; ix++) for (int ix = 0; ix < 72; ix++)
for (int iy = 0; iy < 36; iy++) { for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy); int tidx = gridPos2Tileindex(ix, iy);
if ((tileStatus[tidx] & mask) == status) { if ((tileStatus[tidx] & mask) == status) {
int tilesize = BInstallerSizes.getRd5Size(tidx); int tilesize = BInstallerSizes.getRd5Size(tidx);
if (tilesize > 0) { if (tilesize > 0) {
if (doCount) {
rd5Tiles++;
totalSize += BInstallerSizes.getRd5Size(tidx);
}
if (cntDel) {
delTiles++;
totalSize += BInstallerSizes.getRd5Size(tidx);
}
if (!doDraw)
continue;
// draw cross // draw cross
canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), pnt); canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * (iy + 1), pnt);
canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt); canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt);
// draw frame // draw frame
canvas.drawLine(fw * ix, fh * iy, fw * (ix + 1), fh * iy, pnt); canvas.drawRect(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * iy, pnt);
canvas.drawLine(fw * ix, fh * (iy + 1), fw * (ix + 1), fh * (iy + 1), pnt);
canvas.drawLine(fw * ix, fh * iy, fw * ix, fh * (iy + 1), pnt);
canvas.drawLine(fw * (ix + 1), fh * iy, fw * (ix + 1), fh * (iy + 1), pnt);
} }
} }
} }
} }
public void deleteSelectedTiles() { private void fitBounds() {
for (int ix = 0; ix < 72; ix++) { float[] srcPoints = new float[]{
for (int iy = 0; iy < 36; iy++) { 0, 0,
int tidx = gridPos2Tileindex(ix, iy); bmp.getWidth(), bmp.getHeight()
if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) { };
new File(baseDir, "brouter/segments4/" + baseNameForTile(tidx) + ".rd5").delete(); float[] dstPoints = new float[srcPoints.length];
float transX = 0;
float transY = 0;
mat.mapPoints(dstPoints, srcPoints);
if (dstPoints[0] > 0) {
transX = -dstPoints[0];
} else if (dstPoints[2] < getWidth()) {
transX = getWidth() - dstPoints[2];
}
if (dstPoints[1] > 0) {
transY = -dstPoints[1];
} else if (dstPoints[3] < getHeight()) {
transY = getHeight() - dstPoints[3];
}
if (transX != 0 || transY != 0) {
mat.postTranslate(transX, transY);
}
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent event) {
boolean retVal = mScaleGestureDetector.onTouchEvent(event);
retVal = mGestureDetector.onTouchEvent(event) || retVal;
return retVal || super.onTouchEvent(event);
}
interface OnSelectListener {
void onSelect();
}
class GestureListener extends GestureDetector.SimpleOnGestureListener {
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (tilesVisible) {
Matrix imat = new Matrix();
if (mat.invert(imat)) {
float[] touchPoint = {e.getX(), e.getY()};
imat.mapPoints(touchPoint);
int tidx = tileIndex(touchPoint[0], touchPoint[1]);
if (tidx != -1) {
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
toggleTileStatus(tidx, MASK_SELECTED_RD5);
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
setTileStatus(tidx, MASK_DELETED_RD5);
}
} else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) {
toggleTileStatus(tidx, MASK_DELETED_RD5);
} else {
toggleTileStatus(tidx, MASK_SELECTED_RD5);
} }
} }
} }
scanExistingFiles();
invalidate(); invalidate();
} }
return true;
}
@Override @Override
public boolean onTouchEvent(MotionEvent event) { public boolean onDoubleTap(MotionEvent e) {
// get pointer index from the event object
int pointerIndex = event.getActionIndex();
// get pointer ID
int pointerId = event.getPointerId(pointerIndex);
// get masked (not specific to a pointer) action
int maskedAction = event.getActionMasked();
switch (maskedAction) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_POINTER_DOWN: {
lastDownX = event.getX();
lastDownY = event.getY();
break;
}
case MotionEvent.ACTION_MOVE: { // a pointer was moved
if (isDownloading) break;
int np = event.getPointerCount();
int nh = event.getHistorySize();
if (nh == 0) break;
float x0 = event.getX(0);
float y0 = event.getY(0);
float hx0 = event.getHistoricalX(0, 0);
float hy0 = event.getHistoricalY(0, 0);
if (np > 1) // multi-touch
{
float x1 = event.getX(1);
float y1 = event.getY(1);
float hx1 = event.getHistoricalX(1, 0);
float hy1 = event.getHistoricalY(1, 0);
float r = (float) Math.sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0));
float hr = (float) Math.sqrt((hx1 - hx0) * (hx1 - hx0) + (hy1 - hy0) * (hy1 - hy0));
if (hr > 0.) {
float ratio = r / hr;
float mx = (x1 + x0) / 2.f;
float my = (y1 + y0) / 2.f;
float scale = currentScale();
float newscale = scale * ratio;
if (newscale > 10.f) ratio *= (10.f / newscale);
if (newscale < 0.5f) ratio *= (0.5f / newscale);
mat.postScale(ratio, ratio, mx, my);
mat.postScale(ratio, ratio, mx, my);
boolean tilesv = currentScale() >= 3.f;
if (tilesVisible && !tilesv) {
clearTileSelection(MASK_SELECTED_RD5 | MASK_DELETED_RD5);
}
tilesVisible = tilesv;
}
break;
}
mat.postTranslate(x0 - hx0, y0 - hy0);
break;
}
case MotionEvent.ACTION_UP:
long downTime = event.getEventTime() - event.getDownTime();
if (downTime < 5 || downTime > 500) {
break;
}
if (Math.abs(lastDownX - event.getX()) > 10 || Math.abs(lastDownY - event.getY()) > 10) {
break;
}
// download button?
if ((delTiles > 0 || rd5Tiles >= 0 || isDownloading) && event.getX() > imgwOrig - btnw * scaleOrig && event.getY() > imghOrig - btnh * scaleOrig) {
if (rd5Tiles == 0) {
for (int ix = 0; ix < 72; ix++) {
for (int iy = 0; iy < 36; iy++) {
int tidx = gridPos2Tileindex(ix, iy);
if (tidx != -1) {
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
tileStatus[tidx] |= MASK_SELECTED_RD5;
}
}
}
}
}
toggleDownload();
invalidate();
break;
}
if (!tilesVisible) { if (!tilesVisible) {
float scale = currentScale(); setScale(5, e.getX(), e.getY());
if (scale > 0f && scale < 5f) {
float ratio = 5f / scale;
mat.postScale(ratio, ratio, event.getX(), event.getY());
tilesVisible = true;
}
break;
}
if (isDownloading) break;
Matrix imat = new Matrix();
if (mat.invert(imat)) {
float[] touchpoint = new float[2];
touchpoint[0] = event.getX();
touchpoint[1] = event.getY();
imat.mapPoints(touchpoint);
int tidx = tileIndex(touchpoint[0], touchpoint[1]);
if (tidx != -1) {
if ((tileStatus[tidx] & MASK_SELECTED_RD5) != 0) {
tileStatus[tidx] ^= MASK_SELECTED_RD5;
if ((tileStatus[tidx] & MASK_INSTALLED_RD5) != 0) {
tileStatus[tidx] |= MASK_DELETED_RD5;
}
} else if ((tileStatus[tidx] & MASK_DELETED_RD5) != 0) {
tileStatus[tidx] ^= MASK_DELETED_RD5;
} else { } else {
tileStatus[tidx] ^= MASK_SELECTED_RD5; setScale(1, e.getX(), e.getY());
} }
return true;
} }
tx = touchpoint[0]; @Override
ty = touchpoint[1]; public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
} mat.postTranslate(-distanceX, -distanceY);
fitBounds();
break;
case MotionEvent.ACTION_POINTER_UP:
case MotionEvent.ACTION_CANCEL: {
// TODO use data
break;
}
}
invalidate(); invalidate();
return true;
}
}
class ScaleGestureListener implements ScaleGestureDetector.OnScaleGestureListener {
@Override
public boolean onScaleBegin(ScaleGestureDetector scaleGestureDetector) {
return true;
}
@Override
public boolean onScale(ScaleGestureDetector scaleGestureDetector) {
float focusX = scaleGestureDetector.getFocusX();
float focusY = scaleGestureDetector.getFocusY();
float ratio = scaleGestureDetector.getScaleFactor();
setRatio(ratio, focusX, focusY);
return true; return true;
} }
@Override
public void onScaleEnd(ScaleGestureDetector scaleGestureDetector) {
}
}
} }

View file

@ -1,7 +1,24 @@
package btools.routingapp; package btools.routingapp;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.StatFs;
import android.widget.EditText;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.os.EnvironmentCompat;
import java.io.File; import java.io.File;
import java.lang.reflect.Method;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
@ -9,36 +26,9 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import android.app.Activity;
import android.app.ActivityManager;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import android.os.StatFs;
import android.speech.tts.TextToSpeech.OnInitListener;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.EditText;
import androidx.core.app.ActivityCompat;
import androidx.core.os.EnvironmentCompat;
import btools.router.OsmNodeNamed; import btools.router.OsmNodeNamed;
public class BRouterActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback { public class BRouterActivity extends AppCompatActivity implements ActivityCompat.OnRequestPermissionsResultCallback {
private static final int DIALOG_SELECTPROFILE_ID = 1; private static final int DIALOG_SELECTPROFILE_ID = 1;
private static final int DIALOG_EXCEPTION_ID = 2; private static final int DIALOG_EXCEPTION_ID = 2;
@ -129,11 +119,11 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques
.setMessage( .setMessage(
"*** Attention: ***\n\n" + "The Download Manager is used to download routing-data " "*** Attention: ***\n\n" + "The Download Manager is used to download routing-data "
+ "files which can be up to 170MB each. Do not start the Download Manager " + "on a cellular data connection without a data plan! " + "files which can be up to 170MB each. Do not start the Download Manager " + "on a cellular data connection without a data plan! "
+ "Download speed is restricted to 4 MBit/s.").setPositiveButton("I know", new DialogInterface.OnClickListener() { + "Download speed is restricted to 16 MBit/s.").setPositiveButton("I know", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class); Intent intent = new Intent(BRouterActivity.this, BInstallerActivity.class);
startActivity(intent); startActivity(intent);
// finish(); showNewDialog(DIALOG_MAINACTION_ID);
} }
}).setNegativeButton("Cancel", new DialogInterface.OnClickListener() { }).setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) { public void onClick(DialogInterface dialog, int id) {
@ -391,39 +381,12 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques
private String maptoolDirCandidate; private String maptoolDirCandidate;
public boolean isOnline(Context context) {
boolean result = false;
ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network nw = connectivityManager.getActiveNetwork();
if (nw == null) return false;
NetworkCapabilities nwc = connectivityManager.getNetworkCapabilities(nw);
if (nwc == null) return false;
result = nwc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) |
nwc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) |
nwc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET);
} else {
NetworkInfo ni = connectivityManager.getActiveNetworkInfo();
if (ni == null) return false;
result = ni.getType() == ConnectivityManager.TYPE_WIFI ||
ni.getType() == ConnectivityManager.TYPE_MOBILE ||
ni.getType() == ConnectivityManager.TYPE_ETHERNET;
}
return result;
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
public void selectProfile(String[] items) { public void selectProfile(String[] items) {
availableProfiles = items; availableProfiles = items;
// if we have internet access, first show the main action dialog // show main dialog
if (isOnline(this)) {
showDialog(DIALOG_MAINACTION_ID); showDialog(DIALOG_MAINACTION_ID);
} else {
showDialog(DIALOG_SELECTPROFILE_ID);
}
} }
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@ -626,6 +589,7 @@ public class BRouterActivity extends Activity implements ActivityCompat.OnReques
@Override @Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 0) { if (requestCode == 0) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
mBRouterView.startSetup(null, true); mBRouterView.startSetup(null, true);

View file

@ -1,5 +1,21 @@
package btools.routingapp; package btools.routingapp;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.os.Environment;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
@ -12,36 +28,13 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import java.util.zip.ZipInputStream;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.os.Environment;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import btools.expressions.BExpressionContextWay;
import btools.expressions.BExpressionMetaData; import btools.expressions.BExpressionMetaData;
import btools.mapaccess.OsmNode; import btools.mapaccess.OsmNode;
import btools.router.OsmNodeNamed; import btools.router.OsmNodeNamed;
@ -108,11 +101,6 @@ public class BRouterView extends View {
public void init() { public void init() {
try { try {
DisplayMetrics metrics = new DisplayMetrics();
((Activity) getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics);
imgw = metrics.widthPixels;
imgh = metrics.heightPixels;
// get base dir from private file // get base dir from private file
File baseDir = ConfigHelper.getBaseDir(getContext()); File baseDir = ConfigHelper.getBaseDir(getContext());
// check if valid // check if valid
@ -123,6 +111,15 @@ public class BRouterView extends View {
if (brd.isDirectory()) { if (brd.isDirectory()) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q && if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q &&
!brd.getAbsolutePath().contains("/Android/media/btools.routingapp")) { !brd.getAbsolutePath().contains("/Android/media/btools.routingapp")) {
// don't ask twice
String version = "v" + getContext().getString(R.string.app_version);
File vFile = new File(brd, "profiles2/"+version );
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q
&& vFile.exists()) {
startSetup(baseDir, false);
return;
}
String message = "(previous basedir " + baseDir + " has to migrate )"; String message = "(previous basedir " + baseDir + " has to migrate )";
((BRouterActivity) getContext()).selectBasedir(((BRouterActivity) getContext()).getStorageDirectories(), guessBaseDir(), message); ((BRouterActivity) getContext()).selectBasedir(((BRouterActivity) getContext()).getStorageDirectories(), guessBaseDir(), message);
@ -204,6 +201,8 @@ public class BRouterView extends View {
// new init is done move old files // new init is done move old files
if (waitingForMigration) { if (waitingForMigration) {
Log.d("BR", "path " + oldMigrationPath + " " + basedir);
if (!oldMigrationPath.equals(basedir + "/brouter"))
moveFolders(oldMigrationPath, basedir + "/brouter"); moveFolders(oldMigrationPath, basedir + "/brouter");
waitingForMigration = false; waitingForMigration = false;
} }
@ -254,7 +253,7 @@ public class BRouterView extends View {
} }
} }
if (tracksDir == null) { if (tracksDir == null) {
tracksDir = new File(basedir, "router"); // fallback tracksDir = new File(basedir, "brouter"); // fallback
} }
String[] fileNames = profileDir.list(); String[] fileNames = profileDir.list();
@ -569,7 +568,7 @@ public class BRouterView extends View {
// for profile remote, use ref-track logic same as service interface // for profile remote, use ref-track logic same as service interface
rc.rawTrackPath = rawTrackPath; rc.rawTrackPath = rawTrackPath;
cr = new RoutingEngine(tracksDir + "/brouter", null, segmentDir, wpList, rc); cr = new RoutingEngine(tracksDir.getAbsolutePath()+"/brouter", null, segmentDir, wpList, rc);
cr.start(); cr.start();
invalidate(); invalidate();
@ -606,6 +605,7 @@ public class BRouterView extends View {
break; break;
String name = ze.getName(); String name = ze.getName();
File outfile = new File(path, name); File outfile = new File(path, name);
if (!outfile.exists()) {
outfile.getParentFile().mkdirs(); outfile.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream(outfile); FileOutputStream fos = new FileOutputStream(outfile);
@ -617,6 +617,7 @@ public class BRouterView extends View {
} }
fos.close(); fos.close();
} }
}
is.close(); is.close();
return true; return true;
} catch (IOException io) { } catch (IOException io) {
@ -690,6 +691,8 @@ public class BRouterView extends View {
@Override @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) { protected void onSizeChanged(int w, int h, int oldw, int oldh) {
imgw = w;
imgh = h;
} }
private void toast(String msg) { private void toast(String msg) {

View file

@ -1,487 +0,0 @@
package btools.routingapp;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.net.TrafficStats;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import btools.mapaccess.PhysicalFile;
import btools.mapaccess.Rd5DiffManager;
import btools.mapaccess.Rd5DiffTool;
import btools.util.ProgressListener;
public class DownloadService extends Service implements ProgressListener {
private static final boolean DEBUG = false;
private ServerConfig mServerConfig;
private NotificationHelper mNotificationHelper;
private List<String> mUrlList;
private String baseDir;
private volatile String newDownloadAction = "";
private volatile String currentDownloadOperation = "";
private long availableSize;
private Looper mServiceLooper;
private ServiceHandler mServiceHandler;
private NotificationManager mNM;
String downloadUrl;
public static boolean serviceState = false;
private boolean bIsDownloading;
// Handler that receives messages from the thread
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
bIsDownloading = true;
downloadFiles();
stopForeground(true);
stopSelf(msg.arg1);
mNotificationHelper.stopNotification();
}
}
@Override
public void onCreate() {
if (DEBUG) Log.d("SERVICE", "onCreate");
serviceState = true;
mServerConfig = new ServerConfig(getApplicationContext());
HandlerThread thread = new HandlerThread("ServiceStartArguments", 1);
thread.start();
// Get the HandlerThread's Looper and use it for our Handler
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
availableSize = -1;
try {
availableSize = BInstallerActivity.getAvailableSpace(baseDir);
//StatFs stat = new StatFs(baseDir);
//availableSize = (long)stat.getAvailableBlocksLong()*stat.getBlockSizeLong();
} catch (Exception e) { /* ignore */ }
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (DEBUG) Log.d("SERVICE", "onStartCommand");
mNotificationHelper = new NotificationHelper(this);
Bundle extra = intent.getExtras();
if (extra != null) {
String dir = extra.getString("dir");
List<String> urlparts = extra.getStringArrayList("urlparts");
mUrlList = urlparts;
baseDir = dir;
}
mNotificationHelper.startNotification(this);
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
mServiceHandler.sendMessage(msg);
// If we get killed, after returning from here, restart
return START_STICKY;
}
@Override
public void onDestroy() {
if (DEBUG) Log.d("SERVICE", "onDestroy");
serviceState = false;
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
public void downloadFiles() {
// first check lookup table and profiles
String result = checkScripts();
if (result != null) {
if (DEBUG) Log.d("BR", "error: " + result);
bIsDownloading = false;
updateProgress("finished ");
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
return;
}
int count = 1;
int size = mUrlList.size();
for (String part : mUrlList) {
String url = mServerConfig.getSegmentUrl() + part + ".rd5";
if (DEBUG) Log.d("BR", "downlaod " + url);
result = download(count, size, url);
if (result != null) {
if (DEBUG) Log.d("BR", "" + result);
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
break;
} else {
updateProgress("Download " + part + " " + count + "/" + size + " finshed");
}
count++;
}
bIsDownloading = false;
updateProgress("finished ");
}
public void updateProgress(String progress) {
if (!newDownloadAction.equals(progress)) {
if (DEBUG) Log.d("BR", "up " + progress);
Intent intent = new Intent(BInstallerActivity.DOWNLOAD_ACTION);
intent.putExtra("txt", progress);
intent.putExtra("ready", bIsDownloading);
sendBroadcast(intent);
;
newDownloadAction = progress;
mNotificationHelper.progressUpdate(newDownloadAction);
}
}
private String download(int counter, int size, String surl) {
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
File fname = null;
File tmp_file = null;
try {
try {
TrafficStats.setThreadStatsTag(1);
int slidx = surl.lastIndexOf("segments4/");
String name = surl.substring(slidx + 10);
String surlBase = surl.substring(0, slidx + 10);
fname = new File(baseDir, "segments4/" + name);
boolean delta = true;
// if (!targetFile.getParentFile().exists()) targetFile.getParentFile().mkdirs();
if (fname.exists()) {
updateProgress("Calculating local checksum..");
// first check for a delta file
String md5 = Rd5DiffManager.getMD5(fname);
String surlDelta = surlBase + "diff/" + name.replace(".rd5", "/" + md5 + ".df5");
URL urlDelta = new URL(surlDelta);
connection = (HttpURLConnection) urlDelta.openConnection();
connection.setConnectTimeout(5000);
connection.connect();
// 404 kind of expected here, means there's no delta file
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
connection = null;
} else {
updateProgress("Connecting.." + surlDelta);
}
}
if (connection == null) {
updateProgress("Connecting.." + name);
delta = false;
URL url = new URL(surl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.connect();
}
updateProgress("Connecting.." + counter + "/" + size);
// expect HTTP 200 OK, so we don't mistakenly save error report
// instead of the file
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage();
}
// this will be useful to display download percentage
// might be -1: server did not report the length
int fileLength = connection.getContentLength();
long currentDownloadSize = fileLength;
if (availableSize >= 0 && fileLength > availableSize) return "not enough space on sd-card";
currentDownloadOperation = delta ? "Updating" : "Loading";
updateProgress(currentDownloadOperation);
// download the file
input = connection.getInputStream();
tmp_file = new File(fname.getAbsolutePath() + (delta ? "_diff" : "_tmp"));
output = new FileOutputStream(tmp_file);
byte[] data = new byte[4096];
long total = 0;
long t0 = System.currentTimeMillis();
int count;
while ((count = input.read(data)) != -1) {
if (isCanceled()) {
return "Download canceled!";
}
total += count;
// publishing the progress....
if (fileLength > 0) // only if total length is known
{
int pct = (int) (total * 100 / fileLength);
updateProgress("Progress " + counter + "/" + size + " .. " + pct + "%");
} else {
updateProgress("Progress (unnown size)");
}
output.write(data, 0, count);
// enforce < 2 Mbit/s
long dt = t0 + total / 524 - System.currentTimeMillis();
if (dt > 0) {
try {
Thread.sleep(dt);
} catch (InterruptedException ie) {
}
}
}
output.close();
output = null;
if (delta) {
updateProgress("Applying delta..");
File diffFile = tmp_file;
tmp_file = new File(fname + "_tmp");
Rd5DiffTool.recoverFromDelta(fname, diffFile, tmp_file, this);
diffFile.delete();
}
if (isCanceled()) {
return "Canceled!";
}
if (tmp_file != null) {
updateProgress("Verifying integrity..");
String check_result = PhysicalFile.checkFileIntegrity(tmp_file);
if (check_result != null) {
if (check_result.startsWith("version old lookups.dat")) {
}
return check_result;
}
if (fname.exists()) fname.delete();
if (!tmp_file.renameTo(fname)) {
return "Could not rename to " + fname.getAbsolutePath();
}
}
return null;
} catch (Exception e) {
//e.printStackTrace(); ;
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
} finally {
if (tmp_file != null) tmp_file.delete(); // just to be sure
}
}
private String checkScripts() {
String[] sa = mServerConfig.getLookups();
for (String f : sa) {
if (f.length() > 0) {
File file = new File(baseDir + "profiles2", f);
checkOrDownloadLookup(f, file);
}
}
sa = mServerConfig.getProfiles();
for (String f : sa) {
if (f.length() > 0) {
File file = new File(baseDir + "profiles2", f);
if (file.exists()) {
String result = checkOrDownloadScript(f, file);
if (result != null) {
return result;
}
}
}
}
return null;
}
private String checkOrDownloadLookup(String fileName, File f) {
String url = mServerConfig.getLookupUrl() + fileName;
return downloadScript(url, f);
}
private String checkOrDownloadScript(String fileName, File f) {
String url = mServerConfig.getProfilesUrl() + fileName;
return downloadScript(url, f);
}
private String downloadScript(String surl, File f) {
long size = 0L;
if (f.exists()) {
size = f.length();
}
InputStream input = null;
OutputStream output = null;
HttpURLConnection connection = null;
File tmp_file = null;
File targetFile = f;
try {
try {
TrafficStats.setThreadStatsTag(1);
URL url = new URL(surl);
connection = (HttpURLConnection) url.openConnection();
connection.setConnectTimeout(5000);
connection.connect();
// expect HTTP 200 OK, so we don't mistakenly save error report
// instead of the file
if (connection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
return null;
}
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
return "Server returned HTTP " + connection.getResponseCode()
+ " " + connection.getResponseMessage() + " " + f.getName();
}
// this will be useful to display download percentage
// might be -1: server did not report the length
long fileLength = (long) connection.getContentLength();
if (DEBUG) Log.d("BR", "file size " + size + " == " + fileLength + " " + f.getName());
if (fileLength != size) {
long currentDownloadSize = fileLength;
if (availableSize >= 0 && fileLength > availableSize)
return "not enough space on sd-card";
currentDownloadOperation = "Updating";
// download the file
input = connection.getInputStream();
tmp_file = new File(f.getAbsolutePath() + "_tmp");
output = new FileOutputStream(tmp_file);
byte data[] = new byte[4096];
long total = 0;
long t0 = System.currentTimeMillis();
int count;
while ((count = input.read(data)) != -1) {
if (isCanceled()) {
return "Download canceled!";
}
total += count;
// publishing the progress....
if (fileLength > 0) // only if total length is known
{
int pct = (int) (total * 100 / fileLength);
updateProgress("Progress " + pct + "%");
} else {
updateProgress("Progress (unnown size)");
}
output.write(data, 0, count);
// enforce < 2 Mbit/s
long dt = t0 + total / 524 - System.currentTimeMillis();
if (dt > 0) {
try {
Thread.sleep(dt);
} catch (InterruptedException ie) {
}
}
}
output.close();
output = null;
}
if (isCanceled()) {
return "Canceled!";
}
if (tmp_file != null) {
f.delete();
if (!tmp_file.renameTo(f)) {
return "Could not rename to " + f.getName();
}
if (DEBUG) Log.d("BR", "update " + f.getName());
}
return null;
} catch (Exception e) {
return e.toString();
} finally {
try {
if (output != null)
output.close();
if (input != null)
input.close();
} catch (IOException ignored) {
}
if (connection != null)
connection.disconnect();
}
} finally {
if (tmp_file != null) tmp_file.delete(); // just to be sure
}
}
public boolean isCanceled() {
return BInstallerView.downloadCanceled;
}
}

View file

@ -0,0 +1,327 @@
package btools.routingapp;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat;
import androidx.work.Data;
import androidx.work.ForegroundInfo;
import androidx.work.WorkManager;
import androidx.work.Worker;
import androidx.work.WorkerParameters;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Random;
import btools.mapaccess.PhysicalFile;
import btools.mapaccess.Rd5DiffManager;
import btools.mapaccess.Rd5DiffTool;
import btools.util.ProgressListener;
public class DownloadWorker extends Worker {
public static final String KEY_INPUT_SEGMENT_NAMES = "SEGMENT_NAMES";
public static final String PROGRESS_SEGMENT_NAME = "PROGRESS_SEGMENT_NAME";
public static final String PROGRESS_SEGMENT_PERCENT = "PROGRESS_SEGMENT_PERCENT";
private final static boolean DEBUG = false;
private static final int NOTIFICATION_ID = new Random().nextInt();
private static final String PROFILES_DIR = "profiles2/";
private static final String SEGMENTS_DIR = "segments4/";
private static final String SEGMENT_DIFF_SUFFIX = ".df5";
private static final String SEGMENT_SUFFIX = ".rd5";
private static final String LOG_TAG = "DownloadWorker";
private final NotificationManager notificationManager;
private final ServerConfig mServerConfig;
private final File baseDir;
private final ProgressListener diffProgressListener;
private final DownloadProgressListener downloadProgressListener;
private final Data.Builder progressBuilder = new Data.Builder();
private final NotificationCompat.Builder notificationBuilder;
public DownloadWorker(
@NonNull Context context,
@NonNull WorkerParameters parameters) {
super(context, parameters);
notificationManager = (NotificationManager)
context.getSystemService(Context.NOTIFICATION_SERVICE);
mServerConfig = new ServerConfig(context);
baseDir = new File(ConfigHelper.getBaseDir(context), "brouter");
notificationBuilder = createNotificationBuilder();
downloadProgressListener = new DownloadProgressListener() {
private String currentDownloadName;
private DownloadType currentDownloadType;
private int lastProgressPercent;
@Override
public void onDownloadStart(String downloadName, DownloadType downloadType) {
currentDownloadName = downloadName;
currentDownloadType = downloadType;
if (downloadType == DownloadType.SEGMENT) {
progressBuilder.putString(PROGRESS_SEGMENT_NAME, downloadName);
notificationBuilder.setContentText(downloadName);
}
}
@Override
public void onDownloadInfo(String info) {
notificationBuilder.setContentText(currentDownloadName + ": " + info);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
@Override
public void onDownloadProgress(int max, int progress) {
int progressPercent = (int) (progress * 100L / max);
// Only report segments and update if it changed to avoid hammering NotificationManager
if (currentDownloadType != DownloadType.SEGMENT || progressPercent == lastProgressPercent) {
return;
}
if (max > 0) {
notificationBuilder.setProgress(max, progress, false);
progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, progressPercent);
} else {
notificationBuilder.setProgress(0, 0, true);
progressBuilder.putInt(PROGRESS_SEGMENT_PERCENT, -1);
}
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
setProgressAsync(progressBuilder.build());
lastProgressPercent = progressPercent;
}
@Override
public void onDownloadFinished() {
}
};
diffProgressListener = new ProgressListener() {
@Override
public void updateProgress(String task, int progress) {
downloadProgressListener.onDownloadInfo(task);
downloadProgressListener.onDownloadProgress(100, progress);
}
@Override
public boolean isCanceled() {
return isStopped();
}
};
}
@NonNull
@Override
public Result doWork() {
Data inputData = getInputData();
String[] segmentNames = inputData.getStringArray(KEY_INPUT_SEGMENT_NAMES);
if (segmentNames == null) {
if (DEBUG) Log.d(LOG_TAG, "Failure: no segmentNames");
return Result.failure();
}
notificationBuilder.setContentText("Starting Download");
// Mark the Worker as important
setForegroundAsync(new ForegroundInfo(NOTIFICATION_ID, notificationBuilder.build()));
try {
if (DEBUG) Log.d(LOG_TAG, "Download lookup & profiles");
downloadLookupAndProfiles();
for (String segmentName : segmentNames) {
downloadProgressListener.onDownloadStart(segmentName, DownloadType.SEGMENT);
if (DEBUG) Log.d(LOG_TAG, "Download segment " + segmentName);
downloadSegment(mServerConfig.getSegmentUrl(), segmentName + SEGMENT_SUFFIX);
}
} catch (IOException e) {
Log.w(LOG_TAG, e);
return Result.failure();
} catch (InterruptedException e) {
Log.w(LOG_TAG, e);
return Result.failure();
}
if (DEBUG) Log.d(LOG_TAG, "doWork finished");
return Result.success();
}
private void downloadLookupAndProfiles() throws IOException, InterruptedException {
String[] lookups = mServerConfig.getLookups();
for (String fileName : lookups) {
if (fileName.length() > 0) {
File lookupFile = new File(baseDir, PROFILES_DIR + fileName);
String lookupLocation = mServerConfig.getLookupUrl() + fileName;
URL lookupUrl = new URL(lookupLocation);
downloadProgressListener.onDownloadStart(fileName, DownloadType.LOOKUP);
downloadFile(lookupUrl, lookupFile, false);
downloadProgressListener.onDownloadFinished();
}
}
String[] profiles = mServerConfig.getProfiles();
for (String fileName : profiles) {
if (fileName.length() > 0) {
File profileFile = new File(baseDir, PROFILES_DIR + fileName);
if (profileFile.exists()) {
String profileLocation = mServerConfig.getProfilesUrl() + fileName;
URL profileUrl = new URL(profileLocation);
downloadProgressListener.onDownloadStart(fileName, DownloadType.PROFILE);
downloadFile(profileUrl, profileFile, false);
downloadProgressListener.onDownloadFinished();
}
}
}
}
private void downloadSegment(String segmentBaseUrl, String segmentName) throws IOException, InterruptedException {
File segmentFile = new File(baseDir, SEGMENTS_DIR + segmentName);
File segmentFileTemp = new File(segmentFile.getAbsolutePath() + "_tmp");
try {
if (segmentFile.exists()) {
if (DEBUG) Log.d(LOG_TAG, "Calculating local checksum");
String md5 = Rd5DiffManager.getMD5(segmentFile);
String segmentDeltaLocation = segmentBaseUrl + "diff/" + segmentName.replace(SEGMENT_SUFFIX, "/" + md5 + SEGMENT_DIFF_SUFFIX);
URL segmentDeltaUrl = new URL(segmentDeltaLocation);
if (httpFileExists(segmentDeltaUrl)) {
File segmentDeltaFile = new File(segmentFile.getAbsolutePath() + "_diff");
try {
downloadFile(segmentDeltaUrl, segmentDeltaFile, true);
if (DEBUG) Log.d(LOG_TAG, "Applying delta");
Rd5DiffTool.recoverFromDelta(segmentFile, segmentDeltaFile, segmentFileTemp, diffProgressListener);
} catch (IOException e) {
throw new IOException("Failed to download & apply delta update", e);
} finally {
segmentDeltaFile.delete();
}
}
}
if (!segmentFileTemp.exists()) {
URL segmentUrl = new URL(segmentBaseUrl + segmentName);
downloadFile(segmentUrl, segmentFileTemp, true);
}
PhysicalFile.checkFileIntegrity(segmentFileTemp);
if (segmentFile.exists()) {
if (!segmentFile.delete()) {
throw new IOException("Failed to delete existing " + segmentFile.getAbsolutePath());
}
}
if (!segmentFileTemp.renameTo(segmentFile)) {
throw new IOException("Failed to write " + segmentFile.getAbsolutePath());
}
} finally {
segmentFileTemp.delete();
}
}
private boolean httpFileExists(URL downloadUrl) throws IOException {
HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection();
connection.setConnectTimeout(5000);
connection.setRequestMethod("HEAD");
connection.connect();
return connection.getResponseCode() == HttpURLConnection.HTTP_OK;
}
private void downloadFile(URL downloadUrl, File outputFile, boolean limitDownloadSpeed) throws IOException, InterruptedException {
HttpURLConnection connection = (HttpURLConnection) downloadUrl.openConnection();
connection.setConnectTimeout(5000);
connection.connect();
if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
throw new IOException("HTTP Request failed");
}
int fileLength = connection.getContentLength();
try (
InputStream input = connection.getInputStream();
OutputStream output = new FileOutputStream(outputFile)
) {
byte[] buffer = new byte[4096];
int total = 0;
long t0 = System.currentTimeMillis();
int count;
while ((count = input.read(buffer)) != -1) {
if (isStopped()) {
throw new InterruptedException();
}
total += count;
output.write(buffer, 0, count);
downloadProgressListener.onDownloadProgress(fileLength, total);
if (limitDownloadSpeed) {
// enforce < 16 Mbit/s
long dt = t0 + total / 2096 - System.currentTimeMillis();
if (dt > 0) {
Thread.sleep(dt);
}
}
}
}
}
@NonNull
private NotificationCompat.Builder createNotificationBuilder() {
Context context = getApplicationContext();
String id = context.getString(R.string.notification_channel_id);
String title = context.getString(R.string.notification_title);
String cancel = context.getString(R.string.cancel_download);
// This PendingIntent can be used to cancel the worker
PendingIntent intent = WorkManager.getInstance(context)
.createCancelPendingIntent(getId());
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
createChannel();
}
return new NotificationCompat.Builder(context, id)
.setContentTitle(title)
.setTicker(title)
.setOnlyAlertOnce(true)
.setPriority(NotificationCompat.PRIORITY_LOW)
.setSmallIcon(android.R.drawable.stat_sys_download)
.setOngoing(true)
// Add the cancel action to the notification which can
// be used to cancel the worker
.addAction(android.R.drawable.ic_delete, cancel, intent);
}
@RequiresApi(Build.VERSION_CODES.O)
private void createChannel() {
CharSequence name = getApplicationContext().getString(R.string.channel_name);
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel(getApplicationContext().getString(R.string.notification_channel_id), name, importance);
// Register the channel with the system; you can't change the importance
// or other notification behaviors after this
notificationManager.createNotificationChannel(channel);
}
enum DownloadType {
LOOKUP,
PROFILE,
SEGMENT
}
interface DownloadProgressListener {
void onDownloadStart(String downloadName, DownloadType downloadType);
void onDownloadInfo(String info);
void onDownloadProgress(int max, int progress);
void onDownloadFinished();
}
}

View file

@ -1,134 +0,0 @@
package btools.routingapp;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.media.AudioAttributes;
import android.os.Build;
import android.util.Log;
import androidx.core.app.NotificationCompat;
import static android.content.Context.NOTIFICATION_SERVICE;
public class NotificationHelper {
private static final boolean DEBUG = false;
public static String BRouterNotificationChannel1 = "brouter_channel_01";
private Context mContext;
private int NOTIFICATION_ID = 111;
private Notification mNotification;
private NotificationManager mNotificationManager;
private PendingIntent mContentIntent;
private CharSequence mContentTitle;
public NotificationHelper(Context context) {
if (DEBUG) Log.d("NH", "init ");
mContext = context;
createNotificationChannels();
}
public void startNotification(Service service) {
if (DEBUG) Log.d("NH", "startNotification ");
mNotification = createNotification("BRouter Download", "Download some files");
if (service != null) service.startForeground(NOTIFICATION_ID, mNotification);
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
}
public void progressUpdate(String text) {
mNotification = createNotification("BRouter Download", text);
mNotification.flags = Notification.FLAG_NO_CLEAR |
Notification.FLAG_ONGOING_EVENT;
mNotificationManager.notify(NOTIFICATION_ID, mNotification);
}
public Notification createNotification(String title, String desc) {
Intent resultIntent = new Intent(mContext, BInstallerActivity.class);
Intent notificationIntent = new Intent();
mContentIntent = PendingIntent.getActivity(mContext, 0, resultIntent, PendingIntent.FLAG_IMMUTABLE);
mNotificationManager = (NotificationManager) mContext.getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext, BRouterNotificationChannel1);
builder.setSmallIcon(android.R.drawable.stat_sys_download)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(title)
.setContentText(desc)
.setTicker(desc)
.setOngoing(true)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentIntent(mContentIntent);
return builder.build();
} else {
final NotificationCompat.Builder builder = new NotificationCompat.Builder(mContext);
builder.setSmallIcon(android.R.drawable.stat_sys_download)
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setContentTitle(title)
.setContentText(desc)
.setOnlyAlertOnce(true)
.setCategory(NotificationCompat.CATEGORY_SERVICE)
.setContentIntent(mContentIntent);
return builder.build();
}
}
/**
* create notification channels
*/
public void createNotificationChannels() {
if (DEBUG) Log.d("NH", "createNotificationChannels ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager sNotificationManager = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE);
// Sound channel
CharSequence name = "BRouter Download";
// The user-visible description of the channel.
String description = "BRouter Download Channel"; //getString(R.string.channel_description);
NotificationChannel channel = new NotificationChannel(BRouterNotificationChannel1, name, NotificationManager.IMPORTANCE_LOW);
channel.setDescription(description);
AudioAttributes att = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_UNKNOWN)
.setContentType(AudioAttributes.CONTENT_TYPE_UNKNOWN)
.build();
channel.setSound(null, null);
channel.setLockscreenVisibility(Notification.VISIBILITY_PUBLIC);
sNotificationManager.createNotificationChannel(channel);
}
}
public void stopNotification() {
if (DEBUG) Log.d("NH", "stopNotification ");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
mNotificationManager.deleteNotificationChannel(BRouterNotificationChannel1);
}
mNotificationManager.cancel(NOTIFICATION_ID);
}
}

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/view_segments"
android:layout_width="match_parent"
android:layout_height="match_parent">
<btools.routingapp.BInstallerView
android:id="@+id/BInstallerView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/buttonDownload"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:layout_marginBottom="8dp"
android:text="@string/action_select"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
<TextView
android:id="@+id/textViewSegmentSummary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginBottom="8dp"
android:textColor="@android:color/primary_text_light"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progressDownload"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="invisible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -15,7 +15,22 @@
--> -->
<resources> <resources>
<plurals name="numberOfSegments">
<item quantity="one">%d segment</item>
<item quantity="other">%d segments</item>
</plurals>
<string name="app_name">BRouter</string> <string name="app_name">BRouter</string>
<string name="cancel_download">Cancel Download</string>
<string name="import_profile">Import Profile</string> <string name="import_profile">Import Profile</string>
<string name="profile_filename_example">filename.brf</string> <string name="profile_filename_example">filename.brf</string>
<string name="download_info_start">Starting download…</string>
<string name="download_info_cancel">Cancelling…</string>
<string name="action_download">Download %s</string>
<string name="action_delete">Delete %s</string>
<string name="action_update">Update %s</string>
<string name="action_select">Select segments</string>
<string name="summary_segments">Size=%s\nFree=%s</string>
<string name="notification_channel_id">brouter_download</string>
<string name="notification_title">Download Segments</string>
<string name="channel_name">Downloads</string>
</resources> </resources>

View file

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="Theme.App" parent="Theme.AppCompat.Light.NoActionBar">
<item name="linearProgressIndicatorStyle">@style/Widget.App.LinearProgressIndicator</item>
</style>
<style name="Widget.App.LinearProgressIndicator" parent="Widget.MaterialComponents.LinearProgressIndicator">
<item name="trackThickness">20dp</item>
</style>
</resources>

View file

@ -56,6 +56,7 @@ distributions {
exclude('**/softaccess.brf') exclude('**/softaccess.brf')
from ('../misc') { from ('../misc') {
include 'readmes/*' include 'readmes/*'
include 'readmes/osmand/*'
include 'profiles2/*' include 'profiles2/*'
} }
from ('../brouter-routing-app/build/outputs/apk/api19/release') { from ('../brouter-routing-app/build/outputs/apk/api19/release') {

View file

@ -11,6 +11,8 @@ import java.util.HashMap;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.TreeSet; import java.util.TreeSet;
import btools.router.SuspectInfo;
public class SuspectManager extends Thread public class SuspectManager extends Thread
{ {
private static SimpleDateFormat dfTimestampZ = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" ); private static SimpleDateFormat dfTimestampZ = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss" );
@ -364,7 +366,7 @@ public class SuspectManager extends Thread
if ( "falsepositive".equals( command ) ) if ( "falsepositive".equals( command ) )
{ {
int wps = NearRecentWps.count( id ); int wps = NearRecentWps.count( id );
if ( wps < 8 ) if ( wps < 0 ) // FIXME
{ {
message = "marking false-positive requires at least 8 recent nearby waypoints from BRouter-Web, found: " + wps; message = "marking false-positive requires at least 8 recent nearby waypoints from BRouter-Web, found: " + wps;
} }
@ -428,6 +430,12 @@ public class SuspectManager extends Thread
br.close(); br.close();
} }
// get triggers
int triggers = suspects.trigger4Id( id );
String triggerText = SuspectInfo.getTriggerText( triggers );
String url1 = "http://brouter.de/brouter-web/#map=18/" + dlat + "/" + dlon String url1 = "http://brouter.de/brouter-web/#map=18/" + dlat + "/" + dlon
+ "/OpenStreetMap&lonlats=" + dlon + "," + dlat + "&profile=" + profile; + "/OpenStreetMap&lonlats=" + dlon + "," + dlat + "&profile=" + profile;
@ -456,6 +464,7 @@ public class SuspectManager extends Thread
{ {
bw.write( "<strong>" + message + "</strong><br><br>\n" ); bw.write( "<strong>" + message + "</strong><br><br>\n" );
} }
bw.write( "Trigger: " + triggerText + "<br><br>\n" );
bw.write( "<a href=\"" + url1 + "\">Open in BRouter-Web</a><br><br>\n" ); bw.write( "<a href=\"" + url1 + "\">Open in BRouter-Web</a><br><br>\n" );
bw.write( "<a href=\"" + url2 + "\">Open in OpenStreetmap</a><br><br>\n" ); bw.write( "<a href=\"" + url2 + "\">Open in OpenStreetmap</a><br><br>\n" );
bw.write( "<a href=\"" + url3 + "\">Open in JOSM (via remote control)</a><br><br>\n" ); bw.write( "<a href=\"" + url3 + "\">Open in JOSM (via remote control)</a><br><br>\n" );
@ -585,6 +594,7 @@ public class SuspectManager extends Thread
int cnt; int cnt;
long[] ids; long[] ids;
int[] prios; int[] prios;
int[] triggers;
boolean[] newOrConfirmed; boolean[] newOrConfirmed;
boolean[] falsePositive; boolean[] falsePositive;
long timestamp; long timestamp;
@ -594,10 +604,23 @@ public class SuspectManager extends Thread
cnt = count; cnt = count;
ids = new long[cnt]; ids = new long[cnt];
prios = new int[cnt]; prios = new int[cnt];
triggers = new int[cnt];
newOrConfirmed = new boolean[cnt]; newOrConfirmed = new boolean[cnt];
falsePositive = new boolean[cnt]; falsePositive = new boolean[cnt];
timestamp = time; timestamp = time;
} }
int trigger4Id( long id )
{
for( int i = 0; i<cnt; i++ )
{
if ( id == ids[i] )
{
return triggers[i];
}
}
return 0;
}
} }
private static HashMap<String,SuspectList> allSuspectsMap = new HashMap<String,SuspectList>(); private static HashMap<String,SuspectList> allSuspectsMap = new HashMap<String,SuspectList>();
@ -654,6 +677,7 @@ public class SuspectManager extends Thread
pointer = prioCount[nprio]++; pointer = prioCount[nprio]++;
allSuspects.ids[pointer] = id; allSuspects.ids[pointer] = id;
allSuspects.prios[pointer] = prio; allSuspects.prios[pointer] = prio;
allSuspects.triggers[pointer] = tk2.hasMoreTokens() ? Integer.parseInt( tk2.nextToken() ) : 0;
allSuspects.newOrConfirmed[pointer] = new File( "confirmednegatives/" + id ).exists() || !(new File( "suspectarchive/" + id ).exists() ); allSuspects.newOrConfirmed[pointer] = new File( "confirmednegatives/" + id ).exists() || !(new File( "suspectarchive/" + id ).exists() );
allSuspects.falsePositive[pointer] = new File( "falsepositives/" + id ).exists(); allSuspects.falsePositive[pointer] = new File( "falsepositives/" + id ).exists();
} }

View file

@ -1,8 +1,9 @@
package btools.util; package btools.util;
public interface ProgressListener public interface ProgressListener
{ {
public void updateProgress( String progress ); public void updateProgress(String task, int progress);
public boolean isCanceled(); public boolean isCanceled();
} }

View file

@ -21,7 +21,7 @@ allprojects {
// this file // this file
// app: build.gradle (versionCode only) // app: build.gradle (versionCode only)
// OsmTrack (version and versionDate) // OsmTrack (version and versionDate)
project.version "1.6.2" project.version "1.6.3"
group 'org.btools' group 'org.btools'
repositories { repositories {

View file

@ -140,6 +140,11 @@ public class OsmParser extends MapCreatorBase
rListener.nextRelation( r ); rListener.nextRelation( r );
if ( fromWid == null || toWid == null || viaNid == null || viaNid.size() != 1 ) if ( fromWid == null || toWid == null || viaNid == null || viaNid.size() != 1 )
{ {
// dummy-TR for each viaNid
for( int vi = 0; vi < ( viaNid == null ? 0 : viaNid.size() ); vi++ )
{
rListener.nextRestriction( r, 0L, 0L, viaNid.get( vi ) );
}
return; return;
} }
for( int fi = 0; fi < fromWid.size(); fi++ ) for( int fi = 0; fi < fromWid.size(); fi++ )
@ -152,7 +157,7 @@ public class OsmParser extends MapCreatorBase
} }
catch( Exception e ) catch( Exception e )
{ {
throw new RuntimeException( "error writing relation: " + e ); throw new RuntimeException( "error writing relation", e );
} }
} }

View file

@ -1,182 +0,0 @@
#
# Car-Routing based on a kinematic model
#
# Depending on the vmax-parameter (target-speed)
# this can be anything from least-time routing to eco-routing
#
#
---model:btools.router.KinematicModel
---context:global
# kinematic parameters
assign vmax = 90 # kmh
assign recup_efficiency = 0.7 # (ratio)
assign totalweight = 1640 # kg
assign f_roll = 232 # Newton
assign f_air = 0.4 # 0.5*cw*A*rho
assign f_recup = 400 # Newton
assign p_standby = 250 # Watt
# technical parameters
assign forceSecondaryData = true
assign validForCars = true
assign pass1coefficient = 1.3
assign turnInstructionMode = 1 # 0=none, 1=auto-choose, 2=locus-style, 3=osmand-style
---context:way # following code refers to way-tags
assign initialcost switch route=ferry 20000 0
#
# calculate logical car access
#
assign caraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
switch access=
switch highway=motorway|motorway_link 1
switch highway=trunk|trunk_link 1
switch highway=primary|primary_link 1
switch highway=secondary|secondary_link 1
switch highway=tertiary|tertiary_link 1
switch highway=unclassified 1
switch route=ferry 1
switch highway=residential|living_street 1
switch highway=service 1
0
access=yes|permissive|designated|destination
vehicle=yes|designated|destination
motor_vehicle=yes|permissive|designated|destination
motorcar=yes|permissive|designated|destination
assign accessspeedlimit = if caraccess then 999 else 0
assign isbadoneway = if reversedirection=yes then ( if oneway= then junction=roundabout else oneway=yes|true|1 ) else oneway=-1
assign onewayspeedlimit = if isbadoneway then 0 else 999
assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link
assign maxspeed_surface =
switch or surface= surface=paved|asphalt|concrete 999
switch surface=paving_stones|cobblestone 30
2
assign maxspeed_tracktype =
switch tracktype= 999
switch tracktype=grade1 40
switch tracktype=grade2 5
1
assign maxspeed_implicit =
switch highway=motorway 999
switch highway=motorway_link 130
switch highway=trunk 250
switch highway=trunk_link 100
switch highway=primary|primary_link 100
switch highway=secondary|secondary_link 90
switch highway=tertiary|tertiary_link 80
switch highway=unclassified 50
switch route=ferry 10
switch highway=bridleway 10
switch highway=residential|living_street 30
switch highway=service 30
switch highway=track|road|path switch tracktype=grade1 30 5
0
assign maxspeed =
min onewayspeedlimit
min accessspeedlimit
switch maxspeed=50 50
switch maxspeed=30 30
switch maxspeed=10 10
switch maxspeed=20 20
switch maxspeed=40 40
switch maxspeed=60 60
switch maxspeed=70 70
switch maxspeed=80 80
switch maxspeed=90 90
switch maxspeed=100 100
switch maxspeed=110 110
switch maxspeed=120 120
switch maxspeed=130 130
switch maxspeed=urban 50
switch maxspeed=rural 100
min maxspeed_implicit
min maxspeed_surface maxspeed_tracktype
assign costfactor = if equal maxspeed 0 then 10000 else 0
assign minspeed =
switch highway=motorway|trunk 75 0
# way priorities used for voice hint generation
assign priorityclassifier =
if ( highway=motorway ) then 30
else if ( highway=motorway_link ) then 29
else if ( highway=trunk ) then 28
else if ( highway=trunk_link ) then 27
else if ( highway=primary ) then 26
else if ( highway=primary_link ) then 25
else if ( highway=secondary ) then 24
else if ( highway=secondary_link ) then 23
else if ( highway=tertiary ) then 22
else if ( highway=tertiary_link ) then 21
else if ( highway=unclassified ) then 20
else if ( highway=residential|living_street ) then 6
else if ( highway=service ) then 6
else if ( highway=track ) then if tracktype=grade1 then 4 else 2
else if ( highway=bridleway|road ) then 2
else 0
# some more classifying bits used for voice hint generation...
assign isgoodoneway = if reversedirection=yes then oneway=-1
else if oneway= then junction=roundabout else oneway=yes|true|1
assign isroundabout = junction=roundabout
assign isgoodforcars = if greater priorityclassifier 6 then true
else if highway=residential|living_street|service then true
else if ( and highway=track tracktype=grade1 ) then true
else false
# ... encoded into a bitmask
assign classifiermask add isbadoneway
add multiply isgoodoneway 2
add multiply isroundabout 4
add multiply islinktype 8
add multiply isgoodforcars 16
multiply highway=residential|living_street 32
---context:node # following code refers to node tags
#
# calculate logical car access to nodes
#
assign caraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
switch access=
not barrier=gate|bollard|lift_gate|cycle_barrier
access=yes|permissive|designated|destination
vehicle=yes|permissive|designated|destination
motor_vehicle=yes|permissive|designated|destination
motorcar=yes|permissive|designated|destination
assign initialcost =
switch caraccess
0
1000000
assign maxspeed =
if or crossing=traffic_signals highway=traffic_signals then 0
else 999

View file

@ -1,214 +0,0 @@
#
# Car-Routing based on a kinematic model
#
# Depending on the vmax-parameter (target-speed)
# this can be anything from least-time routing to eco-routing
#
#
---model:btools.router.KinematicModel
---context:global
# kinematic parameters
assign vmax = 90 # kmh
assign recup_efficiency = 0.7 # (ratio)
assign totalweight = 1640 # kg
assign f_roll = 232 # Newton
assign f_air = 0.4 # 0.5*cw*A*rho
assign f_recup = 400 # Newton
assign p_standby = 250 # Watt
# technical parameters
assign validForCars = true
assign pass1coefficient = 1.3
assign turnInstructionMode = 1 # 0=none, 1=auto-choose, 2=locus-style, 3=osmand-style
---context:way # following code refers to way-tags
assign initialcost switch route=ferry 20000 0
#
# calculate logical car access
#
assign isresidentialorliving = or highway=residential|living_street living_street=yes
assign caraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
switch access=
switch highway=motorway|motorway_link 1
switch highway=trunk|trunk_link 1
switch highway=primary|primary_link 1
switch highway=secondary|secondary_link 1
switch highway=tertiary|tertiary_link 1
switch highway=unclassified 1
switch route=ferry 1
switch isresidentialorliving 1
switch highway=service 1
0
access=yes|permissive|designated|destination
vehicle=yes|designated|destination
motor_vehicle=yes|permissive|designated|destination
motorcar=yes|permissive|designated|destination
assign unknowncaraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
access=unknown
vehicle=unknown
motor_vehicle=unknown
motorcar=unknown
assign psvaccess
or bus=yes|designated|urban
or psv=yes|designated
or hov=yes|designated
switch motorcar=
switch motor_vehicle=
switch vehicle=
access=psv|hov
vehicle=psv|hov
motor_vehicle=psv|hov
motorcar=psv|hov
assign accessspeedlimit = if caraccess then 999 else 0
assign isroundabout = junction=roundabout|circular
assign implicitoneway = or isroundabout highway=motorway
assign isbadoneway = if reversedirection=yes then ( if oneway= then implicitoneway else oneway=yes|true|1 ) else oneway=-1
assign onewayspeedlimit = if isbadoneway then 0 else 999
assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link
assign maxspeed_surface =
switch or surface= surface=paved|asphalt|concrete 999
switch surface=paving_stones|cobblestone 30
2
assign maxspeed_tracktype =
switch tracktype= 999
switch tracktype=grade1 40
switch tracktype=grade2 5
1
assign maxspeed_implicit =
switch highway=motorway 999
switch highway=motorway_link 130
switch highway=trunk 250
switch highway=trunk_link 100
switch highway=primary|primary_link 100
switch highway=secondary|secondary_link 90
switch highway=tertiary|tertiary_link 80
switch highway=unclassified 50
switch route=ferry 10
switch highway=bridleway 10
switch isresidentialorliving 30
switch highway=service 30
switch highway=track|road|path switch tracktype=grade1 30 5
0
assign maxspeed =
min onewayspeedlimit
min accessspeedlimit
switch maxspeed=50 50
switch maxspeed=30 30
switch maxspeed=10 10
switch maxspeed=20 20
switch maxspeed=40 40
switch maxspeed=60 60
switch maxspeed=70 70
switch maxspeed=80 80
switch maxspeed=90 90
switch maxspeed=100 100
switch maxspeed=110 110
switch maxspeed=120 120
switch maxspeed=130 130
switch maxspeed=urban 50
switch maxspeed=rural 100
min maxspeed_implicit
min maxspeed_surface maxspeed_tracktype
assign minspeed =
switch highway=motorway|trunk 75 0
# way priorities used for voice hint generation
assign priorityclassifier =
if ( highway=motorway ) then 30
else if ( highway=motorway_link ) then 29
else if ( highway=trunk ) then 28
else if ( highway=trunk_link ) then 27
else if ( highway=primary ) then 26
else if ( highway=primary_link ) then 25
else if ( highway=secondary ) then 24
else if ( highway=secondary_link ) then 23
else if ( highway=tertiary ) then 22
else if ( highway=tertiary_link ) then 21
else if ( highway=unclassified ) then 20
else if ( isresidentialorliving ) then 6
else if ( highway=service ) then 6
else if ( highway=track ) then if tracktype=grade1 then 4 else 2
else if ( highway=bridleway|road ) then 2
else 0
assign costfactor = if equal maxspeed 0
then ( if ( and equal accessspeedlimit 0 and greater onewayspeedlimit 0 and greater priorityclassifier 20 not psvaccess )
then switch unknowncaraccess 9997 9998
else 10000 )
else 0
# some more classifying bits used for voice hint generation...
assign isgoodoneway = if reversedirection=yes then oneway=-1
else if oneway= then implicitoneway else oneway=yes|true|1
assign isgoodforcars = if greater priorityclassifier 6 then true
else if or isresidentialorliving highway=service then true
else if ( and highway=track tracktype=grade1 ) then true
else false
# ... encoded into a bitmask
assign classifiermask add isbadoneway
add multiply isgoodoneway 2
add multiply isroundabout 4
add multiply islinktype 8
add multiply isgoodforcars 16
multiply isresidentialorliving 32
---context:node # following code refers to node tags
#
# calculate logical car access to nodes
#
assign caraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
switch access=
not barrier=gate|bollard|lift_gate|cycle_barrier
access=yes|permissive|designated|destination
vehicle=yes|permissive|designated|destination
motor_vehicle=yes|permissive|designated|destination
motorcar=yes|permissive|designated|destination
assign initialcost =
switch caraccess
0
1000000
assign maxspeed =
if or crossing=traffic_signals highway=traffic_signals
then
switch greater way:priorityclassifier 24 5
switch greater way:priorityclassifier 22 3
switch greater way:priorityclassifier 20 1
0
else 999

View file

@ -1,128 +0,0 @@
#
# Car-Routing is experimantal !!!
#
# DO NOT USE FOR ACTUAL NAVIGATION
#
# Turn restrictions are missing, leading to wrong routes
#
---context:global
assign downhillcost 0
assign downhillcutoff 0
assign uphillcost 0
assign uphillcutoff 0
assign validForCars 1
assign considerTurnRestrictions = false
assign pass1coefficient 0.
assign pass2coefficient -1
---context:way # following code refers to way-tags
assign turncost = 0
assign initialcost switch route=ferry 20000 0
#
# calculate logical car access
#
assign isresidentialorliving = or highway=residential|living_street living_street=yes
assign caraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
switch access=
switch or highway=motorway highway=motorway_link 1
switch or highway=trunk highway=trunk_link 1
switch or highway=primary highway=primary_link 1
switch or highway=secondary highway=secondary_link 1
switch or highway=tertiary highway=tertiary_link 1
switch highway=unclassified 1
switch route=ferry 1
switch isresidentialorliving 1
switch highway=service 1
0
or access=yes or access=permissive or access=designated access=destination
or vehicle=yes or vehicle=designated vehicle=destination
or motor_vehicle=yes or motor_vehicle=permissive or motor_vehicle=designated motor_vehicle=destination
or motorcar=yes or motorcar=permissive or motorcar=designated motorcar=destination
assign accesspenalty
switch caraccess
0
10000
assign onewaypenalty
switch switch reversedirection=yes
switch oneway=
junction=roundabout
or oneway=yes or oneway=true oneway=1
oneway=-1
10000
0.0
assign ispaved or surface=paved or surface=asphalt or surface=concrete surface=paving_stones
assign costfactor
add max onewaypenalty accesspenalty
switch and highway= not route=ferry 10000
switch or highway=motorway highway=motorway_link 1
switch or highway=trunk highway=trunk_link 1
switch or highway=primary highway=primary_link 1.2
switch or highway=secondary highway=secondary_link 1.6
switch or highway=tertiary highway=tertiary_link 2.0
switch highway=unclassified 2.5
switch route=ferry 5.67
switch highway=bridleway 5
switch isresidentialorliving 3.5
switch highway=service 3.5
switch or highway=track or highway=road highway=path
switch tracktype=grade1 5
switch ispaved 5
30
10000
assign trafficsourcedensity =
if isresidentialorliving then 1
else if and maxspeed=30|50 highway=tertiary|secondary|primary then 1
else 0
assign istrafficbackbone
if greater costfactor 9999 then false
else if highway=motorway|motorway_link then true
else false
---context:node # following code refers to node tags
#
# calculate logical car access to nodes
#
assign caraccess
switch motorcar=
switch motor_vehicle=
switch vehicle=
switch access=
switch barrier=gate 0
switch barrier=bollard 0
switch barrier=lift_gate 0
switch barrier=cycle_barrier 0
1
or access=yes or access=permissive or access=designated access=destination
or vehicle=yes or vehicle=permissive or vehicle=designated vehicle=destination
or motor_vehicle=yes or motor_vehicle=permissive or motor_vehicle=designated motor_vehicle=destination
or motorcar=yes or motorcar=permissive or motorcar=designated motorcar=destination
assign initialcost
switch caraccess
0
1000000

View file

@ -2,6 +2,7 @@
# this prevents suppression of unused tags, so they are visibly in the data tab # this prevents suppression of unused tags, so they are visibly in the data tab
assign processUnusedTags = true assign processUnusedTags = true
assign validForFoot = true
---context:way # following code refers to way-tags ---context:way # following code refers to way-tags

View file

@ -1,305 +0,0 @@
# Profile for medium-level hiking
#
# See https://github.com/poutnikl/Brouter-profiles for
# more variants and poutnik's profile collection
# *** Based on Trekking profile, added flag driven customizations
# 10/6/2015 v1.7.2 BETA - Fixed general way access penalties
# See bottom for Changelog and verbose comments.
---context:global
assign consider_elevation 1 # 0 as default, otherwise less interesting flat roads are chosen.
assign shortest_way 0 # 0 as default
assign iswet 0 # 0 as default, 1 tries to prevent muddy boots and wet buttocks
assign prefer_hiking_routes 1 # 1 as default, gives penalty to way not being hiking route and cancels route turncost
assign stick_to_hiking_routes 0 # 0 as default, as above, but gives higher penalty costfactor + way/node initial costs
assign non_hiking_route_penalty 0.15 # 0.25 as default, used with prefer_hiking_routes
assign non_sticky_route_penalty 0.5 # 1.0 as default, used stick_to_hiking_routes #v1.5
#SAC - mountain hiking - see http://wiki.openstreetmap.org/wiki/Key:sac_scale
assign SAC_scale_limit 3 # 0..6, 0 to avoid any SAC paths, 1 for T1 as maximum, 6 for T6 as maximum
# all paths with sac_scale higher than SAC_scale_limit are forbidden.
assign SAC_scale_preferred 1 # The same, but the preferred SAC scale level. Level below are slightly, above strongly penalized
assign SAC_access_penalty 9000 # costfactor 9999 means the most horrible but allowed road.,
# 100000=forbidden. This makes difference if forbidden way is the only option.
#orientation/decision penalties, not used for preferred hiking routes
assign turncost_value 0 # 20 as default, not used now
assign initialcost_value 0 # 50 as default, not used now
#less frequently changed flags
assign allow_steps 1 # 1 as default
assign allow_ferries 1 # 1 as default
assign cost_of_unknown 2 # 2 as default
#
assign elevationpenaltybuffer 5 # 5 as default
assign elevationmaxbuffer 10 # 10 as default
assign elevationbufferreduce 0.0 # 0.0 as default
# as initial point, considered flat speed 4 km/h, climbing speed 600 m /h
assign uphillcostvalue 7
assign uphillcutoffvalue 3
assign downhillcutoffvalue 3
assign downhillcostvalue 7
assign downhillcost switch consider_elevation downhillcostvalue 0
assign downhillcutoff switch consider_elevation downhillcutoffvalue 0
assign uphillcost switch consider_elevation uphillcostvalue 0
assign uphillcutoff switch consider_elevation uphillcutoffvalue 0
assign validForFoot 1
assign hr_preferred or prefer_hiking_routes stick_to_hiking_routes
#Penalizing SAC routes below (K1) and above(K2) preferred SAC scale
#see http://wiki.openstreetmap.org/wiki/Key:sac_scale
assign SAC_K1 0.1 # Penalizing of SAC levels below preferred
assign SAC_K2 0.6 # Penalizing of SAC levels above preferred
#all the extra complexity of code below, with adding +/- 1.0
#is to keep final penalties additive, even with multiplicative incremental penalty approach
#code is run only once, being in global context
assign SAC_K10 add SAC_K1 1.0
assign SAC_K20 add SAC_K2 1.0
assign SAC_K1_2 add multiply SAC_K10 SAC_K10 -1.0
assign SAC_K2_2 add multiply SAC_K20 SAC_K20 -1.0
assign SAC_K1_3 add ( multiply ( add SAC_K1_2 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_3 add ( multiply ( add SAC_K2_2 1.0 ) SAC_K20 ) -1.0
assign SAC_K1_4 add ( multiply ( add SAC_K1_3 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_4 add ( multiply ( add SAC_K2_3 1.0 ) SAC_K20 ) -1.0
assign SAC_K1_5 add ( multiply ( add SAC_K1_4 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_5 add ( multiply ( add SAC_K2_4 1.0 ) SAC_K20 ) -1.0
assign SAC_K1_6 add ( multiply ( add SAC_K1_5 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_6 add ( multiply ( add SAC_K2_5 1.0 ) SAC_K20 ) -1.0
---context:way # following code refers to way-tags
assign ispaved or surface=paved or surface=asphalt or surface=concrete surface=paving_stones
assign isunpaved not or surface= or ispaved or surface=fine_gravel surface=cobblestone
assign any_hiking_route or route=hiking or route_hiking_iwn=yes
or route_hiking_nwn=yes or route_hiking_rwn=yes
or route_hiking_lwn=yes or route_hiking_=yes
or route_foot_=yes or route_foot_nwn=yes
or route_foot_rwn=yes route_foot_lwn=yes
assign any_cycleroute =
if route_bicycle_icn=yes then true
else if route_bicycle_ncn=yes then true
else if route_bicycle_rcn=yes then true
else if route_bicycle_lcn=yes then true
else false
assign is_ldhr and any_hiking_route hr_preferred
assign nodeaccessgranted any_hiking_route
# ismuddy addresses potentially bad surface conditions during wet weather ( mud, slickiness of grass)
assign ismuddy and or isunpaved surface=
and iswet
not or surface=gravel surface=pebblestone
assign issidewalk not or sidewalk= or sidewalk=none sidewalk=no
assign turncost switch or shortest_way is_ldhr 0 turncost_value #v1.5
assign initialclassifier =
if route=ferry then 1
else 0
assign initialcost
switch route=ferry 10000
switch or shortest_way is_ldhr 0 switch stick_to_hiking_routes initialcost_value 0
assign defaultaccess switch access= not motorroad=yes switch or access=private access=no 0 1
assign footaccess or any_hiking_route
or issidewalk
switch foot= defaultaccess switch foot=private|no 0 1
assign bikeaccess
or any_cycleroute
switch bicycle=
switch vehicle=
defaultaccess
switch or vehicle=private vehicle=no
0
1
not or bicycle=private or bicycle=no bicycle=dismount
assign footaccess
or and bikeaccess not foot=no
or bicycle=dismount
switch foot=
defaultaccess
not or foot=private foot=no
assign accesspenalty switch footaccess 0 switch bikeaccess 4 100000
#
#SAC is estimated path difficulty,
#integrating both MTB and SAC scales with estimated MTB/SAC difficulty matching
#see http://wiki.openstreetmap.org/wiki/Key:mtb:scale
# http://wiki.openstreetmap.org/wiki/Key:sac_scale
assign SAC
if sac_scale=difficult_alpine_hiking then 6
else if or sac_scale=demanding_alpine_hiking mtb:scale=6 then 5
else if or sac_scale=alpine_hiking mtb:scale=5 then 4
else if or sac_scale=demanding_mountain_hiking mtb:scale=4 then 3
else if or sac_scale=mountain_hiking mtb:scale=3|2+ then 2
else if or sac_scale=hiking|T1-hiking|yes mtb:scale=2|1+|2- then 1
else 0
assign SAC_scale_access # if SAC_scale_limit < SAC then true else false
if sac_scale= then true else equal ( max SAC_scale_limit SAC ) SAC_scale_limit
assign SAC_scale_penalty
if not SAC_scale_access then SAC_access_penalty # not allowed SAC scale
else if equal SAC SAC_scale_preferred then 0.0
else if equal ( add SAC 1 ) SAC_scale_preferred then SAC_K1
else if equal ( add SAC 2 ) SAC_scale_preferred then SAC_K1_2
else if equal ( add SAC 3 ) SAC_scale_preferred then SAC_K1_3
else if equal ( add SAC 4 ) SAC_scale_preferred then SAC_K1_4
else if equal ( add SAC 5 ) SAC_scale_preferred then SAC_K1_5
else if equal ( add SAC 6 ) SAC_scale_preferred then SAC_K1_6
else if equal ( add SAC_scale_preferred 1 ) SAC then SAC_K2
else if equal ( add SAC_scale_preferred 2 ) SAC then SAC_K2_2
else if equal ( add SAC_scale_preferred 3 ) SAC then SAC_K2_3
else if equal ( add SAC_scale_preferred 4 ) SAC then SAC_K2_4
else if equal ( add SAC_scale_preferred 5 ) SAC then SAC_K2_5
else if equal ( add SAC_scale_preferred 6 ) SAC then SAC_K2_6
else 1.0
assign costfactor
if shortest_way then ( add 1 accesspenalty ) else
add ( switch is_ldhr 0.0 # hiking route, no non-hiking road penalty
switch stick_to_hiking_routes non_sticky_route_penalty
switch prefer_hiking_routes non_hiking_route_penalty
0.0 # no hiking route preference
)
add accesspenalty
add SAC_scale_penalty
switch ( and highway= not route=ferry ) 100000
switch highway=steps ( switch allow_steps ( switch consider_elevation 1.0 3.0 ) 100000 )
switch route=ferry switch allow_ferries 2.34 100000
# iswet=1 in global context section means wet weather, increases penalty for eventually inconvenient ways
# ismuddy boolean relates in wet weather to unpaved or unclassified surface, that can have mud or get slicky in wet weather.
switch highway=pedestrian switch ismuddy 1.3 1.0
switch highway=bridleway switch ismuddy 2.5 1.2
switch highway=cycleway switch ismuddy 1.4
switch iswet 1.0 1.2
switch highway=residential|living_street
switch ismuddy 1.5
switch iswet 1.0 1.1
switch highway=service switch ismuddy 1.5
switch iswet 1.1 1.3
switch highway=track|road|path|footway
switch tracktype=grade1 switch ismuddy 1.1
switch iswet 1.0 1.21
switch tracktype=grade2 switch ismuddy 1.25
switch iswet 1.11 1.12
switch tracktype=grade3 switch ismuddy 1.4
switch iswet 1.21 1.07
switch tracktype=grade4 switch ismuddy 1.7
switch iswet 1.31 1.05
switch tracktype=grade5 switch ismuddy 2.0
switch iswet 1.5 1.01
switch ismuddy 2.01
switch iswet 1.51 1.12
switch highway=motorway|motorway_link 100000
switch highway=proposed|abandoned switch ismuddy 3 1.5
switch highway=construction switch ismuddy 10 2.5
switch highway=trunk|trunk_link|primary|primary_link switch iswet switch issidewalk 2.5 5
switch issidewalk 5 10
switch highway=secondary|secondary_link switch iswet switch issidewalk 2.0 2.5
switch issidewalk 2.5 4.0
switch highway=tertiary|tertiary_link|unclassified switch ismuddy 2.0 switch iswet switch issidewalk 1.4 1.7
switch issidewalk 1.7 2.0
add cost_of_unknown ( switch ismuddy 0.5 0.0 )
# include `smoothness=` tags in the response's WayTags for track analysis
assign dummyUsage = smoothness=
---context:node # following code refers to node tags
assign defaultaccess
switch access=
1 # add default barrier restrictions here!
switch or access=private access=no
0
1
assign bikeaccess
or nodeaccessgranted=yes
switch bicycle=
switch vehicle=
defaultaccess
switch or vehicle=private vehicle=no
0
1
switch or bicycle=private or bicycle=no bicycle=dismount
0
1
assign footaccess
or bicycle=dismount
switch foot=
defaultaccess
switch or foot=private foot=no
0
1
assign initialcost switch or bikeaccess footaccess 0 1000000
# changelog:
# *** Based on Trekking profile, added flag driven customizations
# Hiking Version 1.6 ALFA
# 2014-10-10 (c) Poutnik
# Route_foot related hints were improved with usage of Hiking.brf from below and by Arndt comment
# http://pastebin.com/YLpkGVD4
# http://forum.openstreetmap.org/viewtopic.php?pid=447507#p447507
#
# 2014-10-10 v1.1 - changed hiking route preference logic
# 1.2 - fixed preference counting bug
# 2014-10-11 1.3 - adding foot route to hiking routes,
# fixed ferry cost to respect initial cost triggerring.
# added bikeaccess, added shortest way mode, code cleanup
# 2014-10-12: v1.4 - involving turncosts and way/node initial costs as orientation/decision penalties,
# but turning them off for sticking to hiking routes as extra preference,
# tweaking cost preferences
# 2014-10-13 v1.5
# redesigned route preference policy - turncost turned off even for nonsticky but preferred hiking routes
# cost tweaking.
# removed uniform cost=1 for sticky hiking routes, to distinguish quality
# giving penalty 1 for non hiking routes at sticky hiking routes.
# used not round costs to often trigger initial cost calculation
#v1.51 - bugfix of redundant routing penalty
#v 1.6 - initialcost penalty only for sticking routes, decreased way penalties for preferring routes
# 31/5/2015 v 1.7 ALFA - sac_scale + sac_scale_limit implemented
# 10/6/2015 v1.7.1 ALFA - sac_scale improved, MTB scale integrated to SAC scale
# sac_scale_preferred implemented, with progressive penalizing for SAC <> SAC preferred
# 10/6/2015 v1.7.2 BETA - Fixed general way access penalties

View file

@ -0,0 +1,485 @@
# Walking-Hiking-Mountain/Alpine Hiking profile TEMPLATE
# 18/5/2016 v1.8.7 ! Fixed down/uphillcostfactors for shortest_way - to be really shortest
#
# SAC T3 - demanding_mountain_hiking - exposed sites may be secured, possible need of hands for balance, Partly exposed with fall hazard, Well sure-footed, Good hiking shoes, Basic alpine experience
#
# Legend above is placeholder for generated comments of final profile
# See the profile bottom for changelogs and verbose *) comments
# See also https://github.com/poutnikl/Brouter-profiles/wiki
# and https://github.com/poutnikl/Hiking-Poutnik
---context:global
assign consider_elevation 1 # 0 as default, otherwise less interesting flat roads are chosen.
assign shortest_way 0 # 0 as default, duplicate shortest standard profile, SAC access limit ignored for now
assign turnInstructionMode = 1 # 0=none, 1=auto-choose, 2=locus-style, 3=osmand-style
assign turnInstructionCatchingRange 20 # V1.8.5 / default=40, but foot paths may be more distingushed, especially in cities.
assign iswet 0 # 0 as default, 1 tries to prevent muddy boots and wet buttocks
assign hiking_routes_preference 0.20 # 0.10 as default, Increases cost of non hiking routes by multiplier 1 + hiking_routes_preference
assign Offroad_factor 0.0 # default 0.0, see ****), see also https://github.com/poutnikl/Brouter-profiles/wiki/Trekking-MTB-Profiles---legend
assign path_preference 0.0 # 0.0 as default, try 20.0 to penalize nonpath ways a/o paved ways
#SAC - mountain hiking - see http://wiki.openstreetmap.org/wiki/Key:sac_scale
assign SAC_scale_limit 3 # 0..6, 0 to avoid any SAC paths, 1 for T1 as maximum, 6 for T6 as maximum
# all paths with sac_scale higher than SAC_scale_limit are forbidden.
assign SAC_scale_preferred 1 # The same, but the preferred SAC scale level. Level below are slightly, above strongly penalized
assign SAC_access_penalty 9000 # costfactor 9999 means the most horrible but allowed road.,
# 100000=forbidden. This makes difference if forbidden way is the only option.
assign SAC_K1 0.05 # Penalizing of SAC levels below preferred
assign SAC_K2 0.6 # Penalizing of SAC levels above preferred
#orientation/decision penalties, not used for preferred hiking routes
assign turncost_value 0 # not used now
assign initialcost_value 0 # not used now
#less frequently changed flags
assign allow_steps 1 # 1 as default
assign allow_ferries 1 # 1 as default
assign cost_of_unknown 2 # 2 as default
#
assign elevationpenaltybuffer 5 # 5 as default
assign elevationmaxbuffer 10 # 10 as default
assign elevationbufferreduce 1.0 # 0.0 as default
# as initial point, considered flat speed 4 km/h, climbing speed 600 m /h
assign uphillcostvalue 7
assign uphillcutoffvalue 3
assign downhillcutoffvalue 3
assign downhillcostvalue 7
#internal parameters
assign Offroad_hillcostfactor multiply -0.3333 ( max -3.0 ( multiply -1.0 ( max 0.0 Offroad_factor ) ) )
# for Offroadfactor <=0 is 0, for Offroadfactor >=3 is 1, otherwise 0.3333 * Offroadfactor
# progressively decreases hillcosts to be 0.0 at Offroad_factor = 3.0
# if Offroad_factor = 1 , then downhillcost decreases e.g. from 60 to 40
assign downhillcost if consider_elevation then
( multiply ( add 1.0 ( multiply Offroad_hillcostfactor -1.0 ) ) downhillcostvalue ) else 0
assign uphillcost if consider_elevation then
( multiply ( add 1.0 ( multiply Offroad_hillcostfactor -1.0 ) ) uphillcostvalue ) else 0
assign uphillcutoff if consider_elevation then uphillcutoffvalue else 1.5
assign downhillcutoff if consider_elevation then downhillcutoffvalue else 1.5
assign nonhiking_route_penalty add 1.0 max 0.0 hiking_routes_preference
assign validForFoot 1
#Penalizing SAC routes below (K1) and above(K2) preferred SAC scale
#see http://wiki.openstreetmap.org/wiki/Key:sac_scale
#SAC_scale_penalty:
#Penalty is SAC_access_penalty for SAC > SAC_scale_limit
#Penalty is 0.0 for SAC_scale_preferred = SAC, SAC <= SAC_scale_limit
#Penalty is ( 1 + SAC_K1)^(SAC_scale_preferred - SAC) -1 for SAC_scale_preferred > SAC, SAC <= SAC_scale_limit
#Penalty is ( 1 + SAC_K2)^(SAC - SAC_scale_preferred) -1 for SAC_scale_preferred < SAC, SAC <= SAC_scale_limit
#extra complexity of code below, with adding +/- 1.0
#is to keep final penalties additive, even with multiplicative incremental penalty approach
#code is run only once, being in global context
assign SAC_K10 add SAC_K1 1.0
assign SAC_K20 add SAC_K2 1.0
assign SAC_K1_2 add multiply SAC_K10 SAC_K10 -1.0
assign SAC_K2_2 add multiply SAC_K20 SAC_K20 -1.0
assign SAC_K1_3 add ( multiply ( add SAC_K1_2 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_3 add ( multiply ( add SAC_K2_2 1.0 ) SAC_K20 ) -1.0
assign SAC_K1_4 add ( multiply ( add SAC_K1_3 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_4 add ( multiply ( add SAC_K2_3 1.0 ) SAC_K20 ) -1.0
assign SAC_K1_5 add ( multiply ( add SAC_K1_4 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_5 add ( multiply ( add SAC_K2_4 1.0 ) SAC_K20 ) -1.0
assign SAC_K1_6 add ( multiply ( add SAC_K1_5 1.0 ) SAC_K10 ) -1.0
assign SAC_K2_6 add ( multiply ( add SAC_K2_5 1.0 ) SAC_K20 ) -1.0
---context:way # following code refers to way-tags
assign ispaved or surface=paved or surface=asphalt or surface=concrete surface=paving_stones
assign isunpaved not or surface= or ispaved or surface=fine_gravel surface=cobblestone
assign any_hiking_route or route=hiking or route_hiking_iwn=yes
or route_hiking_nwn=yes or route_hiking_rwn=yes
or route_hiking_lwn=yes or route_hiking_=yes
or route_foot_=yes or route_foot_nwn=yes
or route_foot_rwn=yes route_foot_lwn=yes
assign any_cycleroute =
if route_bicycle_icn=yes then true
else if route_bicycle_ncn=yes then true
else if route_bicycle_rcn=yes then true
else if route_bicycle_lcn=yes then true
else false
assign is_ldhr and any_hiking_route not equal 0.0 hiking_routes_preference
assign nodeaccessgranted any_hiking_route
# ismuddy addresses potentially bad surface conditions during wet weather ( mud, slickiness of grass)
assign ismuddy and or isunpaved surface=
and iswet
not or surface=gravel surface=pebblestone
assign issidewalk sidewalk=left|right|both|yes
assign istrack highway=track|road|path|footway
assign ismainroad highway=motorway|motorway_link|trunk|trunk_link|primary|primary_link|secondary|secondary_link|tertiary|tertiary_link|unclassified
#assign turncost switch or shortest_way is_ldhr 0 turncost_value #v1.5
assign turncost 0 #v1.8.3
assign initialcost
switch route=ferry 10000
switch or shortest_way is_ldhr 0 initialcost_value
assign defaultaccess switch access= not motorroad=yes switch or access=private access=no 0 1
assign bikeaccess
or any_cycleroute
switch bicycle=
switch vehicle=
defaultaccess
switch or vehicle=private vehicle=no
0
1
not or bicycle=private or bicycle=no bicycle=dismount
assign footaccess or any_hiking_route
or issidewalk
or and bikeaccess not foot=no
or bicycle=dismount
switch foot= defaultaccess not foot=private|no
assign accesspenalty switch footaccess 0 switch bikeaccess 4 100000
assign badoneway = 0
assign onewaypenalty = 0
#SAC is estimated path difficulty,
#integrating both MTB and SAC scales with estimated MTB/SAC difficulty matching
#see http://wiki.openstreetmap.org/wiki/Key:mtb:scale
# http://wiki.openstreetmap.org/wiki/Key:sac_scale
assign SAC
if sac_scale= then (
if mtb:scale= then 0
else if mtb:scale=6|5 then 5
else if mtb:scale=4 then 4
else if mtb:scale=3 then 3
else if mtb:scale=2-|2|2+ then 2
else if mtb:scale=1+|1 then 1
else 0
)
else
if sac_scale=difficult_alpine_hiking then 6
else if sac_scale=demanding_alpine_hiking then 5
else if sac_scale=alpine_hiking then 4
else if sac_scale=demanding_mountain_hiking then 3
else if sac_scale=mountain_hiking then 2
else if sac_scale=hiking|T1-hiking|yes then 1
else 0
assign SAC_scale_access # if SAC_scale_limit < SAC then true else false
if sac_scale= then true else equal ( max SAC_scale_limit SAC ) SAC_scale_limit
assign SAC_scale_penalty
if not SAC_scale_access then SAC_access_penalty # not allowed SAC scale
else if equal SAC SAC_scale_preferred then 0.0
else if equal ( add SAC 1 ) SAC_scale_preferred then SAC_K1
else if equal ( add SAC 2 ) SAC_scale_preferred then SAC_K1_2
else if equal ( add SAC 3 ) SAC_scale_preferred then SAC_K1_3
else if equal ( add SAC 4 ) SAC_scale_preferred then SAC_K1_4
else if equal ( add SAC 5 ) SAC_scale_preferred then SAC_K1_5
else if equal ( add SAC 6 ) SAC_scale_preferred then SAC_K1_6
else if equal ( add SAC_scale_preferred 1 ) SAC then SAC_K2
else if equal ( add SAC_scale_preferred 2 ) SAC then SAC_K2_2
else if equal ( add SAC_scale_preferred 3 ) SAC then SAC_K2_3
else if equal ( add SAC_scale_preferred 4 ) SAC then SAC_K2_4
else if equal ( add SAC_scale_preferred 5 ) SAC then SAC_K2_5
else if equal ( add SAC_scale_preferred 6 ) SAC then SAC_K2_6
else 1.0
assign tracktype_penalty (
if not istrack then 0.0 else if tracktype= then 0.0
else if tracktype=grade1 then 0.1 else if tracktype=grade2 then 0.05
else if tracktype=grade3 then 0.0 else if tracktype=grade4 then 0.0
else if tracktype=grade5 then 0.0 else 0.0 )
assign surface_penalty (
if not istrack then 0.0
else if ispaved then 0.0
else if surface=cobblestone then 0.0
else if surface=fine_gravel|ground|earth|unpaved|grass|compacted then 0.0
else if surface=dirt|sand then 0.1
else if surface= then 0.0
else if surface=gravel|pebblestone then 0.2
else 0.0 )
assign wet_penalty (
if not iswet then 0.0
else if ismainroad then -0.1
else if tracktype=grade1 then ( if ispaved then -0.2 else if ismuddy then 0.2 else 0.1 )
else if tracktype=grade2 then ( if ispaved then -0.1 else if ismuddy then 0.4 else 0.2 )
else if tracktype=grade3 then ( if ispaved then -0.0 else if ismuddy then 0.8 else 0.3 )
else if tracktype=grade4 then ( if ispaved then 0.1 else if ismuddy then 1.5 else 0.5 )
else if tracktype=grade5 then ( if ispaved then 0.2 else if ismuddy then 2.5 else 1.0 )
else ( if ispaved then -0.2 else if ismuddy then 2.5 else 1.0 )
)
assign Offroad_factor_for_road
if ( equal Offroad_factor 0.0 ) then 0.0 else
(
if ismainroad then Offroad_factor
else if ( or ispaved highway=residential|living_street|service|pedestrian ) then ( multiply 0.33 Offroad_factor )
else if ( not isunpaved ) then ( multiply -0.33 Offroad_factor )
else ( multiply -1 multiply Offroad_factor ( add 1.0 ( multiply 0.33 SAC_scale_penalty ) ) )
)
assign nonpath_penalty =
if ( equal path_preference 0.0 ) then 0.0 # nonpath_penalty inactive
else if not istrack then path_preference #istrack = highway=track/path/road/footway
else if ispaved then ( multiply path_preference 0.5 )
else if or ( and not isunpaved not highway=path )
( tracktype=grade1|grade2 ) then ( multiply path_preference 0.25 )
else if not ( and isunpaved
and highway=path
and tracktype=grade1|grade2
not surface=gravel|cobblestone|pebblestone )
then ( multiply path_preference 0.125 )
else 0.0
assign rawcostfactor # can be <1.0, is treated later
if shortest_way then ( add 1 accesspenalty ) else
add nonpath_penalty
add accesspenalty
(
if ( and highway= not route=ferry ) then 100000
else if highway=steps then ( switch allow_steps ( switch consider_elevation 1.0 3.0 ) 100000 )
else if route=ferry then ( if allow_ferries then 2.34 else 100000 )
# iswet=1 in global context section means wet weather, increases penalty for eventually inconvenient ways
# ismuddy boolean relates in wet weather to unpaved or unclassified surface, that can have mud or get slicky in wet weather.
else if highway=pedestrian then ( switch ismuddy 1.3 1.0 )
else if highway=bridleway then ( switch ismuddy 2.5 switch iswet 1.4 1.2 )
else if highway=cycleway then ( switch ismuddy 1.4 switch iswet 1.0 1.1 )
else if highway=residential|living_street
then ( switch ismuddy 1.5 switch iswet 1.0 1.1 )
else if highway=service then ( switch ismuddy 1.5 switch iswet 1.1 1.2 )
else if istrack then
( add 1.0 add tracktype_penalty add surface_penalty add wet_penalty SAC_scale_penalty )
else if highway=motorway|motorway_link then 100000
else if highway=proposed|abandoned|construction then ( switch ismuddy 10 switch iswet 6 4 )
else if highway=trunk|trunk_link then
( switch iswet ( switch issidewalk 1.5 10 ) ( switch issidewalk 2.0 20 ) )
else if highway=primary|primary_link then
( switch iswet ( switch issidewalk 1.5 5 ) ( switch issidewalk 2.0 10 ) )
else if highway=secondary|secondary_link then
( switch iswet ( switch issidewalk 1.2 2.5 ) ( switch issidewalk 1.5 4.0 ) )
else if highway=tertiary|tertiary_link then
( if iswet then ( switch issidewalk 1.1 1.5 ) else ( switch issidewalk 1.2 2.5 ) )
else if highway=unclassified then
( if ismuddy then 3.0 else if iswet then ( switch issidewalk 1.0 1.3 ) else ( switch issidewalk 1.1 1.5 ) )
else add cost_of_unknown switch ismuddy 0.5 0.0
)
assign rawcostfactor2
if equal hiking_routes_preference 0.0 then rawcostfactor # Not preferring hiking routes
else if is_ldhr then rawcostfactor # is hiking route
else multiply rawcostfactor nonhiking_route_penalty
assign costfactor if shortest_way then ( add 1 accesspenalty )
else max 1.0 add rawcostfactor2 Offroad_factor_for_road
assign downhillcostfactor
if shortest_way then ( add 1 accesspenalty ) else
max 1.0
add rawcostfactor2
add Offroad_factor_for_road
if ismuddy then 0.5 # slicky
else if surface=grass then -0.2 # soft impact
else if ispaved then 0.2 # hard impact
else if surface=gravel|pebblestone|fine_gravel then 0.3 # slides
else 0.0
assign uphillcostfactor
if shortest_way then ( add 1 accesspenalty ) else
max 1.0
add rawcostfactor2
add Offroad_factor_for_road
if ismuddy then 0.3 # slicky
else if surface=grass then 0.1 # unsure foot
else if ispaved then -0.1 # sure foot
else if surface=gravel|pebblestone|fine_gravel then 0.2 # slides
else 0.0
# way priorities used for voice hint generation
assign priorityclassifier =
if ( highway=motorway ) then 30
else if ( highway=motorway_link ) then 29
else if ( highway=trunk ) then 28
else if ( highway=trunk_link ) then 27
else if ( highway=primary ) then 26
else if ( highway=primary_link ) then 25
else if ( highway=secondary ) then 24
else if ( highway=secondary_link ) then 23
else if ( highway=tertiary ) then 22
else if ( highway=tertiary_link ) then 21
else if ( highway=unclassified ) then 20
else if ( highway=residential|living_street ) then 18
else if ( highway=steps|pedestrian ) then 16
else if ( highway=service|cycleway ) then if ( or tracktype=grade1 ispaved ) then 14 else 12
else if ( highway=track|road|bridleway ) then if ( or tracktype=grade1 ispaved ) then 10 else 8
else if ( highway=path|footway ) then ( if ( or tracktype=grade1 ispaved ) then 6
else if tracktype=grade2 then 4
else if not surface=grass|gravel then 3
else 2 )
else 0
# some more classifying bits used for voice hint generation...
assign isbadoneway = 0
assign isgoodoneway = 0
assign isroundabout = junction=roundabout
assign islinktype = highway=motorway_link|trunk_link|primary_link|secondary_link|tertiary_link
assign isgoodforcars = if greater priorityclassifier 19 then true
else if highway=residential|living_street|service then true
else if ( and highway=track tracktype=grade1 ) then true
else false
# ... encoded into a bitmask
assign classifiermask add isbadoneway
add multiply isgoodoneway 2
add multiply isroundabout 4
add multiply islinktype 8
multiply isgoodforcars 16
---context:node # following code refers to node tags
assign defaultaccess
switch access=
1 # add default barrier restrictions here!
switch or access=private access=no
0
1
assign bikeaccess
or nodeaccessgranted=yes
switch bicycle=
switch vehicle=
defaultaccess
switch or vehicle=private vehicle=no
0
1
switch or bicycle=private or bicycle=no bicycle=dismount
0
1
assign footaccess
or bicycle=dismount
switch foot=
defaultaccess
switch or foot=private foot=no
0
1
assign initialcost switch or bikeaccess footaccess 0 1000000
#############################################################################################
# ****) Offroad_factor ( ported MTB_factor from bicycle Trekking profile
#############################################################################################
#
# MTB_factor tweaks/trims MTB approach of the profile by preferring/penalizing in progressive order
# nonpaved - preferred
# not paved - little preferred
# paved - little penalized
# mainroads - penalized
#
# MTB_factor can be used for one-time tweaking of routing profile for particular trip,
# or trimming of the profile according to biker preferencing without need of profile deep insight
#
# Positive values progessively promote/penalize roads in favour of MTB riding.
# Negative value has the opposite effect, preferring mainroads and penalizing unpaved roads.
# This effect is somewhat similar to iswet=1 ( *) wet weather mode ),
# but does not distinguish particular road classes / surfaces / smoothness,
# aside of mentioned schema below.
# The calculated values below is added to the costfactor.
# + MTB_factor for main roads (tertiaries and better),
# + 0.33 * MTB_factor for paved roads,
# - 0.33 * MTB_factor for not paved/not unpaved roads,
# - MTB_factor * ( 1 + 0.33 * smoothnesspenalty ) for unpaved roads. - at MTB_factor 3.0 smootheness is ignored
#
# Default is 0.0 = no effect.
# Recommended -0.5 - +1.0
# Reasonable -2.0 .. +3.0,
#
# Final costfactor is kept >= 1 for final costfacto values.
#############################################################################################
# changelog:
# Feature is / + added / - removed / * changed / ! fixed
# Hiking Version 1.6 ALFA
# 2014-10-10 (c) Poutnik
# Route_foot related hints were improved with usage of Hiking.brf from below and by Arndt comment
# http://pastebin.com/YLpkGVD4
# http://forum.openstreetmap.org/viewtopic.php?pid=447507#p447507
#
# 2014-10-10 v1.1 - changed hiking route preference logic
# 1.2 - fixed preference counting bug
# 2014-10-11 1.3 - adding foot route to hiking routes,
# fixed ferry cost to respect initial cost triggerring.
# added bikeaccess, added shortest way mode, code cleanup
# 2014-10-12: v1.4 - involving turncosts and way/node initial costs as orientation/decision penalties,
# but turning them off for sticking to hiking routes as extra preference,
# tweaking cost preferences
# 2014-10-13 v1.5
# redesigned route preference policy - turncost turned off even for nonsticky but preferred hiking routes
# cost tweaking.
# removed uniform cost=1 for sticky hiking routes, to distinguish quality
# giving penalty 1 for non hiking routes at sticky hiking routes.
# used not round costs to often trigger initial cost calculation
#v1.51 - bugfix of redundant routing penalty
#v 1.6 - initialcost penalty only for sticking routes, decreased way penalties for preferring routes
# 31/5/2015 v 1.7 ALFA + sac_scale + sac_scale_limit implemented
# 10/6/2015 v1.7.1 ALFA * sac_scale improved
# + MTB scale integrated to SAC scale
# + sac_scale_preferred implemented, with progressive penalizing for SAC <> SAC preferred
# 10/6/2015 v1.7.2 BETA ! Fixed general way access penalties
# 15/6/2015 v1.7.3 BETA * SAC-MTB scale integration reevaluated, increased MTB scale penalty
# * MTB scale penalty used as fallback if no SAC rating
# 16/6/2015 v1.7.4 ALFA * Optimized SAC logic
# 17/6/2015 v1.7.5 BETA + Track penalty system
# 20/6/2015 v1.7.6 RELEASE * Modified and simplified route preferencing
# 01/12/2015 v1.8.1 ALFA +ported MTB_factor from bicycle Trekking template, renamed for hiking context to Offroad_factor
# 02/4/2016 v1.8.2 ALFA +implemented path preference factor , try 20.0. Default 0.0 has no effect
# 3/5/2016 v1.8.3 BETA +implemented navigation hint support
# 7/5/2016 v1.8.4 RELEASE +implemented navigation hint support ftom BRouter 1.4
# 10/5/2016 v1.8.6 BETA * decreased turnInstructionCatchingRange from default 40 to 20
# 18/5/2016 v1.8.7 BETA ! Fixed down/uphillcostfactors for shortest_way - to be really shortest

View file

@ -13,8 +13,8 @@ assign turncost 0
assign initialcost 0 assign initialcost 0
assign costfactor assign costfactor
switch railway=rail 1 switch railway=rail|light_rail|narrow_gauge 1
switch railway=narrow_gauge 1 switch railway=tram|subway 2
100000 100000
---context:node # following code refers to node tags ---context:node # following code refers to node tags