diff --git a/README.md b/README.md index bb2b895..ddb4dbe 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,9 @@ brouter ======= configurable OSM offline router with elevation awareness, Java + Android + +For more infos see http://brensche.de/brouter + +Compile with (Java 6!): + +> mvn clean install -Dandroid.sdk.path= diff --git a/brouter-core/pom.xml b/brouter-core/pom.xml new file mode 100644 index 0000000..e75292b --- /dev/null +++ b/brouter-core/pom.xml @@ -0,0 +1,31 @@ + + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-core + jar + + + + org.btools + brouter-util + ${project.version} + + + org.btools + brouter-mapaccess + ${project.version} + + + org.btools + brouter-expressions + ${project.version} + + + diff --git a/brouter-core/src/main/java/btools/router/MatchedWaypoint.java b/brouter-core/src/main/java/btools/router/MatchedWaypoint.java new file mode 100644 index 0000000..cb59d90 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/MatchedWaypoint.java @@ -0,0 +1,56 @@ +/** + * Information on matched way point + * + * @author ab + */ +package btools.router; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import btools.mapaccess.OsmNode; + +final class MatchedWaypoint +{ + public OsmNode node1; + public OsmNode node2; + public OsmNodeNamed crosspoint; + public OsmNodeNamed waypoint; + public double radius; + public int cost; + + public void writeToStream( DataOutput dos ) throws IOException + { + dos.writeInt( node1.ilat ); + dos.writeInt( node1.ilon ); + dos.writeInt( node2.ilat ); + dos.writeInt( node2.ilon ); + dos.writeInt( crosspoint.ilat ); + dos.writeInt( crosspoint.ilon ); + dos.writeInt( waypoint.ilat ); + dos.writeInt( waypoint.ilon ); + dos.writeDouble( radius ); + } + + public static MatchedWaypoint readFromStream( DataInput dis ) throws IOException + { + MatchedWaypoint mwp = new MatchedWaypoint(); + mwp.node1 = new OsmNode(); + mwp.node2 = new OsmNode(); + mwp.crosspoint = new OsmNodeNamed(); + mwp.waypoint = new OsmNodeNamed(); + + mwp.node1.ilat = dis.readInt(); + mwp.node1.ilon = dis.readInt(); + mwp.node2.ilat = dis.readInt(); + mwp.node2.ilon = dis.readInt(); + mwp.crosspoint.ilat = dis.readInt(); + mwp.crosspoint.ilon = dis.readInt(); + mwp.waypoint.ilat = dis.readInt(); + mwp.waypoint.ilon = dis.readInt(); + mwp.radius = dis.readDouble(); + return mwp; + } + +} diff --git a/brouter-core/src/main/java/btools/router/OpenSet.java b/brouter-core/src/main/java/btools/router/OpenSet.java new file mode 100644 index 0000000..77b1340 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OpenSet.java @@ -0,0 +1,172 @@ +/** + * Implementation for the open-set + * that should be somewhat faster + * and memory-efficient than the original + * version based on java.util.TreeSet + * + * It relies on the two double-linked + * lists implemented in OsmPath + * + * @author ab + */ +package btools.router; + +import btools.mapaccess.OsmNode; + +public class OpenSet +{ + private OsmPath start = new OsmPath(); + private OsmPath index2 = new OsmPath(); + + private int addCount = 0; + + private int size = 0; + + public void clear() + { + start.nextInSet = null; + start.nextInIndexSet = null; + index2.nextInIndexSet = null; + size = 0; + addCount = 0; + } + + public void add( OsmPath path ) + { + int ac = path.adjustedCost; + OsmPath p1 = index2; + + // fast forward along index2 + while( p1.nextInIndexSet != null && p1.nextInIndexSet.adjustedCost < ac ) + { + p1 = p1.nextInIndexSet; + } + if ( p1 == index2 ) + { + p1 = start; + } + + // search using index1 + for(;;) + { + if ( p1.nextInIndexSet != null && p1.nextInIndexSet.adjustedCost < ac ) + { + p1 = p1.nextInIndexSet; + } + else if ( p1.nextInSet != null && p1.nextInSet.adjustedCost < ac ) + { + p1 = p1.nextInSet; + } + else + { + break; + } + } + OsmPath p2 = p1.nextInSet; + + p1.nextInSet = path; + path.prevInSet = p1; + path.nextInSet = p2; + if ( p2 != null ) { p2.prevInSet = path; } + size++; + + addCount++; + + // feed random samples to the indices + if ( (addCount & 31) == 0 ) + { + addIndex( path, start ); + } + else if ( (addCount & 1023) == 1023 ) + { + addIndex( path, index2 ); + } + } + + public void remove( OsmPath path ) + { + OsmPath p1 = path.prevInSet; + OsmPath p2 = path.nextInSet; + if ( p1 == null ) + { + return; // not in set + } + path.prevInSet = null; + path.nextInSet = null; + if ( p2 != null ) + { + p2.prevInSet = p1; + } + p1.nextInSet = p2; + + removeIndex( path ); + + size--; + } + + public OsmPath first() + { + return start.nextInSet; + } + + public int size() + { + return size; + } + + public int[] getExtract() + { + int div = size / 1000 + 1; + + int[] res = new int[size/div * 2]; + int i = 0; + int cnt = 0; + for( OsmPath p = start.nextInSet; p != null; p = p.nextInSet ) + { + if ( (++cnt) % div == 0 ) + { + OsmNode n = p.getLink().targetNode; + res[i++] = n.ilon; + res[i++] = n.ilat; + } + } + return res; + } + + // index operations + + private void addIndex( OsmPath path, OsmPath index ) + { + int ac = path.adjustedCost; + OsmPath p1 = index; + OsmPath p2 = p1.nextInIndexSet; + while( p2 != null && p2.adjustedCost < ac ) + { + p1 = p2; + p2 = p2.nextInIndexSet; + } + p1.nextInIndexSet = path; + path.prevInIndexSet = p1; + path.nextInIndexSet = p2; + if ( p2 != null ) { p2.prevInIndexSet = path; } + } + + + private void removeIndex( OsmPath path ) + { + OsmPath p1 = path.prevInIndexSet; + OsmPath p2 = path.nextInIndexSet; + if ( p1 == null ) + { + return; // not in set + } + path.prevInIndexSet = null; + path.nextInIndexSet = null; + if ( p2 != null ) + { + p2.prevInIndexSet = p1; + } + p1.nextInIndexSet = p2; + } + +} diff --git a/brouter-core/src/main/java/btools/router/OsmNodeNamed.java b/brouter-core/src/main/java/btools/router/OsmNodeNamed.java new file mode 100644 index 0000000..1566174 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OsmNodeNamed.java @@ -0,0 +1,33 @@ +/** + * Container for an osm node + * + * @author ab + */ +package btools.router; + +import btools.mapaccess.OsmNode; + +public class OsmNodeNamed extends OsmNode +{ + public String name; + public double radius; // radius of nogopoint + public boolean isNogo = false; + + @Override + public String toString() + { + return ilon + "," + ilat + "," + name; + } + + public static OsmNodeNamed decodeNogo( String s ) + { + OsmNodeNamed n = new OsmNodeNamed(); + int idx1 = s.indexOf( ',' ); + n.ilon = Integer.parseInt( s.substring( 0, idx1 ) ); + int idx2 = s.indexOf( ',', idx1+1 ); + n.ilat = Integer.parseInt( s.substring( idx1+1, idx2 ) ); + n.name = s.substring( idx2+1 ); + n.isNogo = true; + return n; + } +} diff --git a/brouter-core/src/main/java/btools/router/OsmPath.java b/brouter-core/src/main/java/btools/router/OsmPath.java new file mode 100644 index 0000000..39adac9 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OsmPath.java @@ -0,0 +1,371 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import btools.mapaccess.*; + +final class OsmPath implements OsmLinkHolder +{ + // double-linked lists for the openSet + public OsmPath nextInSet; + public OsmPath prevInSet; + public OsmPath nextInIndexSet; + public OsmPath prevInIndexSet; + + /** + * The cost of that path (a modified distance) + */ + public int cost = 0; + + /** + * The elevation-hysteresis-buffer (0-10 m) + */ + private int ehbd; // in micrometer + private int ehbu; // in micrometer + + // the elevation assumed for that path can have a value + // if the corresponding node has not + public short selev; + + private static final int MAX_EHB = 10000000; + + public int adjustedCost = 0; + + public void setAirDistanceCostAdjustment( int costAdjustment ) + { + adjustedCost = cost + costAdjustment; + } + + private OsmNode sourcenode; + private OsmLink link; + public OsmPathElement originElement; + + private OsmLinkHolder nextForLink = null; + + public int treedepth = 0; + + // the position of the waypoint just before + // this path position (for angle calculation) + public int originLon; + public int originLat; + + // the costfactor of the segment just before this paths position + public float lastCostfactor; + + public String message; + + OsmPath() + { + } + + OsmPath( OsmLink link ) + { + this(); + this.link = link; + this.selev = link.targetNode.getSElev(); + } + + OsmPath( OsmNode sourcenode, OsmPath origin, OsmLink link, OsmTrack refTrack, boolean recordTransferNodes, RoutingContext rc ) + { + this(); + this.originElement = new OsmPathElement( origin ); + this.link = link; + this.sourcenode = sourcenode; + this.cost = origin.cost; + this.ehbd = origin.ehbd; + this.ehbu = origin.ehbu; + this.lastCostfactor = origin.lastCostfactor; + addAddionalPenalty(refTrack, recordTransferNodes, origin, link, rc ); + } + + private void addAddionalPenalty(OsmTrack refTrack, boolean recordTransferNodes, OsmPath origin, OsmLink link, RoutingContext rc ) + { + rc.nogomatch = false; + + // extract the 3 positions of the first section + int lon0 = origin.originLon; + int lat0 = origin.originLat; + + OsmNode p1 = origin.link.targetNode; + int lon1 = p1.getILon(); + int lat1 = p1.getILat(); + short ele1 = origin.selev; + + int linkdisttotal = 0; + int linkdist = 0; + int linkelevationcost = 0; + int linkturncost = 0; + + OsmTransferNode transferNode = link.decodeFirsttransfer(); + OsmNode targetNode = link.targetNode; + long lastDescription = -1L; + String lastMessage = null; + for(;;) + { + originLon = lon1; + originLat = lat1; + + int lon2; + int lat2; + short ele2; + long description; + + if ( transferNode == null ) + { + lon2 = targetNode.ilon; + lat2 = targetNode.ilat; + ele2 = targetNode.selev; + description = link.descriptionBitmap; + } + else + { + lon2 = transferNode.ilon; + lat2 = transferNode.ilat; + ele2 = transferNode.selev; + description = transferNode.descriptionBitmap; + } + + // if way description changed, store message + if ( lastMessage != null && description != lastDescription ) + { + originElement.message = lastMessage; + linkdist = 0; + linkelevationcost = 0; + linkturncost = 0; + } + lastDescription = description; + + int dist = rc.calcDistance( lon1, lat1, lon2, lat2 ); + int elefactor = 250000; + boolean stopAtEndpoint = false; + if ( rc.shortestmatch ) + { + elefactor = (int)(elefactor*rc.wayfraction); + + if ( rc.isEndpoint ) + { + stopAtEndpoint = true; + } + else + { + // we just start here, reset cost + cost = 0; + ehbd = 0; + ehbu = 0; + if ( recordTransferNodes ) + { + if ( rc.wayfraction > 0. ) + { + originElement = new OsmPathElement( rc.ilonshortest, rc.ilatshortest, ele2, null ); + } + else + { + originElement = null; // prevent duplicate point + } + } + } + } + + linkdist += dist; + linkdisttotal += dist; + + rc.messageHandler.setCurrentPos( lon2, lat2 ); + rc.expctxWay.evaluate( description, rc.messageHandler ); + + // *** penalty for way-change + if ( origin.originElement != null ) + { + // penalty proportional to direction change + double cos = rc.calcCosAngle( lon0, lat0, lon1, lat1, lon2, lat2 ); + int turncost = (int)(cos * rc.expctxWay.getTurncost() + 0.2 ); // e.g. turncost=90 -> 90 degree = 90m penalty + cost += turncost; + linkturncost += turncost; + } + + // *** penalty for elevation (penalty is for descend! in a way that slow descends give no penalty) + // only the part of the descend that does not fit into the elevation-hysteresis-buffer + // leads to an immediate penalty + + if ( ele2 == Short.MIN_VALUE ) ele2 = ele1; + if ( ele1 != Short.MIN_VALUE ) + { + ehbd += (ele1 - ele2)*elefactor - dist * rc.downhillcutoff; + ehbu += (ele2 - ele1)*elefactor - dist * rc.uphillcutoff; + } + + if ( ehbd > MAX_EHB ) + { + if ( rc.downhillcostdiv > 0 ) + { + int elevationCost = (ehbd-MAX_EHB)/rc.downhillcostdiv; + cost += elevationCost; + linkelevationcost += elevationCost; + } + ehbd = MAX_EHB; + } + else if ( ehbd < 0 ) + { + ehbd = 0; + } + + if ( ehbu > MAX_EHB ) + { + if ( rc.uphillcostdiv > 0 ) + { + int elevationCost = (ehbu-MAX_EHB)/rc.uphillcostdiv; + cost += elevationCost; + linkelevationcost += elevationCost; + } + ehbu = MAX_EHB; + } + else if ( ehbu < 0 ) + { + ehbu = 0; + } + + // *** penalty for distance + float costfactor = rc.expctxWay.getCostfactor(); + float fcost = dist * costfactor + 0.5f; + if ( costfactor >= 10000. || fcost + cost >= 2000000000. ) + { + cost = -1; + return; + } + int waycost = (int)(fcost); + cost += waycost; + + // *** add initial cost if factor changed + float costdiff = costfactor - lastCostfactor; + if ( costdiff > 0.0005 || costdiff < -0.0005 ) + { + lastCostfactor = costfactor; + float initialcost = rc.expctxWay.getInitialcost(); + int iicost = (int)initialcost; + cost += iicost; + } + + if ( recordTransferNodes ) + { + int iCost = (int)(rc.expctxWay.getCostfactor()*1000 + 0.5f); + lastMessage = (lon2-180000000) + "\t" + + (lat2-90000000) + "\t" + + ele2/4 + "\t" + + linkdist + "\t" + + iCost + "\t" + + linkelevationcost + + "\t" + linkturncost + + rc.expctxWay.getCsvDescription( description ); + } + + if ( stopAtEndpoint ) + { + if ( recordTransferNodes ) + { + originElement = new OsmPathElement( rc.ilonshortest, rc.ilatshortest, ele2, originElement ); + originElement.cost = cost; + } + if ( rc.nogomatch ) + { + cost = -1; + } + return; + } + + if ( transferNode == null ) + { + // *** penalty for being part of the reference track + if ( refTrack != null && refTrack.containsNode( targetNode ) && refTrack.containsNode( origin.link.targetNode ) ) + { + int reftrackcost = linkdisttotal; + cost += reftrackcost; + } + message = lastMessage; + selev = ele2; + break; + } + transferNode = transferNode.next; + + if ( recordTransferNodes ) + { + originElement = new OsmPathElement( lon2, lat2, ele2, originElement ); + originElement.cost = cost; + } + lon0 = lon1; + lat0 = lat1; + lon1 = lon2; + lat1 = lat2; + ele1 = ele2; + + } + + // check for nogo-matches (after the *actual* start of segment) + if ( rc.nogomatch ) + { + cost = -1; + return; + } + + // finally add node-costs for target node + if ( targetNode.nodeDescription != 0L ) + { + rc.messageHandler.setCurrentPos( targetNode.ilon, targetNode.ilat ); + rc.expctxNode.evaluate( targetNode.nodeDescription, rc.messageHandler ); + float initialcost = rc.expctxNode.getInitialcost(); + if ( initialcost >= 1000000. ) + { + cost = -1; + return; + } + int iicost = (int)initialcost; + cost += iicost; + } + } + + public int elevationCorrection( RoutingContext rc ) + { + return ( rc.downhillcostdiv > 0 ? ehbd/rc.downhillcostdiv : 0 ) + + ( rc.uphillcostdiv > 0 ? ehbu/rc.uphillcostdiv : 0 ); + } + + public boolean definitlyWorseThan( OsmPath p, RoutingContext rc ) + { + int c = p.cost; + if ( rc.downhillcostdiv > 0 ) + { + int delta = p.ehbd - ehbd; + if ( delta > 0 ) c += delta/rc.downhillcostdiv; + } + if ( rc.uphillcostdiv > 0 ) + { + int delta = p.ehbu - ehbu; + if ( delta > 0 ) c += delta/rc.uphillcostdiv; + } + + return cost > c; + } + + + public OsmNode getSourceNode() + { + return sourcenode; + } + + public OsmLink getLink() + { + return link; + } + + + public void setNextForLink( OsmLinkHolder holder ) + { + nextForLink = holder; + } + + public OsmLinkHolder getNextForLink() + { + return nextForLink; + } +} diff --git a/brouter-core/src/main/java/btools/router/OsmPathElement.java b/brouter-core/src/main/java/btools/router/OsmPathElement.java new file mode 100644 index 0000000..af89341 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OsmPathElement.java @@ -0,0 +1,113 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.router; + +import java.io.DataInput; +import java.io.DataOutput; +import java.io.IOException; + +import btools.mapaccess.OsmNode; +import btools.mapaccess.OsmPos; + +final class OsmPathElement implements OsmPos +{ + private int ilat; // latitude + private int ilon; // longitude + private short selev; // longitude + + public String message = null; // description + + public int cost; + + // interface OsmPos + public int getILat() + { + return ilat; + } + + public int getILon() + { + return ilon; + } + + public short getSElev() + { + return selev; + } + + public double getElev() + { + return selev / 4.; + } + + public long getIdFromPos() + { + return ((long)ilon)<<32 | ilat; + } + + public int calcDistance( OsmPos p ) + { + double l = (ilat-90000000) * 0.00000001234134; + double l2 = l*l; + double l4 = l2*l2; + double coslat = 1.- l2 + l4 / 6.; + + double dlat = (ilat - p.getILat() )/1000000.; + double dlon = (ilon - p.getILon() )/1000000. * coslat; + double d = Math.sqrt( dlat*dlat + dlon*dlon ) * (6378000. / 57.); + return (int)(d + 1.0 ); + } + + public OsmPathElement origin; + + // construct a path element from a path + public OsmPathElement( OsmPath path ) + { + OsmNode n = path.getLink().targetNode; + ilat = n.getILat(); + ilon = n.getILon(); + selev = path.selev; + cost = path.cost; + + origin = path.originElement; + message = path.message; + } + + public OsmPathElement( int ilon, int ilat, short selev, OsmPathElement origin ) + { + this.ilon = ilon; + this.ilat = ilat; + this.selev = selev; + this.origin = origin; + } + + private OsmPathElement() + { + } + + public String toString() + { + return ilon + "_" + ilat; + } + + public void writeToStream( DataOutput dos ) throws IOException + { + dos.writeInt( ilat ); + dos.writeInt( ilon ); + dos.writeShort( selev ); + dos.writeInt( cost ); + } + + public static OsmPathElement readFromStream( DataInput dis ) throws IOException + { + OsmPathElement pe = new OsmPathElement(); + pe.ilat = dis.readInt(); + pe.ilon = dis.readInt(); + pe.selev = dis.readShort(); + pe.cost = dis.readInt(); + return pe; + } +} diff --git a/brouter-core/src/main/java/btools/router/OsmTrack.java b/brouter-core/src/main/java/btools/router/OsmTrack.java new file mode 100644 index 0000000..2f2a485 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/OsmTrack.java @@ -0,0 +1,365 @@ +/** + * Container for a track + * + * @author ab + */ +package btools.router; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.FileWriter; +import java.io.InputStreamReader; +import java.util.ArrayList; + +import btools.mapaccess.OsmPos; +import btools.util.CompactLongMap; +import btools.util.FrozenLongMap; + +public final class OsmTrack +{ + public MatchedWaypoint endPoint; + + private class OsmPathElementHolder + { + public OsmPathElement node; + public OsmPathElementHolder nextHolder; + } + + + public ArrayList nodes = new ArrayList(); + + private CompactLongMap nodesMap; + + public String message = null; + public ArrayList messageList = null; + + public String name = "unset"; + + public void addNode( OsmPathElement node ) + { + nodes.add( 0, node ); + } + + public void buildMap() + { + nodesMap = new CompactLongMap(); + for( OsmPathElement node: nodes ) + { + long id = node.getIdFromPos(); + OsmPathElementHolder nh = new OsmPathElementHolder(); + nh.node = node; + OsmPathElementHolder h = nodesMap.get( id ); + if ( h != null ) + { + while( h.nextHolder != null ) + { + h = h.nextHolder; + } + h.nextHolder = nh; + } + else + { + nodesMap.fastPut( id, nh ); + } + } + nodesMap = new FrozenLongMap( nodesMap ); + } + + /** + * writes the track in binary-format to a file + * @param filename the filename to write to + */ + public void writeBinary( String filename ) throws Exception + { + DataOutputStream dos = new DataOutputStream( new BufferedOutputStream( new FileOutputStream( filename ) ) ); + + endPoint.writeToStream( dos ); + dos.writeInt( nodes.size() ); + for( OsmPathElement node: nodes ) + { + node.writeToStream( dos ); + } + dos.close(); + } + + public static OsmTrack readBinary( String filename, OsmNodeNamed newEp ) + { + OsmTrack t = null; + if ( filename != null ) + { + File f = new File( filename ); + if ( f.exists() ) + { + try + { + DataInputStream dis = new DataInputStream( new BufferedInputStream( new FileInputStream( f ) ) ); + MatchedWaypoint ep = MatchedWaypoint.readFromStream( dis ); + int dlon = ep.waypoint.ilon - newEp.ilon; + int dlat = ep.waypoint.ilat - newEp.ilat; + if ( dlon < 20 && dlon > -20 && dlat < 20 && dlat > -20 ) + { + t = new OsmTrack(); + t.endPoint = ep; + int n = dis.readInt(); + OsmPathElement last_pe = null; + for( int i=0; i 0 || nodes.size() == 0 ) + { + nodes.add( t.nodes.get(i) ); + } + } + distance += t.distance; + ascend += t.ascend; + plainAscend += t.plainAscend; + cost += t.cost; + } + + public int distance; + public int ascend; + public int plainAscend; + public int cost; + + /** + * writes the track in gpx-format to a file + * @param filename the filename to write to + */ + public void writeGpx( String filename ) throws Exception + { + BufferedWriter bw = new BufferedWriter( new FileWriter( filename ) ); + + bw.write( formatAsGpx() ); + bw.close(); + } + + public String formatAsGpx() + { + StringBuilder sb = new StringBuilder(8192); + + sb.append( "\n" ); + for( int i=messageList.size()-1; i >= 0; i-- ) + { + String message = messageList.get(i); + if ( i < messageList.size()-1 ) message = "(alt-index " + i + ": " + message + " )"; + if ( message != null ) sb.append( "\n" ); + } + sb.append( "\n" ); + sb.append( " \n" ); + sb.append( " " + name + "\n" ); + sb.append( " \n" ); + + for( OsmPathElement n : nodes ) + { + String sele = n.getSElev() == Short.MIN_VALUE ? "" : "" + n.getElev() + ""; + sb.append( " " + sele + "\n" ); + } + + sb.append( " \n" ); + sb.append( " \n" ); + sb.append( "\n" ); + + return sb.toString(); + } + + public void writeKml( String filename ) throws Exception + { + BufferedWriter bw = new BufferedWriter( new FileWriter( filename ) ); + + bw.write( formatAsKml() ); + bw.close(); + } + + public String formatAsKml() + { + StringBuilder sb = new StringBuilder(8192); + + sb.append( "\n" ); + + sb.append( "\n" ); + sb.append( " \n" ); + sb.append( " KML Samples\n" ); + sb.append( " 1\n" ); + sb.append( " 3.497064\n" ); + sb.append( " 872\n" ); + sb.append( " To enable simple instructions add: 'instructions=1' as parameter to the URL\n" ); + sb.append( " \n" ); + sb.append( " Paths\n" ); + sb.append( " 0\n" ); + sb.append( " Examples of paths.\n" ); + sb.append( " \n" ); + sb.append( " Tessellated\n" ); + sb.append( " 0\n" ); + sb.append( " tag has a value of 1, the line will contour to the underlying terrain]]>\n" ); + sb.append( " \n" ); + sb.append( " 1\n" ); + sb.append( " " ); + + + for( OsmPathElement n : nodes ) + { + sb.append( formatPos( n.getILon() - 180000000 ) + "," + formatPos( n.getILat() - 90000000 ) + "\n" ); + } + + sb.append( " \n" ); + sb.append( " \n" ); + sb.append( " \n" ); + sb.append( " \n" ); + sb.append( " \n" ); + sb.append( "\n" ); + + return sb.toString(); + } + + private static String formatPos( int p ) + { + boolean negative = p < 0; + if ( negative ) p = -p; + char[] ac = new char[12]; + int i = 11; + while( p != 0 || i > 3 ) + { + ac[i--] = (char)('0' + (p % 10)); + p /= 10; + if ( i == 5 ) ac[i--] = '.'; + } + if ( negative ) ac[i--] = '-'; + return new String( ac, i+1, 11-i ); + } + + public void dumpMessages( String filename, RoutingContext rc ) throws Exception + { + BufferedWriter bw = filename == null ? null : new BufferedWriter( new FileWriter( filename ) ); + + // csv-header-line + + String header = "Longitude\tLatitude\tElevation\tDistance\tCostPerKm\tElevCost\tTurnCost"; + if ( rc.expctxWay != null ) + { + header += rc.expctxWay.getCsvHeader(); + } + dumpLine( bw, header ); + for( OsmPathElement n : nodes ) + { + if ( n.message != null ) + { + dumpLine( bw, n.message ); + } + } + if ( bw != null ) bw.close(); + } + + private void dumpLine( BufferedWriter bw, String s) throws Exception + { + if ( bw == null ) + { + System.out.println( s ); + } + else + { + bw.write( s ); + bw.write( "\n" ); + } + } + + public void readGpx( String filename ) throws Exception + { + File f = new File( filename ); + if ( !f.exists() ) return; + BufferedReader br = new BufferedReader( + new InputStreamReader( + new FileInputStream( f ) ) ); + + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + + int idx0 = line.indexOf( "= 0 ) + { + idx0 += 12; + int idx1 = line.indexOf( '"', idx0 ); + int ilon = (int)((Double.parseDouble( line.substring( idx0, idx1 ) ) + 180. )*1000000. + 0.5); + int idx2 = line.indexOf( " lat=\"" ); + if ( idx2 < 0 ) continue; + idx2 += 6; + int idx3 = line.indexOf( '"', idx2 ); + int ilat = (int)((Double.parseDouble( line.substring( idx2, idx3 ) ) + 90. )*1000000. + 0.5); + nodes.add( new OsmPathElement( ilon, ilat, (short)0, null ) ); + } + } + br.close(); + } + + public boolean equalsTrack( OsmTrack t ) + { + if ( nodes.size() != t.nodes.size() ) return false; + for( int i=0; i 3 ) idx = 3; + alternativeIdx = idx; + } + public int getAlternativeIdx() + { + return alternativeIdx; + } + public int alternativeIdx = 0; + public String localFunction; + + public String rawTrackPath; + + public String getProfileName() + { + String name = localFunction == null ? "unknown" : localFunction; + if ( name.endsWith( ".brf" ) ) name = name.substring( 0, localFunction.length() - 4 ); + int idx = name.lastIndexOf( '/' ); + if ( idx >= 0 ) name = name.substring( idx+1 ); + return name; + } + + public BExpressionContext expctxWay; + public BExpressionContext expctxNode; + + public int downhillcostdiv; + public int downhillcutoff; + public int uphillcostdiv; + public int uphillcutoff; + public boolean carMode; + public double pass1coefficient; + public double pass2coefficient; + + public void readGlobalConfig( BExpressionContext expctxGlobal ) + { + downhillcostdiv = (int)expctxGlobal.getVariableValue( "downhillcost" ); + downhillcutoff = (int)(expctxGlobal.getVariableValue( "downhillcutoff" )*10000); + uphillcostdiv = (int)expctxGlobal.getVariableValue( "uphillcost" ); + uphillcutoff = (int)(expctxGlobal.getVariableValue( "uphillcutoff" )*10000); + if ( downhillcostdiv != 0 ) downhillcostdiv = 1000000/downhillcostdiv; + if ( uphillcostdiv != 0 ) uphillcostdiv = 1000000/uphillcostdiv; + carMode = 0.f != expctxGlobal.getVariableValue( "validForCars" ); + pass1coefficient = expctxGlobal.getVariableValue( "pass1coefficient", 1.5f ); + pass2coefficient = expctxGlobal.getVariableValue( "pass2coefficient", 0.f ); + } + + public RoutingMessageHandler messageHandler = new RoutingMessageHandler(); + + public List nogopoints = null; + private List keepnogopoints = null; + + private double coslat; + public boolean nogomatch = false; + public boolean isEndpoint = false; + + public boolean shortestmatch = false; + public double wayfraction; + public int ilatshortest; + public int ilonshortest; + + public void prepareNogoPoints( List nogos ) + { + for( OsmNodeNamed nogo : nogos ) + { + String s = nogo.name; + int idx = s.indexOf( ' ' ); + if ( idx > 0 ) s = s.substring( 0 , idx ); + int ir = 20; // default radius + if ( s.length() > 4 ) + { + try { ir = Integer.parseInt( s.substring( 4 ) ); } + catch( Exception e ) { /* ignore */ } + } + nogo.radius = ir / 111894.; // 6378000. / 57.; + } + } + + public void setWaypoint( OsmNodeNamed wp, boolean endpoint ) + { + keepnogopoints = nogopoints; + nogopoints = new ArrayList(); + nogopoints.add( wp ); + if ( keepnogopoints != null ) nogopoints.addAll( keepnogopoints ); + isEndpoint = endpoint; + } + + public void unsetWaypoint() + { + nogopoints = keepnogopoints; + isEndpoint = false; + } + + public int calcDistance( int lon1, int lat1, int lon2, int lat2 ) + { + double l = (lat2 - 90000000) * 0.00000001234134; + double l2 = l*l; + double l4 = l2*l2; + coslat = 1.- l2 + l4 / 6.; + double coslat6 = coslat*0.000001; + + double dx = (lon2 - lon1 ) * coslat6; + double dy = (lat2 - lat1 ) * 0.000001; + double d = Math.sqrt( dy*dy + dx*dx ); + + shortestmatch = false; + + if ( d > 0. && nogopoints != null ) + { + for( OsmNodeNamed nogo : nogopoints ) + { + double x1 = (lon1 - nogo.ilon) * coslat6; + double y1 = (lat1 - nogo.ilat) * 0.000001; + double x2 = (lon2 - nogo.ilon) * coslat6; + double y2 = (lat2 - nogo.ilat) * 0.000001; + double r12 = x1*x1 + y1*y1; + double r22 = x2*x2 + y2*y2; + double radius = Math.abs( r12 < r22 ? y1*dx - x1*dy : y2*dx - x2*dy ) / d; + + if ( radius < nogo.radius ) // 20m + { + double s1 = x1*dx + y1*dy; + double s2 = x2*dx + y2*dy; + + + if ( s1 < 0. ) { s1 = -s1; s2 = -s2; } + if ( s2 > 0. ) + { + radius = Math.sqrt( s1 < s2 ? r12 : r22 ); + if ( radius > nogo.radius ) continue; // 20m ^ 2 + } + if ( nogo.isNogo ) nogomatch = true; + else + { + shortestmatch = true; + nogo.radius = radius; // shortest distance to way + // calculate remaining distance + if ( s2 < 0. ) + { + double distance = d > 0. ? -s2 / d : 0.; + wayfraction = d > 0. ? distance / d : 0.; + double xm = x2 - wayfraction*dx; + double ym = y2 - wayfraction*dy; + ilonshortest = (int)(xm / coslat6 + nogo.ilon); + ilatshortest = (int)(ym / 0.000001 + nogo.ilat); + } + else if ( s1 > s2 ) + { + wayfraction = 0.; + ilonshortest = lon2; + ilatshortest = lat2; + } + else + { + wayfraction = 1.; + ilonshortest = lon1; + ilatshortest = lat1; + } + + // here it gets nasty: there can be nogo-points in the list + // *after* the shortest distance point. In case of a shortest-match + // we use the reduced way segment for nogo-matching, in order not + // to cut our escape-way if we placed a nogo just in front of where we are + if ( isEndpoint ) + { + wayfraction = 1. - wayfraction; + lon2 = ilonshortest; + lat2 = ilatshortest; + } + else + { + nogomatch = false; + lon1 = ilonshortest; + lat1 = ilatshortest; + } + dx = (lon2 - lon1 ) * coslat6; + dy = (lat2 - lat1 ) * 0.000001; + d = Math.sqrt( dy*dy + dx*dx ); + } + } + } + } + double dd = d * 111894.7368; // 6378000. / 57.; + return (int)(dd + 1.0 ); + } + + // assumes that calcDistance/calcCosAngle called in sequence, so coslat valid + public double calcCosAngle( int lon0, int lat0, int lon1, int lat1, int lon2, int lat2 ) + { + double dlat1 = (lat1 - lat0); + double dlon1 = (lon1 - lon0) * coslat; + double dlat2 = (lat2 - lat1); + double dlon2 = (lon2 - lon1) * coslat; + + double dd = Math.sqrt( (dlat1*dlat1 + dlon1*dlon1)*(dlat2*dlat2 + dlon2*dlon2) ); + if ( dd == 0. ) return 0.; + double cosp = (dlat1*dlat2 + dlon1*dlon2)/dd; + return 1.-cosp; // don't care to really do acos.. + } + + @Override + public boolean isWithinRadius( int ilon0, int ilat0, OsmTransferNode firstTransfer, int ilon1, int ilat1 ) + { + OsmNodeNamed wp = nogopoints.get(0); + double keepRadius = wp.radius; + try + { + int ilon = ilon0; + int ilat = ilat0; + for( OsmTransferNode trans = firstTransfer; trans != null; trans = trans.next ) + { + calcDistance( ilon, ilat, trans.ilon, trans.ilat ); + ilon = trans.ilon; + ilat = trans.ilat; + } + calcDistance( ilon, ilat, ilon1, ilat1 ); + return wp.radius < keepRadius; + } + finally + { + wp.radius = keepRadius; + } + } + +} diff --git a/brouter-core/src/main/java/btools/router/RoutingEngine.java b/brouter-core/src/main/java/btools/router/RoutingEngine.java new file mode 100644 index 0000000..ad276bc --- /dev/null +++ b/brouter-core/src/main/java/btools/router/RoutingEngine.java @@ -0,0 +1,968 @@ +package btools.router; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import btools.expressions.BExpressionContext; +import btools.mapaccess.NodesCache; +import btools.mapaccess.OsmLink; +import btools.mapaccess.OsmLinkHolder; +import btools.mapaccess.OsmNode; +import btools.mapaccess.OsmNodesMap; + +public class RoutingEngine extends Thread +{ + private OsmNodesMap nodesMap; + private NodesCache nodesCache; + private OpenSet openSet = new OpenSet(); + private boolean finished = false; + + private List waypoints = null; + private int linksProcessed = 0; + + private OsmTrack foundTrack = new OsmTrack(); + private OsmTrack foundRawTrack = null; + private int alternativeIndex = 0; + + private String errorMessage = null; + + private volatile boolean terminated; + + private String segmentDir; + private String outfileBase; + private String logfileBase; + private boolean infoLogEnabled; + private RoutingContext routingContext; + + private double airDistanceCostFactor; + private OsmTrack guideTrack; + + private OsmPathElement matchPath; + + private long startTime; + private long maxRunningTime; + + public boolean quite = false; + + public RoutingEngine( String outfileBase, String logfileBase, String segmentDir, + List waypoints, RoutingContext rc ) + { + this.segmentDir = segmentDir; + this.outfileBase = outfileBase; + this.logfileBase = logfileBase; + this.waypoints = waypoints; + this.infoLogEnabled = outfileBase != null; + this.routingContext = rc; + + if ( rc.localFunction != null ) + { + String profileBaseDir = System.getProperty( "profileBaseDir" ); + File profileDir; + File profileFile; + if ( profileBaseDir == null ) + { + profileDir = new File( rc.localFunction ).getParentFile(); + profileFile = new File( rc.localFunction ) ; + } + else + { + profileDir = new File( profileBaseDir ); + profileFile = new File( profileDir, rc.localFunction + ".brf" ) ; + } + BExpressionContext expctxGlobal = new BExpressionContext( "global" ); + expctxGlobal.readMetaData( new File( profileDir, "lookups.dat" ) ); + expctxGlobal.parseFile( profileFile, null ); + expctxGlobal.evaluate( 1L, rc.messageHandler ); + rc.readGlobalConfig(expctxGlobal); + + rc.expctxWay = new BExpressionContext( "way", 4096 ); + rc.expctxWay.readMetaData( new File( profileDir, "lookups.dat" ) ); + rc.expctxWay.parseFile( profileFile, "global" ); + + rc.expctxNode = new BExpressionContext( "node", 1024 ); + rc.expctxNode.readMetaData( new File( profileDir, "lookups.dat" ) ); + rc.expctxNode.parseFile( profileFile, "global" ); + } + } + + private void logInfo( String s ) + { + if ( infoLogEnabled ) + { + System.out.println( s ); + } + } + + public void run() + { + doRun( 0 ); + } + + public void doRun( long maxRunningTime ) + { + try + { + startTime = System.currentTimeMillis(); + this.maxRunningTime = maxRunningTime; + OsmTrack sum = null; + OsmTrack track = null; + ArrayList messageList = new ArrayList(); + for( int i=0; !terminated; i++ ) + { + track = findTrack( sum ); + track.message = "track-length = " + track.distance + " filtered ascend = " + track.ascend + + " plain-ascend = " + track.plainAscend + " cost=" + track.cost; + track.name = "brouter_" + routingContext.getProfileName() + "_" + i; + + messageList.add( track.message ); + track.messageList = messageList; + if ( outfileBase != null ) + { + String filename = outfileBase + i + ".gpx"; + OsmTrack oldTrack = new OsmTrack(); + oldTrack.readGpx(filename); + if ( track.equalsTrack( oldTrack ) ) + { + if ( sum == null ) sum = new OsmTrack(); + sum.addNodes( track ); + continue; + } + track.writeGpx( filename ); + foundTrack = track; + alternativeIndex = i; + } + else + { + if ( i == routingContext.getAlternativeIdx() ) + { + if ( "CSV".equals( System.getProperty( "reportFormat" ) ) ) + { + track.dumpMessages( null, routingContext ); + } + else + { + if ( !quite ) + { + System.out.println( track.formatAsGpx() ); + } + } + foundTrack = track; + } + else + { + if ( sum == null ) sum = new OsmTrack(); + sum.addNodes( track ); + continue; + } + } + if ( logfileBase != null ) + { + String logfilename = logfileBase + i + ".csv"; + track.dumpMessages( logfilename, routingContext ); + } + break; + } + long endTime = System.currentTimeMillis(); + logInfo( "execution time = " + (endTime-startTime)/1000. + " seconds" ); + } + catch( Exception e) + { + errorMessage = e instanceof IllegalArgumentException ? e.getMessage() : e.toString(); + logInfo( "Exception (linksProcessed=" + linksProcessed + ": " + errorMessage ); + e.printStackTrace(); + } + catch( Error e) + { + String hint = cleanOnOOM(); + errorMessage = e.toString() + hint; + logInfo( "Error (linksProcessed=" + linksProcessed + ": " + errorMessage ); + e.printStackTrace(); + } + finally + { + openSet.clear(); + finished = true; // this signals termination to outside + } + } + + public String cleanOnOOM() + { + boolean oom_carsubset_hint = nodesCache == null ? false : nodesCache.oom_carsubset_hint; + nodesMap = null; + nodesCache = null; + terminate(); + return oom_carsubset_hint ? "\nPlease use 'carsubset' maps for long-distance car-routing" : ""; + } + + + + private OsmTrack findTrack( OsmTrack refTrack ) + { + OsmTrack totaltrack = new OsmTrack(); + MatchedWaypoint[] wayointIds = new MatchedWaypoint[waypoints.size()]; + + // check for a track for that target + OsmTrack nearbyTrack = null; + if ( refTrack == null ) + { + nearbyTrack = OsmTrack.readBinary( routingContext.rawTrackPath, waypoints.get( waypoints.size()-1) ); + if ( nearbyTrack != null ) + { + wayointIds[waypoints.size()-1] = nearbyTrack.endPoint; + } + } + + // match waypoints to nodes + for( int i=0; i nodeList = nodesCache.getAllNodes(); + + MatchedWaypoint mwp = new MatchedWaypoint(); + mwp.waypoint = wp; + + // first loop just to expand reverse links + for( OsmNode n : nodeList ) + { + if ( !nodesCache.obtainNonHollowNode( n ) ) + { + continue; + } + expandHollowLinkTargets( n, false ); + OsmLink startLink = new OsmLink(); + startLink.targetNode = n; + OsmPath startPath = new OsmPath( startLink ); + startLink.addLinkHolder( startPath ); + for( OsmLink link = n.firstlink; link != null; link = link.next ) + { + if ( link.counterLinkWritten ) continue; // reverse link not found + OsmNode nextNode = link.targetNode; + if ( nextNode.isHollow() ) continue; // border node? + if ( nextNode.firstlink == null ) continue; // don't care about dead ends + if ( nextNode == n ) continue; // ? + double oldRadius = wp.radius; + OsmPath testPath = new OsmPath( n, startPath, link, null, false, routingContext ); + if ( wp.radius < oldRadius ) + { + if ( testPath.cost < 0 ) + { + wp.radius = oldRadius; // no valid way + } + else + { + mwp.node1 = n; + mwp.node2 = nextNode; + mwp.radius = wp.radius; + mwp.cost = testPath.cost; + mwp.crosspoint = new OsmNodeNamed(); + mwp.crosspoint.ilon = routingContext.ilonshortest; + mwp.crosspoint.ilat = routingContext.ilatshortest; + } + } + } + } + return mwp; + } + + // expand hollow link targets and resolve reverse links + private void expandHollowLinkTargets( OsmNode n, boolean failOnReverseNotFound ) + { + for( OsmLink link = n.firstlink; link != null; link = link.next ) + { + if ( ! nodesCache.obtainNonHollowNode( link.targetNode ) ) + { + continue; + } + + if ( link.counterLinkWritten ) + { + OsmLink rlink = link.targetNode.getReverseLink( n.getILon(), n.getILat() ); + if ( rlink == null ) + { + if ( failOnReverseNotFound ) throw new RuntimeException( "reverse link not found!" ); + } + else + { + link.descriptionBitmap = rlink.descriptionBitmap; + link.firsttransferBytes = rlink.firsttransferBytes; + link.counterLinkWritten = false; + } + } + } + n.wasProcessed = true; + } + + private OsmTrack searchTrack( MatchedWaypoint startWp, MatchedWaypoint endWp, OsmTrack nearbyTrack, OsmTrack refTrack ) + { + OsmTrack track = null; + double[] airDistanceCostFactors = new double[]{ routingContext.pass1coefficient, routingContext.pass2coefficient }; + boolean isDirty = false; + + if ( nearbyTrack != null ) + { + airDistanceCostFactor = 0.; + try + { + track = findTrack( "re-routing", startWp, endWp, nearbyTrack , refTrack, true ); + } + catch( IllegalArgumentException iae ) + { + // fast partial recalcs: if that timed out, but we had a match, + // build the concatenation from the partial and the nearby track + if ( matchPath != null ) + { + track = mergeTrack( matchPath, nearbyTrack ); + isDirty = true; + } + maxRunningTime += System.currentTimeMillis() - startTime; // reset timeout... + } + } + + if ( track == null ) + { + for( int cfi = 0; cfi < airDistanceCostFactors.length && !terminated; cfi++ ) + { + airDistanceCostFactor = airDistanceCostFactors[cfi]; + + if ( airDistanceCostFactor < 0. ) + { + continue; + } + + OsmTrack t = findTrack( cfi == 0 ? "pass0" : "pass1", startWp, endWp, track , refTrack, false ); + if ( t == null && track != null && matchPath != null ) + { + // ups, didn't find it, use a merge + t = mergeTrack( matchPath, track ); + } + if ( t != null ) + { + track = t; + } + else + { + throw new IllegalArgumentException( "no track found at pass=" + cfi ); + } + } + } + if ( track == null ) throw new IllegalArgumentException( "no track found" ); + + if ( refTrack == null && !isDirty ) + { + track.endPoint = endWp; + foundRawTrack = track; + } + + // final run for verbose log info and detail nodes + airDistanceCostFactor = 0.; + guideTrack = track; + try + { + OsmTrack tt = findTrack( "re-tracking", startWp, endWp, null , refTrack, false ); + if ( tt == null ) throw new IllegalArgumentException( "error re-tracking track" ); + return tt; + } + finally + { + guideTrack = null; + } + } + + + private void resetCache() + { + nodesMap = new OsmNodesMap(); + nodesCache = new NodesCache(segmentDir, nodesMap, routingContext.expctxWay.lookupVersion, routingContext.carMode, nodesCache ); + } + + private OsmNode getStartNode( long startId ) + { + // initialize the start-node + OsmNode start = nodesMap.get( startId ); + if ( start == null ) + { + start = new OsmNode( startId ); + start.setHollow(); + nodesMap.put( startId, start ); + } + if ( !nodesCache.obtainNonHollowNode( start ) ) + { + return null; + } + expandHollowLinkTargets( start, true ); + return start; + } + + private OsmPath getStartPath( OsmNode n1, OsmNode n2, MatchedWaypoint mwp, MatchedWaypoint endWp, boolean sameSegmentSearch ) + { + OsmPath p = getStartPath( n1, n2, mwp.waypoint, endWp.crosspoint ); + + // special case: start+end on same segment + if ( sameSegmentSearch ) + { + OsmPath pe = getEndPath( n1, p.getLink(), endWp.crosspoint, endWp.crosspoint ); + OsmPath pt = getEndPath( n1, p.getLink(), null, endWp.crosspoint ); + int costdelta = pt.cost - p.cost; + if ( pe.cost >= costdelta ) + { + pe.cost -= costdelta; + pe.adjustedCost -= costdelta; + + if ( guideTrack != null ) + { + // nasty stuff: combine the path cause "new OsmPath()" cannot handle start+endpoint + OsmPathElement startElement = p.originElement; + while( startElement.origin != null ) + { + startElement = startElement.origin; + } + if ( pe.originElement.cost > costdelta ) + { + OsmPathElement e = pe.originElement; + while( e.origin != null && e.origin.cost > costdelta ) + { + e = e.origin; + e.cost -= costdelta; + } + e.origin = startElement; + } + else + { + pe.originElement = startElement; + } + } + return pe; + } + } + return p; + } + + + + private OsmPath getStartPath( OsmNode n1, OsmNode n2, OsmNodeNamed wp, OsmNode endPos ) + { + try + { + routingContext.setWaypoint( wp, false ); + OsmPath bestPath = null; + OsmLink bestLink = null; + OsmLink startLink = new OsmLink(); + startLink.targetNode = n1; + OsmPath startPath = new OsmPath( startLink ); + startLink.addLinkHolder( startPath ); + double minradius = 1e10; + for( OsmLink link = n1.firstlink; link != null; link = link.next ) + { + OsmNode nextNode = link.targetNode; + if ( nextNode.isHollow() ) continue; // border node? + if ( nextNode.firstlink == null ) continue; // don't care about dead ends + if ( nextNode == n1 ) continue; // ? + if ( nextNode != n2 ) continue; // just that link + + wp.radius = 1e9; + OsmPath testPath = new OsmPath( null, startPath, link, null, guideTrack != null, routingContext ); + testPath.setAirDistanceCostAdjustment( (int)( nextNode.calcDistance( endPos ) * airDistanceCostFactor ) ); + if ( wp.radius < minradius ) + { + bestPath = testPath; + minradius = wp.radius; + bestLink = link; + } + } + if ( bestLink != null ) + { + bestLink.addLinkHolder( bestPath ); + } + bestPath.treedepth = 1; + + return bestPath; + } + finally + { + routingContext.unsetWaypoint(); + } + } + + private OsmPath getEndPath( OsmNode n1, OsmLink link, OsmNodeNamed wp, OsmNode endPos ) + { + try + { + if ( wp != null ) routingContext.setWaypoint( wp, true ); + OsmLink startLink = new OsmLink(); + startLink.targetNode = n1; + OsmPath startPath = new OsmPath( startLink ); + startLink.addLinkHolder( startPath ); + + if ( wp != null ) wp.radius = 1e-5; + + OsmPath testPath = new OsmPath( n1, startPath, link, null, guideTrack != null, routingContext ); + testPath.setAirDistanceCostAdjustment( 0 ); + + return testPath; + } + finally + { + if ( wp != null ) routingContext.unsetWaypoint(); + } + } + + private OsmTrack findTrack( String operationName, MatchedWaypoint startWp, MatchedWaypoint endWp, OsmTrack costCuttingTrack, OsmTrack refTrack, boolean reducedTimeoutWhenUnmatched ) + { + boolean verbose = guideTrack != null; + + int maxTotalCost = 1000000000; + + logInfo( "findtrack with maxTotalCost=" + maxTotalCost + " airDistanceCostFactor=" + airDistanceCostFactor ); + + matchPath = null; + int nodesVisited = 0; + + resetCache(); + long endNodeId1 = endWp.node1.getIdFromPos(); + long endNodeId2 = endWp.node2.getIdFromPos(); + long startNodeId1 = startWp.node1.getIdFromPos(); + long startNodeId2 = startWp.node2.getIdFromPos(); + + OsmNode endPos = endWp.crosspoint; + + boolean sameSegmentSearch = ( startNodeId1 == endNodeId1 && startNodeId2 == endNodeId2 ) + || ( startNodeId1 == endNodeId2 && startNodeId2 == endNodeId1 ); + + OsmNode start1 = getStartNode( startNodeId1 ); + OsmNode start2 = getStartNode( startNodeId2 ); + if ( start1 == null || start2 == null ) return null; + + OsmPath startPath1 = getStartPath( start1, start2, startWp, endWp, sameSegmentSearch ); + OsmPath startPath2 = getStartPath( start2, start1, startWp, endWp, sameSegmentSearch ); + + int maxAdjCostFromQueue = 0; + + synchronized( openSet ) + { + openSet.clear(); + if ( startPath1.cost >= 0 ) openSet.add( startPath1 ); + if ( startPath2.cost >= 0 ) openSet.add( startPath2 ); + } + while(!terminated) + { + if ( maxRunningTime > 0 ) + { + long timeout = ( matchPath == null && reducedTimeoutWhenUnmatched ) ? maxRunningTime/3 : maxRunningTime; + if ( System.currentTimeMillis() - startTime > timeout ) + { + throw new IllegalArgumentException( operationName + " timeout after " + (timeout/1000) + " seconds" ); + } + } + OsmPath path = null; + synchronized( openSet ) + { + if ( openSet.size() == 0 ) break; + path = openSet.first(); + openSet.remove( path ); + } + + if ( path.adjustedCost < maxAdjCostFromQueue && airDistanceCostFactor == 0.) + { + throw new RuntimeException( "assertion failed: path.adjustedCost < maxAdjCostFromQueue: " + path.adjustedCost + "<" + maxAdjCostFromQueue ); + } + maxAdjCostFromQueue = path.adjustedCost; + + nodesVisited++; + linksProcessed++; + + OsmLink currentLink = path.getLink(); + OsmNode currentNode = currentLink.targetNode; + OsmNode sourceNode = path.getSourceNode(); + + long currentNodeId = currentNode.getIdFromPos(); + if ( sourceNode != null ) + { + long sourceNodeId = sourceNode.getIdFromPos(); + if ( ( sourceNodeId == endNodeId1 && currentNodeId == endNodeId2 ) + || ( sourceNodeId == endNodeId2 && currentNodeId == endNodeId1 ) ) + { + // track found, compile + logInfo( "found track at cost " + path.cost + " nodesVisited = " + nodesVisited ); + return compileTrack( path, verbose ); + } + } + + // recheck cutoff before doing expensive stuff + int airDistance2 = currentNode.calcDistance( endPos ); + if ( path.cost + airDistance2 > maxTotalCost + 10 ) + { + continue; + } + + if ( !currentNode.wasProcessed ) + { + expandHollowLinkTargets( currentNode, true ); + nodesMap.removeCompletedNodes(); + } + + if ( sourceNode != null ) + { + sourceNode.unlinkLink ( currentLink ); + } + + OsmLink counterLink = null; + for( OsmLink link = currentNode.firstlink; link != null; link = link.next ) + { + OsmNode nextNode = link.targetNode; + + if ( nextNode.isHollow() ) + { + continue; // border node? + } + if ( nextNode.firstlink == null ) + { + continue; // don't care about dead ends + } + if ( nextNode == sourceNode ) + { + counterLink = link; + continue; // border node? + } + + if ( guideTrack != null ) + { + int gidx = path.treedepth + 1; + if ( gidx >= guideTrack.nodes.size() ) + { + continue; + } + OsmPathElement guideNode = guideTrack.nodes.get( gidx ); + if ( nextNode.getILat() != guideNode.getILat() || nextNode.getILon() != guideNode.getILon() ) + { + continue; + } + } + + OsmPath bestPath = null; + + boolean isFinalLink = false; + long targetNodeId = link.targetNode.getIdFromPos(); + if ( currentNodeId == endNodeId1 || currentNodeId == endNodeId2 ) + { + if ( targetNodeId == endNodeId1 || targetNodeId == endNodeId2 ) + { + isFinalLink = true; + } + } + + for( OsmLinkHolder linkHolder = currentLink.firstlinkholder; linkHolder != null; linkHolder = linkHolder.getNextForLink() ) + { + OsmPath otherPath = (OsmPath)linkHolder; + try + { + if ( isFinalLink ) + { + endWp.crosspoint.radius = 1e-5; + routingContext.setWaypoint( endWp.crosspoint, true ); + } + OsmPath testPath = new OsmPath( currentNode, otherPath, link, refTrack, guideTrack != null, routingContext ); + if ( testPath.cost >= 0 && ( bestPath == null || testPath.cost < bestPath.cost ) ) + { + bestPath = testPath; + } + } + finally + { + routingContext.unsetWaypoint(); + } + if ( otherPath != path ) + { + synchronized( openSet ) + { + openSet.remove( otherPath ); + } + } + } + if ( bestPath != null ) + { + int airDistance = isFinalLink ? 0 : nextNode.calcDistance( endPos ); + bestPath.setAirDistanceCostAdjustment( (int)( airDistance * airDistanceCostFactor ) ); + + // check for a match with the cost-cutting-track + if ( costCuttingTrack != null ) + { + OsmPathElement pe = costCuttingTrack.getLink( currentNodeId, targetNodeId ); + if ( pe != null ) + { + int costEstimate = bestPath.cost + + bestPath.elevationCorrection( routingContext ) + + ( costCuttingTrack.cost - pe.cost ); + if ( costEstimate <= maxTotalCost ) + { + matchPath = new OsmPathElement( bestPath ); + } + if ( costEstimate < maxTotalCost ) + { + logInfo( "maxcost " + maxTotalCost + " -> " + costEstimate + " airDistance=" + airDistance ); + maxTotalCost = costEstimate; + } + } + } + + if ( isFinalLink || bestPath.cost + airDistance <= maxTotalCost + 10 ) + { + // add only if this may beat an existing path for that link + OsmLinkHolder dominator = link.firstlinkholder; + while( dominator != null ) + { + if ( bestPath.definitlyWorseThan( (OsmPath)dominator, routingContext ) ) + { + break; + } + dominator = dominator.getNextForLink(); + } + + if ( dominator == null ) + { + bestPath.treedepth = path.treedepth + 1; + link.addLinkHolder( bestPath ); + synchronized( openSet ) + { + openSet.add( bestPath ); + } + } + } + } + } + // if the counterlink does not yet have a path, remove it + if ( counterLink != null && counterLink.firstlinkholder == null ) + { + currentNode.unlinkLink(counterLink); + } + + } + return null; + } + + private void preloadPosition( OsmNode n, int minRingWidth, int minCount ) + { + int c = 0; + int ring = 0; + while( ring <= minRingWidth || ( c < minCount && ring <= 5 ) ) + { + c += preloadRing( n, ring++ ); + } + } + + private int preloadRing( OsmNode n, int ring ) + { + int d = 12500; + int c = 0; + for( int idxLat=-ring; idxLat<=ring; idxLat++ ) + for( int idxLon=-ring; idxLon<=ring; idxLon++ ) + { + int absLat = idxLat < 0 ? -idxLat : idxLat; + int absLon = idxLon < 0 ? -idxLon : idxLon; + int max = absLat > absLon ? absLat : absLon; + if ( max < ring ) continue; + c += nodesCache.loadSegmentFor( n.ilon + d*idxLon , n.ilat +d*idxLat ); + } + return c; + } + + private OsmTrack compileTrack( OsmPath path, boolean verbose ) + { + OsmPathElement element = new OsmPathElement( path ); + + // for final track, cut endnode + if ( guideTrack != null ) element = element.origin; + + OsmTrack track = new OsmTrack(); + track.cost = path.cost; + + int distance = 0; + double ascend = 0; + double ehb = 0.; + + short ele_start = Short.MIN_VALUE; + short ele_end = Short.MIN_VALUE; + + while ( element != null ) + { + track.addNode( element ); + OsmPathElement nextElement = element.origin; + + short ele = element.getSElev(); + if ( ele != Short.MIN_VALUE ) ele_start = ele; + if ( ele_end == Short.MIN_VALUE ) ele_end = ele; + + if ( nextElement != null ) + { + distance += element.calcDistance( nextElement ); + short ele_next = nextElement.getSElev(); + if ( ele_next != Short.MIN_VALUE ) + { + ehb = ehb + (ele - ele_next)/4.; + } + if ( ehb > 10. ) + { + ascend += ehb-10.; + ehb = 10.; + } + else if ( ehb < 0. ) + { + ehb = 0.; + } + } + element = nextElement ; + } + ascend += ehb; + track.distance = distance; + track.ascend = (int)ascend; + track.plainAscend = ( ele_end - ele_start ) / 4; + logInfo( "track-length = " + track.distance ); + logInfo( "filtered ascend = " + track.ascend ); + track.buildMap(); + return track; + } + + private OsmTrack mergeTrack( OsmPathElement match, OsmTrack oldTrack ) + { + + OsmPathElement element = match; + OsmTrack track = new OsmTrack(); + + while ( element != null ) + { + track.addNode( element ); + element = element.origin ; + } + long lastId = 0; + long id1 = match.getIdFromPos(); + long id0 = match.origin == null ? 0 : match.origin.getIdFromPos(); + boolean appending = false; + for( OsmPathElement n : oldTrack.nodes ) + { + if ( appending ) + { + track.nodes.add( n ); + } + + long id = n.getIdFromPos(); + if ( id == id1 && lastId == id0 ) + { + appending = true; + } + lastId = id; + } + + + track.buildMap(); + return track; + } + + public int[] getOpenSet() + { + synchronized( openSet ) + { + return openSet.getExtract(); + } + } + + public boolean isFinished() + { + return finished; + } + + public int getLinksProcessed() + { + return linksProcessed; + } + + public int getDistance() + { + return foundTrack.distance; + } + + public int getAscend() + { + return foundTrack.ascend; + } + + public int getPlainAscend() + { + return foundTrack.plainAscend; + } + + public OsmTrack getFoundTrack() + { + return foundTrack; + } + + public int getAlternativeIndex() + { + return alternativeIndex; + } + + public OsmTrack getFoundRawTrack() + { + return foundRawTrack; + } + + public String getErrorMessage() + { + return errorMessage; + } + + public void terminate() + { + terminated = true; + } +} diff --git a/brouter-core/src/main/java/btools/router/RoutingMessageHandler.java b/brouter-core/src/main/java/btools/router/RoutingMessageHandler.java new file mode 100644 index 0000000..0d12100 --- /dev/null +++ b/brouter-core/src/main/java/btools/router/RoutingMessageHandler.java @@ -0,0 +1,28 @@ +/** + * Container for routig configs + * + * @author ab + */ +package btools.router; + +import btools.expressions.BExpressionReceiver; + +final class RoutingMessageHandler implements BExpressionReceiver +{ + private int ilon; + private int ilat; + + public void setCurrentPos( int lon, int lat) + { + ilon = lon; + ilat = lat; + } + + + @Override + public void expressionWarning( String context, String message ) + { + System.out.println( "message (lon=" + (ilon-180000000) + " lat=" + (ilat-90000000) + + " context " + context + "): " + message ); + } +} diff --git a/brouter-expressions/pom.xml b/brouter-expressions/pom.xml new file mode 100644 index 0000000..a58eda5 --- /dev/null +++ b/brouter-expressions/pom.xml @@ -0,0 +1,13 @@ + + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-expressions + jar + diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpression.java b/brouter-expressions/src/main/java/btools/expressions/BExpression.java new file mode 100644 index 0000000..80bce4f --- /dev/null +++ b/brouter-expressions/src/main/java/btools/expressions/BExpression.java @@ -0,0 +1,173 @@ +package btools.expressions; + + +final class BExpression +{ + private static final int OR_EXP = 10; + private static final int AND_EXP = 11; + private static final int NOT_EXP = 12; + + private static final int ADD_EXP = 20; + private static final int MULTIPLY_EXP = 21; + private static final int MAX_EXP = 22; + + private static final int SWITCH_EXP = 30; + private static final int ASSIGN_EXP = 31; + private static final int LOOKUP_EXP = 32; + private static final int NUMBER_EXP = 33; + private static final int VARIABLE_EXP = 34; + + private static final int DUMPPOS_EXP = 40; + + private int typ; + private BExpression op1; + private BExpression op2; + private BExpression op3; + private float numberValue; + private int variableIdx; + private int lookupNameIdx; + private int lookupValueIdx; + + // Parse the expression and all subexpression + public static BExpression parse( BExpressionContext ctx, int level ) throws Exception + { + String operator = ctx.parseToken(); + if ( operator == null ) + { + if ( level == 0 ) return null; + else throw new IllegalArgumentException( "unexpected end of file" ); + } + + if ( level == 0 ) + { + if ( !"assign".equals( operator ) ) + { + throw new IllegalArgumentException( "operator " + operator + " is invalid on toplevel (only 'assign' allowed)" ); + } + } + + BExpression exp = new BExpression(); + int nops = 3; + + if ( "switch".equals( operator ) ) + { + exp.typ = SWITCH_EXP; + } + else + { + nops = 2; // check binary expressions + + if ( "or".equals( operator ) ) + { + exp.typ = OR_EXP; + } + else if ( "and".equals( operator ) ) + { + exp.typ = AND_EXP; + } + else if ( "multiply".equals( operator ) ) + { + exp.typ = MULTIPLY_EXP; + } + else if ( "add".equals( operator ) ) + { + exp.typ = ADD_EXP; + } + else if ( "max".equals( operator ) ) + { + exp.typ = MAX_EXP; + } + else + { + nops = 1; // check unary expressions + if ( "assign".equals( operator ) ) + { + if ( level > 0 ) throw new IllegalArgumentException( "assign operator within expression" ); + exp.typ = ASSIGN_EXP; + String variable = ctx.parseToken(); + if ( variable == null ) throw new IllegalArgumentException( "unexpected end of file" ); + exp.variableIdx = ctx.getVariableIdx( variable, true ); + if ( exp.variableIdx < ctx.getMinWriteIdx() ) throw new IllegalArgumentException( "cannot assign to readonly variable " + variable ); + } + else if ( "not".equals( operator ) ) + { + exp.typ = NOT_EXP; + } + else if ( "dumppos".equals( operator ) ) + { + exp.typ = DUMPPOS_EXP; + } + else + { + nops = 0; // check elemantary expressions + int idx = operator.indexOf( '=' ); + if ( idx >= 0 ) + { + exp.typ = LOOKUP_EXP; + String name = operator.substring( 0, idx ); + String value = operator.substring( idx+1 ); + + exp.lookupNameIdx = ctx.getLookupNameIdx( name ); + if ( exp.lookupNameIdx < 0 ) + { + throw new IllegalArgumentException( "unknown lookup name: " + name ); + } + exp.lookupValueIdx = ctx.getLookupValueIdx( exp.lookupNameIdx, value ); + if ( exp.lookupValueIdx < 0 ) + { + throw new IllegalArgumentException( "unknown lookup value: " + value ); + } + } + else if ( (idx = ctx.getVariableIdx( operator, false )) >= 0 ) + { + exp.typ = VARIABLE_EXP; + exp.variableIdx = idx; + } + else + { + try + { + exp.numberValue = Float.parseFloat( operator ); + exp.typ = NUMBER_EXP; + } + catch( NumberFormatException nfe ) + { + throw new IllegalArgumentException( "unknown expression: " + operator ); + } + } + } + } + } + // parse operands + if ( nops > 0 ) exp.op1 = BExpression.parse( ctx, level+1 ); + if ( nops > 1 ) exp.op2 = BExpression.parse( ctx, level+1 ); + if ( nops > 2 ) exp.op3 = BExpression.parse( ctx, level+1 ); + return exp; + } + + // Evaluate the expression + public float evaluate( BExpressionContext ctx ) + { + switch( typ ) + { + case OR_EXP: return op1.evaluate(ctx) != 0.f ? 1.f : ( op2.evaluate(ctx) != 0.f ? 1.f : 0.f ); + case AND_EXP: return op1.evaluate(ctx) != 0.f ? ( op2.evaluate(ctx) != 0.f ? 1.f : 0.f ) : 0.f; + case ADD_EXP: return op1.evaluate(ctx) + op2.evaluate(ctx); + case MULTIPLY_EXP: return op1.evaluate(ctx) * op2.evaluate(ctx); + case MAX_EXP: return max( op1.evaluate(ctx), op2.evaluate(ctx) ); + case SWITCH_EXP: return op1.evaluate(ctx) != 0.f ? op2.evaluate(ctx) : op3.evaluate(ctx); + case ASSIGN_EXP: return ctx.assign( variableIdx, op1.evaluate(ctx) ); + case LOOKUP_EXP: return ctx.getLookupMatch( lookupNameIdx, lookupValueIdx ); + case NUMBER_EXP: return numberValue; + case VARIABLE_EXP: return ctx.getVariableValue( variableIdx ); + case NOT_EXP: return op1.evaluate(ctx) == 0.f ? 1.f : 0.f; + case DUMPPOS_EXP: ctx.expressionWarning( "INFO" ); return op1.evaluate(ctx); + default: throw new IllegalArgumentException( "unknown op-code: " + typ ); + } + } + + private float max( float v1, float v2 ) + { + return v1 > v2 ? v1 : v2; + } +} diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java new file mode 100644 index 0000000..7b2f0c3 --- /dev/null +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionContext.java @@ -0,0 +1,620 @@ +// context for simple expression +// context means: +// - the local variables +// - the local variable names +// - the lookup-input variables + +package btools.expressions; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.TreeMap; + +public final class BExpressionContext +{ + private static final String CONTEXT_TAG = "---context:"; + private static final String VERSION_TAG = "---lookupversion:"; + + private String context; + private boolean _inOurContext = false; + private BufferedReader _br = null; + private boolean _readerDone = false; + + private BExpressionReceiver _receiver; + + private Map lookupNumbers = new HashMap(); + private ArrayList lookupValues = new ArrayList(); + private ArrayList lookupNames = new ArrayList(); + private ArrayList lookupHistograms = new ArrayList(); + + private boolean lookupDataFrozen = false; + + private int[] lookupData = new int[0]; + + private Map variableNumbers = new HashMap(); + + private float[] variableData; + + + // hash-cache for function results + private long[] _arrayBitmap; + private int currentHashBucket = -1; + private long currentBitmap = 0; + + public List expressionList; + + private int minWriteIdx; + + // build-in variable indexes for fast access + private int costfactorIdx; + private int turncostIdx; + private int initialcostIdx; + + private float[] _arrayCostfactor; + private float[] _arrayTurncost; + private float[] _arrayInitialcost; + + public float getCostfactor() { return _arrayCostfactor[currentHashBucket]; } + public float getTurncost() { return _arrayTurncost[currentHashBucket]; } + public float getInitialcost() { return _arrayInitialcost[currentHashBucket]; } + + private int linenr; + + public short lookupVersion = -1; + + public BExpressionContext( String context ) + { + this( context, 4096 ); + } + + /** + * Create an Expression-Context for the given node + * + * @param context global, way or node - context of that instance + * @param hashSize size of hashmap for result caching + */ + public BExpressionContext( String context, int hashSize ) + { + this.context = context; + _arrayBitmap = new long[hashSize]; + + _arrayCostfactor = new float[hashSize]; + _arrayTurncost = new float[hashSize]; + _arrayInitialcost = new float[hashSize]; + } + + + /** + * encode lookup data to a 64-bit word + */ + public long encode( int[] ld ) + { + long w = 0; + for( int inum = 0; inum < lookupValues.size(); inum++ ) // loop over lookup names + { + int n = lookupValues.get(inum).length - 1; + int d = ld[inum]; + if ( n == 2 ) { n = 1; d = d == 2 ? 1 : 0; } // 1-bit encoding for booleans + + while( n != 0 ) { n >>= 1; w <<= 1; } + w |= (long)d; + } + return w; + } + + /** + * decode a 64-bit word into a lookup data array + */ + public void decode( int[] ld, long w ) + { + for( int inum = lookupValues.size()-1; inum >= 0; inum-- ) // loop over lookup names + { + int nv = lookupValues.get(inum).length; + int n = nv == 3 ? 1 : nv-1; // 1-bit encoding for booleans + int m = 0; + long ww = w; + while( n != 0 ) { n >>= 1; ww >>= 1; m = m<<1 | 1; } + int d = (int)(w & m); + if ( nv == 3 && d == 1 ) d = 2; // 1-bit encoding for booleans + ld[inum] = d; + w = ww; + } + } + + /** + * much like decode, but just for counting bits + */ + private void countBits() + { + int bits = 0; + for( int inum = lookupValues.size()-1; inum >= 0; inum-- ) // loop over lookup names + { + int nv = lookupValues.get(inum).length; + int n = nv == 3 ? 1 : nv-1; // 1-bit encoding for booleans + while( n != 0 ) { n >>= 1; bits++; } + } +// System.out.println( "context=" + context + ",bits=" + bits + " keys=" + lookupValues.size() ); + if ( bits > 64 ) throw new IllegalArgumentException( "lookup table for context " + context + " exceeds 64 bits!" ); + } + + public String getCsvDescription( long bitmap ) + { + StringBuilder sb = new StringBuilder( 200 ); + decode( lookupData, bitmap ); + for( int inum = 0; inum < lookupValues.size(); inum++ ) // loop over lookup names + { + BExpressionLookupValue[] va = lookupValues.get(inum); + sb.append( '\t' ).append( va[lookupData[inum]].toString() ); + } + return sb.toString(); + } + + public String getCsvHeader() + { + StringBuilder sb = new StringBuilder( 200 ); + for( String name: lookupNames ) + { + sb.append( '\t' ).append( name ); + } + return sb.toString(); + } + + public void readMetaData( File lookupsFile ) + { + try + { + BufferedReader br = new BufferedReader( new FileReader( lookupsFile ) ); + + int parsedLines = 0; + boolean ourContext = false; + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + line = line.trim(); + if ( line.length() == 0 || line.startsWith( "#" ) ) continue; + if ( line.startsWith( CONTEXT_TAG ) ) + { + ourContext = line.substring( CONTEXT_TAG.length() ).equals( context ); + continue; + } + if ( line.startsWith( VERSION_TAG ) ) + { + lookupVersion = Short.parseShort( line.substring( VERSION_TAG.length() ) ); + continue; + } + if ( !ourContext ) continue; + parsedLines++; + StringTokenizer tk = new StringTokenizer( line, " " ); + String name = tk.nextToken(); + String value = tk.nextToken(); + int idx = name.indexOf( ';' ); + if ( idx >= 0 ) name = name.substring( 0, idx ); + BExpressionLookupValue newValue = addLookupValue( name, value, null ); + + // add aliases + while( newValue != null && tk.hasMoreTokens() ) newValue.addAlias( tk.nextToken() ); + } + br.close(); + if ( parsedLines == 0 && !"global".equals(context) ) + { + throw new IllegalArgumentException( lookupsFile.getAbsolutePath() + + " does not contain data for context " + context + " (old version?)" ); + } + + // post-process metadata: + lookupDataFrozen = true; + countBits(); + } + catch( Exception e ) + { + throw new RuntimeException( e ); + } + } + + private void evaluate( int[] lookupData2 ) + { + lookupData = lookupData2; + for( BExpression exp: expressionList) + { + exp.evaluate( this ); + } + } + + private static int[] crctable = { + 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, + 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, + 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, + 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, + 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, + 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, + 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, + 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, + 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, + 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, + 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, + 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, + 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, + 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, + 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, + 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, + 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, + 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, + 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, + 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, + 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, + 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, + 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, + 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, + 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, + 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, + 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, + 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, + 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, + 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, + 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, + 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d, + }; + + + public void evaluate( long bitmap, BExpressionReceiver receiver ) + { + _receiver = receiver; + + if ( currentBitmap != bitmap || currentHashBucket < 0 ) + { + // calc hash bucket from crc + int crc = 0xFFFFFFFF; + long bm = bitmap; + for( int j=0; j<8; j++ ) + { + crc = (crc >>> 8) ^ crctable[(crc ^ (int)bm) & 0xff]; + bm >>= 8; + } + int hashSize = _arrayBitmap.length; + currentHashBucket = (crc & 0xfffffff) % hashSize; + currentBitmap = bitmap; + } + + if ( _arrayBitmap[currentHashBucket] == bitmap ) + { + return; + } + + _arrayBitmap[currentHashBucket] = bitmap; + + decode( lookupData, bitmap ); + evaluate( lookupData ); + + _arrayCostfactor[currentHashBucket] = variableData[costfactorIdx]; + _arrayTurncost[currentHashBucket] = variableData[turncostIdx]; + _arrayInitialcost[currentHashBucket] = variableData[initialcostIdx]; + + _receiver = null; + } + + public void dumpStatistics() + { + TreeMap counts = new TreeMap(); + // first count + for( String name: lookupNumbers.keySet() ) + { + int cnt = 0; + int inum = lookupNumbers.get(name).intValue(); + int[] histo = lookupHistograms.get(inum); +// if ( histo.length == 500 ) continue; + for( int i=2; i 0 ) + { + String key = counts.lastEntry().getKey(); + String name = counts.get(key); + counts.remove( key ); + int inum = lookupNumbers.get(name).intValue(); + BExpressionLookupValue[] values = lookupValues.get(inum); + int[] histo = lookupHistograms.get(inum); + if ( values.length == 1000 ) continue; + String[] svalues = new String[values.length]; + for( int i=0; i=0; i-- ) + { + System.out.println( name + ";" + svalues[i] ); + } + } + } + + /** + * @return a new lookupData array, or null if no metadata defined + */ + public int[] createNewLookupData() + { + if ( lookupDataFrozen ) + { + return new int[lookupValues.size()]; + } + return null; + } + + /** + * add a new lookup-value for the given name to the given lookupData array. + * If no array is given (null value passed), the value is added to + * the context-binded array. In that case, unknown names and values are + * created dynamically. + * + * @return a newly created value element, if any, to optionally add aliases + */ + public BExpressionLookupValue addLookupValue( String name, String value, int[] lookupData2 ) + { + BExpressionLookupValue newValue = null; + Integer num = lookupNumbers.get( name ); + if ( num == null ) + { + if ( lookupData2 != null ) + { + // do not create unknown name for external data array + return newValue; + } + + // unknown name, create + num = new Integer( lookupValues.size() ); + lookupNumbers.put( name, num ); + lookupNames.add( name ); + lookupValues.add( new BExpressionLookupValue[]{ new BExpressionLookupValue( "" ) + , new BExpressionLookupValue( "unknown" ) } ); + lookupHistograms.add( new int[2] ); + int[] ndata = new int[lookupData.length+1]; + System.arraycopy( lookupData, 0, ndata, 0, lookupData.length ); + lookupData = ndata; + } + + // look for that value + int inum = num.intValue(); + BExpressionLookupValue[] values = lookupValues.get( inum ); + int[] histo = lookupHistograms.get( inum ); + int i=0; + for( ; i _parseFile( File file ) throws Exception + { + _br = new BufferedReader( new FileReader( file ) ); + _readerDone = false; + List result = new ArrayList(); + for(;;) + { + BExpression exp = BExpression.parse( this, 0 ); + if ( exp == null ) break; + result.add( exp ); + } + _br.close(); + _br = null; + return result; + } + + + public float getVariableValue( String name, float defaultValue ) + { + Integer num = variableNumbers.get( name ); + return num == null ? defaultValue : getVariableValue( num.intValue() ); + } + + public float getVariableValue( String name ) + { + Integer num = variableNumbers.get( name ); + return num == null ? 0.f : getVariableValue( num.intValue() ); + } + + public float getVariableValue( int variableIdx ) + { + return variableData[variableIdx]; + } + + public int getVariableIdx( String name, boolean create ) + { + Integer num = variableNumbers.get( name ); + if ( num == null ) + { + if ( create ) + { + num = new Integer( variableNumbers.size() ); + variableNumbers.put( name, num ); + } + else + { + return -1; + } + } + return num.intValue(); + } + + public int getMinWriteIdx() + { + return minWriteIdx; + } + + public float getLookupMatch( int nameIdx, int valueIdx ) + { + return lookupData[nameIdx] == valueIdx ? 1.0f : 0.0f; + } + + public int getLookupNameIdx( String name ) + { + Integer num = lookupNumbers.get( name ); + return num == null ? -1 : num.intValue(); + } + + public int getLookupValueIdx( int nameIdx, String value ) + { + BExpressionLookupValue[] values = lookupValues.get( nameIdx ); + for( int i=0; i< values.length; i++ ) + { + if ( values[i].equals( value ) ) return i; + } + return -1; + } + + + public String parseToken() throws Exception + { + for(;;) + { + String token = _parseToken(); + if ( token == null ) return null; + if ( token.startsWith( CONTEXT_TAG ) ) + { + _inOurContext = token.substring( CONTEXT_TAG.length() ).equals( context ); + } + else if ( _inOurContext ) + { + return token; + } + } + } + + + private String _parseToken() throws Exception + { + StringBuilder sb = new StringBuilder(32); + boolean inComment = false; + for(;;) + { + int ic = _readerDone ? -1 : _br.read(); + if ( ic < 0 ) + { + if ( sb.length() == 0 ) return null; + _readerDone = true; + return sb.toString(); + } + char c = (char)ic; + if ( c == '\n' ) linenr++; + + if ( inComment ) + { + if ( c == '\r' || c == '\n' ) inComment = false; + continue; + } + if ( Character.isWhitespace( c ) ) + { + if ( sb.length() > 0 ) return sb.toString(); + else continue; + } + if ( c == '#' && sb.length() == 0 ) inComment = true; + else sb.append( c ); + } + } + + public float assign( int variableIdx, float value ) + { + variableData[variableIdx] = value; + return value; + } + + public void expressionWarning( String message ) + { + _arrayBitmap[currentHashBucket] = 0L; // no caching if warnings + if ( _receiver != null ) _receiver.expressionWarning( context, message ); + } +} diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionLookupValue.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionLookupValue.java new file mode 100644 index 0000000..8043d3c --- /dev/null +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionLookupValue.java @@ -0,0 +1,66 @@ +/** + * A lookup value with optional aliases + * + * toString just gives the primary value, + * equals just compares against primary value + * matches() also compares aliases + * + * @author ab + */ +package btools.expressions; + +import java.util.ArrayList; + +final class BExpressionLookupValue +{ + String value; + ArrayList aliases; + + @Override + public String toString() + { + return value; + } + + public BExpressionLookupValue( String value ) + { + this.value = value; + } + + public void addAlias( String alias ) + { + if ( aliases == null ) aliases = new ArrayList(); + aliases.add( alias ); + } + + @Override + public boolean equals( Object o ) + { + if ( o instanceof String ) + { + String v = (String)o; + return value.equals( v ); + } + if ( o instanceof BExpressionLookupValue ) + { + BExpressionLookupValue v = (BExpressionLookupValue)o; + + return value.equals( v.value ); + } + return false; + } + + public boolean matches( String s ) + { + if ( value.equals( s ) ) return true; + if ( aliases != null ) + { + for( String alias : aliases ) + { + if ( alias.equals( s ) ) return true; + } + } + return false; + } + +} diff --git a/brouter-expressions/src/main/java/btools/expressions/BExpressionReceiver.java b/brouter-expressions/src/main/java/btools/expressions/BExpressionReceiver.java new file mode 100644 index 0000000..57b44f6 --- /dev/null +++ b/brouter-expressions/src/main/java/btools/expressions/BExpressionReceiver.java @@ -0,0 +1,7 @@ +package btools.expressions; + + +public interface BExpressionReceiver +{ + public void expressionWarning( String context, String message ); +} diff --git a/brouter-map-creator/pom.xml b/brouter-map-creator/pom.xml new file mode 100644 index 0000000..3b93909 --- /dev/null +++ b/brouter-map-creator/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-map-creator + jar + + + + org.btools + brouter-util + ${project.version} + + + org.btools + brouter-expressions + ${project.version} + + + org.btools + brouter-mapaccess + ${project.version} + + + junit + junit + + + diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/MapCreatorBase.java b/brouter-map-creator/src/main/java/btools/mapcreator/MapCreatorBase.java new file mode 100644 index 0000000..70e4052 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/MapCreatorBase.java @@ -0,0 +1,166 @@ +/** + * common base class for the map-filters + * + * @author ab + */ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.BufferedOutputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +public abstract class MapCreatorBase implements WayListener, NodeListener, RelationListener +{ + private DataOutputStream[] tileOutStreams; + protected File outTileDir; + + protected HashMap tags; + + public void putTag( String key, String value ) + { + if ( tags == null ) tags = new HashMap(); + tags.put( key, value ); + } + + public String getTag( String key ) + { + return tags == null ? null : tags.get( key ); + } + + public HashMap getTagsOrNull() + { + return tags; + } + + public void setTags( HashMap tags ) + { + this.tags = tags; + } + + protected static long readId( DataInputStream is) throws IOException + { + int offset = is.readByte(); + if ( offset == 32 ) return -1; + long i = is.readInt(); + i = i << 5; + return i | offset; + } + + protected static void writeId( DataOutputStream o, long id ) throws IOException + { + if ( id == -1 ) + { + o.writeByte( 32 ); + return; + } + int offset = (int)( id & 0x1f ); + int i = (int)( id >> 5 ); + o.writeByte( offset ); + o.writeInt( i ); + } + + + protected static File[] sortBySizeAsc( File[] files ) + { + int n = files.length; + long[] sizes = new long[n]; + File[] sorted = new File[n]; + for( int i=0; i " ); + return; + } + new NodeCutter().process( new File( args[0] ), new File( args[1] ) ); + } + + public void process( File nodeTilesIn, File nodeTilesOut ) throws Exception + { + this.outTileDir = nodeTilesOut; + + new NodeIterator( this, true ).processDir( nodeTilesIn, ".tlf" ); + } + + @Override + public void nodeFileStart( File nodefile ) throws Exception + { + lonoffset = -1; + latoffset = -1; + } + + @Override + public void nextNode( NodeData n ) throws Exception + { + n.writeTo( getOutStreamForTile( getTileIndex( n.ilon, n.ilat ) ) ); + } + + @Override + public void nodeFileEnd( File nodeFile ) throws Exception + { + closeTileOutStreams(); + } + + private int getTileIndex( int ilon, int ilat ) + { + int lonoff = (ilon / 45000000 ) * 45; + int latoff = (ilat / 30000000 ) * 30; + if ( lonoffset == -1 ) lonoffset = lonoff; + if ( latoffset == -1 ) latoffset = latoff; + if ( lonoff != lonoffset || latoff != latoffset ) + throw new IllegalArgumentException( "inconsistent node: " + ilon + " " + ilat ); + + int lon = (ilon / 5000000) % 9; + int lat = (ilat / 5000000) % 6; + if ( lon < 0 || lon > 8 || lat < 0 || lat > 5 ) throw new IllegalArgumentException( "illegal pos: " + ilon + "," + ilat ); + return lon*6 + lat; + } + + + protected String getNameForTile( int tileIndex ) + { + int lon = (tileIndex / 6 ) * 5 + lonoffset - 180; + int lat = (tileIndex % 6 ) * 5 + latoffset - 90; + String slon = lon < 0 ? "W" + (-lon) : "E" + lon; + String slat = lat < 0 ? "S" + (-lat) : "N" + lat; + return slon + "_" + slat + ".n5d"; + } + +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/NodeData.java b/brouter-map-creator/src/main/java/btools/mapcreator/NodeData.java new file mode 100644 index 0000000..5b0b388 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/NodeData.java @@ -0,0 +1,55 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * Container for node data on the preprocessor level + * + * @author ab + */ +public class NodeData extends MapCreatorBase +{ + public long nid; + public int ilon; + public int ilat; + public long description; + public short selev = Short.MIN_VALUE; + + public NodeData( long id, double lon, double lat ) + { + nid = id; + ilat = (int)( ( lat + 90. )*1000000. + 0.5); + ilon = (int)( ( lon + 180. )*1000000. + 0.5); + } + + public NodeData( DataInputStream dis ) throws Exception + { + nid = readId( dis ); + ilon = dis.readInt(); + ilat = dis.readInt(); + int mode = dis.readByte(); + if ( ( mode & 1 ) != 0 ) description = dis.readLong(); + if ( ( mode & 2 ) != 0 ) selev = dis.readShort(); + } + + public void writeTo( DataOutputStream dos ) throws Exception + { + writeId( dos, nid ); + dos.writeInt( ilon ); + dos.writeInt( ilat ); + int mode = ( description == 0L ? 0 : 1 ) | ( selev == Short.MIN_VALUE ? 0 : 2 ); + dos.writeByte( (byte)mode ); + if ( ( mode & 1 ) != 0 ) dos.writeLong( description ); + if ( ( mode & 2 ) != 0 ) dos.writeShort( selev ); + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/NodeFilter.java b/brouter-map-creator/src/main/java/btools/mapcreator/NodeFilter.java new file mode 100644 index 0000000..f04761d --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/NodeFilter.java @@ -0,0 +1,86 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * NodeFilter does 1 step in map-processing: + * + * - filters out unused nodes according to the way file + * + * @author ab + */ +public class NodeFilter extends MapCreatorBase +{ + private DataOutputStream nodesOutStream; + private File nodeTilesOut; + protected DenseLongMap nodebitmap; + + public static void main(String[] args) throws Exception + { + System.out.println("*** NodeFilter: Filter way related nodes"); + if (args.length != 3) + { + System.out.println("usage: java NodeFilter " ); + return; + } + + new NodeFilter().process( new File( args[0] ), new File( args[1] ), new File( args[2] ) ); + } + + public void process( File nodeTilesIn, File wayFileIn, File nodeTilesOut ) throws Exception + { + this.nodeTilesOut = nodeTilesOut; + + // read the wayfile into a bitmap of used nodes + nodebitmap = Boolean.getBoolean( "useDenseMaps" ) ? new DenseLongMap( 1 ) : new TinyDenseLongMap(); + new WayIterator( this, false ).processFile( wayFileIn ); + + // finally filter all node files + new NodeIterator( this, true ).processDir( nodeTilesIn, ".tls" ); + } + + @Override + public void nextWay( WayData data ) throws Exception + { + int nnodes = data.nodes.size(); + for (int i=0; i bit set, -1 -> unset + { + n.writeTo( nodesOutStream ); + } + } + + @Override + public void nodeFileEnd( File nodeFile ) throws Exception + { + nodesOutStream.close(); + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/NodeIterator.java b/brouter-map-creator/src/main/java/btools/mapcreator/NodeIterator.java new file mode 100644 index 0000000..e251755 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/NodeIterator.java @@ -0,0 +1,76 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * Iterate over a singe nodefile or a directory + * of nodetiles and feed the nodes to the callback listener + * + * @author ab + */ +public class NodeIterator extends MapCreatorBase +{ + private NodeListener listener; + private boolean delete; + + public NodeIterator( NodeListener nodeListener, boolean deleteAfterReading ) + { + listener = nodeListener; + delete = deleteAfterReading; + } + + public void processDir( File indir, String inSuffix ) throws Exception + { + if ( !indir.isDirectory() ) + { + throw new IllegalArgumentException( "not a directory: " + indir ); + } + + File[] af = sortBySizeAsc( indir.listFiles() ); + for( int i=0; i | java OsmCutter "); + System.out.println("or : java OsmCutter "); + return; + } + + new OsmCutter().process( + new File( args[0] ) + , new File( args[1] ) + , new File( args[2] ) + , new File( args[3] ) + , args.length > 4 ? new File( args[4] ) : null ); + } + + private BExpressionContext _expctxWay; + private BExpressionContext _expctxNode; + + public void process (File lookupFile, File outTileDir, File wayFile, File relFile, File mapFile) throws Exception + { + if ( !lookupFile.exists() ) + { + throw new IllegalArgumentException( "lookup-file: " + lookupFile + " does not exist" ); + } + + _expctxWay = new BExpressionContext("way"); + _expctxWay.readMetaData( lookupFile ); + + _expctxNode = new BExpressionContext("node"); + _expctxNode.readMetaData( lookupFile ); + + this.outTileDir = outTileDir; + if ( !outTileDir.isDirectory() ) throw new RuntimeException( "out tile directory " + outTileDir + " does not exist" ); + + wayDos = new DataOutputStream( new BufferedOutputStream( new FileOutputStream( wayFile ) ) ); + cyclewayDos = new DataOutputStream( new BufferedOutputStream( new FileOutputStream( relFile ) ) ); + + // read the osm map into memory + long t0 = System.currentTimeMillis(); + new OsmParser().readMap( mapFile, this, this, this ); + long t1 = System.currentTimeMillis(); + + System.out.println( "parsing time (ms) =" + (t1-t0) ); + + // close all files + closeTileOutStreams(); + wayDos.close(); + cyclewayDos.close(); + +/* System.out.println( "-------- way-statistics -------- " ); + _expctxWay.dumpStatistics(); + System.out.println( "-------- node-statistics -------- " ); + _expctxNode.dumpStatistics(); +*/ + System.out.println( statsLine() ); + } + + private void checkStats() + { + if ( (++recordCnt % 100000) == 0 ) System.out.println( statsLine() ); + } + + private String statsLine() + { + return "records read: " + recordCnt + " nodes=" + nodesParsed + " ways=" + waysParsed + " rels=" + relsParsed + " changesets=" + changesetsParsed; + } + + + @Override + public void nextNode( NodeData n ) throws Exception + { + nodesParsed++; + checkStats(); + + if ( n.getTagsOrNull() != null ) + { + int[] lookupData = _expctxNode.createNewLookupData(); + for( String key : n.getTagsOrNull().keySet() ) + { + String value = n.getTag( key ); + _expctxNode.addLookupValue( key, value, lookupData ); + } + n.description = _expctxNode.encode(lookupData); + } + // write node to file + int tileIndex = getTileIndex( n.ilon, n.ilat ); + if ( tileIndex >= 0 ) + { + DataOutputStream dos = getOutStreamForTile( tileIndex ); + n.writeTo( dos ); + } + } + + + @Override + public void nextWay( WayData w ) throws Exception + { + waysParsed++; + checkStats(); + + // filter out non-highway ways + if ( w.getTag( "highway" ) == null ) + { + // ... but eventually fake a ferry tag + if ( "ferry".equals( w.getTag( "route" ) ) ) + { + w.putTag( "highway", "ferry" ); + } + else + { + return; + } + } + + // encode tags + if ( w.getTagsOrNull() != null ) + { + int[] lookupData = _expctxWay.createNewLookupData(); + for( String key : w.getTagsOrNull().keySet() ) + { + String value = w.getTag( key ); + _expctxWay.addLookupValue( key, value, lookupData ); + } + w.description = _expctxWay.encode(lookupData); + } + + w.writeTo( wayDos ); + } + + @Override + public void nextRelation( RelationData r ) throws Exception + { + relsParsed++; + checkStats(); + + // filter out non-cycle relations + if ( ! "bicycle".equals( r.getTag( "route" ) ) ) + { + return; + } + + for ( int i=0; i 7 || lat < 0 || lat > 5 ) + { + System.out.println( "warning: ignoring illegal pos: " + ilon + "," + ilat ); + return -1; + } + return lon*6 + lat; + } + + protected String getNameForTile( int tileIndex ) + { + int lon = (tileIndex / 6 ) * 45 - 180; + int lat = (tileIndex % 6 ) * 30 - 90; + String slon = lon < 0 ? "W" + (-lon) : "E" + lon; + String slat = lat < 0 ? "S" + (-lat) : "N" + lat; + return slon + "_" + slat + ".tls"; + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmLinkP.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmLinkP.java new file mode 100644 index 0000000..141f43c --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmLinkP.java @@ -0,0 +1,29 @@ +/** + * Container for link between two Osm nodes (pre-pocessor version) + * + * @author ab + */ +package btools.mapcreator; + + +public final class OsmLinkP +{ + /** + * The description bitmap is mainly the way description + * used to calculate the costfactor + */ + public long descriptionBitmap; + + /** + * The target is either the next link or the target node + */ + public OsmNodeP targetNode; + + public OsmLinkP next; + + + public boolean counterLinkWritten( ) + { + return descriptionBitmap == 0L; + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java new file mode 100644 index 0000000..3cd27aa --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodeP.java @@ -0,0 +1,243 @@ +/** + * Container for an osm node (pre-pocessor version) + * + * @author ab + */ +package btools.mapcreator; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class OsmNodeP implements Comparable +{ + public static final int EXTERNAL_BITMASK = 0x80; + public static final int FIRSTFORWAY_BITMASK = 0x40; + public static final int TRANSFERNODE_BITMASK = 0x20; + public static final int WRITEDESC_BITMASK = 0x10; + public static final int SKIPDETAILS_BITMASK = 0x08; + public static final int NODEDESC_BITMASK = 0x04; + + /** + * The latitude + */ + public int ilat; + + /** + * The longitude + */ + public int ilon; + + + /** + * The links to other nodes + */ + public OsmLinkP firstlink = null; + + + /** + * The elevation + */ + public short selev; + + public boolean isBorder = false; + + public byte wayAndBits = -1; // use for bridge/tunnel logic + + + // interface OsmPos + public int getILat() + { + return ilat; + } + + public int getILon() + { + return ilon; + } + + public short getSElev() + { + // if all bridge or all tunnel, elevation=no-data + return (wayAndBits & 24) == 0 ? selev : Short.MIN_VALUE; + } + + public double getElev() + { + return selev / 4.; + } + + + + public void addLink( OsmLinkP link ) + { + if ( firstlink != null ) link.next = firstlink; + firstlink = link; + } + + public long getNodeDecsription() + { + return 0L; + } + + public void writeNodeData( DataOutputStream os ) throws IOException + { + int lonIdx = ilon/62500; + int latIdx = ilat/62500; + + // buffer the body to first calc size + ByteArrayOutputStream bos = new ByteArrayOutputStream( ); + DataOutputStream os2 = new DataOutputStream( bos ); + + os2.writeShort( getSElev() ); + + int nlinks = 0; + + // hack: write node-desc as link tag (copy cycleway-bits) + long nodeDescription = getNodeDecsription(); + + for( OsmLinkP link0 = firstlink; link0 != null; link0 = link0.next ) + { + nlinks++; + OsmLinkP link = link0; + OsmNodeP origin = this; + int skipDetailBit = link0.counterLinkWritten() ? SKIPDETAILS_BITMASK : 0; + + // first pass just to see if that link is consistent + while( link != null ) + { + OsmNodeP target = link.targetNode; + if ( !target.isTransferNode() ) + { + break; + } + // next link is the one (of two), does does'nt point back + for( link = target.firstlink; link != null; link = link.next ) + { + if ( link.targetNode != origin ) break; + } + origin = target; + } + if ( link == null ) continue; // dead end + + if ( skipDetailBit == 0) + { + link = link0; + } + origin = this; + long lastDescription = 0; + while( link != null ) + { + OsmNodeP target = link.targetNode; + int tranferbit = target.isTransferNode() ? TRANSFERNODE_BITMASK : 0; + int writedescbit = link.descriptionBitmap != lastDescription ? WRITEDESC_BITMASK : 0; + int nodedescbit = nodeDescription != 0L ? NODEDESC_BITMASK : 0; + + if ( skipDetailBit != 0 ) + { + writedescbit = 0; + } + int targetLonIdx = target.ilon/62500; + int targetLatIdx = target.ilat/62500; + + if ( targetLonIdx == lonIdx && targetLatIdx == latIdx ) + { + // reduced position for internal target + os2.writeByte( tranferbit | writedescbit | nodedescbit | skipDetailBit ); + os2.writeShort( (short)(target.ilon - lonIdx*62500 - 31250) ); + os2.writeShort( (short)(target.ilat - latIdx*62500 - 31250) ); + } + else + { + // full position for external target + os2.writeByte( tranferbit | writedescbit | nodedescbit | skipDetailBit | EXTERNAL_BITMASK ); + os2.writeInt( target.ilon ); + os2.writeInt( target.ilat ); + } + if ( writedescbit != 0 ) + { + os2.writeLong( link.descriptionBitmap ); + } + if ( nodedescbit != 0 ) + { + os2.writeLong( nodeDescription ); + nodeDescription = 0L; + } + lastDescription = link.descriptionBitmap; + + if ( tranferbit == 0) + { + target.markLinkWritten( origin ); + break; + } + os2.writeShort( target.getSElev() ); + // next link is the one (of two), does does'nt point back + for( link = target.firstlink; link != null; link = link.next ) + { + if ( link.targetNode != origin ) break; + } + if ( link == null ) throw new RuntimeException( "follow-up link not found for transfer-node!" ); + origin = target; + } + } + + // calculate the body size + int bodySize = bos.size(); + + os.writeShort( (short)(ilon - lonIdx*62500 - 31250) ); + os.writeShort( (short)(ilat - latIdx*62500 - 31250) ); + os.writeInt( bodySize ); + bos.writeTo( os ); + } + + public String toString2() + { + return (ilon-180000000) + "_" + (ilat-90000000) + "_" + (selev/4); + } + + public long getIdFromPos() + { + return ((long)ilon)<<32 | ilat; + } + + public boolean isTransferNode() + { + return (!isBorder) && _linkCnt() == 2; + } + + private int _linkCnt() + { + int cnt = 0; + + for( OsmLinkP link = firstlink; link != null; link = link.next ) + { + cnt++; + } + return cnt; + } + + // mark the link to the given node as written, + // don't want to write the counter-direction + // in full details + public void markLinkWritten( OsmNodeP t ) + { + for( OsmLinkP link = firstlink; link != null; link = link.next ) + { + if ( link.targetNode == t) link.descriptionBitmap = 0L; + } + } + + /** + * Compares two OsmNodes for position ordering. + * + * @return -1,0,1 depending an comparson result + */ + public int compareTo( OsmNodeP n ) + { + long id1 = getIdFromPos(); + long id2 = n.getIdFromPos(); + if ( id1 < id2 ) return -1; + if ( id1 > id2 ) return 1; + return 0; + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodePT.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodePT.java new file mode 100644 index 0000000..41316b2 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmNodePT.java @@ -0,0 +1,39 @@ +/** + * Container for an osm node with tags (pre-pocessor version) + * + * @author ab + */ +package btools.mapcreator; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class OsmNodePT extends OsmNodeP +{ + public long descriptionBits; + + public byte wayOrBits = 0; // used to propagate bike networks to nodes + + public OsmNodePT() + { + } + + public OsmNodePT( long descriptionBits ) + { + this.descriptionBits = descriptionBits; + } + + @Override + public long getNodeDecsription() + { + return descriptionBits | (long)( (wayOrBits & 6) >> 1 ); + } + + @Override + public boolean isTransferNode() + { + return false; // always have descriptionBits so never transfernode + } + +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/OsmParser.java b/brouter-map-creator/src/main/java/btools/mapcreator/OsmParser.java new file mode 100644 index 0000000..7731cad --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/OsmParser.java @@ -0,0 +1,240 @@ +package btools.mapcreator; + +import java.io.BufferedOutputStream; +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.zip.GZIPInputStream; + +/** + * Parser for OSM data + * + * @author ab + */ +public class OsmParser extends MapCreatorBase +{ + private BufferedReader _br; + + private NodeListener nListener; + private WayListener wListener; + private RelationListener rListener; + + public void readMap( File mapFile, + NodeListener nListener, + WayListener wListener, + RelationListener rListener ) throws Exception + { + + this.nListener = nListener; + this.wListener = wListener; + this.rListener = rListener; + + if ( mapFile == null ) + { + _br = new BufferedReader(new InputStreamReader(System.in)); + } + else + { + if ( mapFile.getName().endsWith( ".gz" ) ) + { + _br = new BufferedReader(new InputStreamReader( new GZIPInputStream( new FileInputStream( mapFile ) ) ) ); + } + else + { + _br = new BufferedReader(new InputStreamReader( new FileInputStream( mapFile ) ) ); + } + } + + for(;;) + { + String line = _br.readLine(); + if ( line == null ) break; + + if ( checkNode( line ) ) continue; + if ( checkWay( line ) ) continue; + if ( checkRelation( line ) ) continue; + if ( checkChangeset( line ) ) continue; + } + + if ( mapFile != null ) + { + _br.close(); + } + } + + + private boolean checkNode( String line ) throws Exception + { + int idx0 = line.indexOf( "" ) ) + { + // read additional tags + for(;;) + { + String l2 = _br.readLine(); + if ( l2 == null ) return false; + + int i2; + if ( (i2 = l2.indexOf( "= 0 ) + { // property-tag + i2 += 8; + int ri2 = l2.indexOf( '"', i2 ); + String key = l2.substring( i2, ri2 ); + i2 = l2.indexOf( " v=\"", ri2 ); + if ( i2 >= 0 ) + { + i2 += 4; + int ri3 = l2.indexOf( '"', i2 ); + String value = l2.substring( i2, ri3 ); + + n.putTag( key, value ); + } + } + else if ( l2.indexOf( "" ) >= 0 ) + { // end-tag + break; + } + } + } + nListener.nextNode( n ); + return true; + } + + + private boolean checkWay( String line ) throws Exception + { + int idx0 = line.indexOf( "= 0 ) + { // node reference + i2 += 9; + int ri2 = l2.indexOf( '"', i2 ); + long nid = Long.parseLong( l2.substring( i2, ri2 ) ); + w.nodes.add( nid ); + } + else if ( (i2 = l2.indexOf( "= 0 ) + { // property-tag + i2 += 8; + int ri2 = l2.indexOf( '"', i2 ); + String key = l2.substring( i2, ri2 ); + i2 = l2.indexOf( " v=\"", ri2 ); + if ( i2 >= 0 ) + { + i2 += 4; + int ri3 = l2.indexOf( '"', i2 ); + String value = l2.substring( i2, ri3 ); + w.putTag( key, value ); + } + } + else if ( l2.indexOf( "" ) >= 0 ) + { // end-tag + break; + } + } + wListener.nextWay( w ); + return true; + } + + private boolean checkChangeset( String line ) throws Exception + { + int idx0 = line.indexOf( "" ) ) + { + int loopcheck = 0; + for(;;) + { + String l2 = _br.readLine(); + if ( l2.indexOf("") >= 0 || ++loopcheck > 10000 ) break; + } + } + return true; + } + + private boolean checkRelation( String line ) throws Exception + { + int idx0 = line.indexOf( "= 0 ) + { // node reference + i2 += 24; + int ri2 = l2.indexOf( '"', i2 ); + long wid = Long.parseLong( l2.substring( i2, ri2 ) ); + r.ways.add( wid ); + } + else if ( (i2 = l2.indexOf( "= 0 ) + { // property-tag + i2 += 8; + int ri2 = l2.indexOf( '"', i2 ); + String key = l2.substring( i2, ri2 ); + i2 = l2.indexOf( " v=\"", ri2 ); + if ( i2 >= 0 ) + { + i2 += 4; + int ri3 = l2.indexOf( '"', i2 ); + String value = l2.substring( i2, ri3 ); + r.putTag( key, value ); + } + } + else if ( l2.indexOf( "" ) >= 0 ) + { // end-tag + break; + } + } + rListener.nextRelation( r ); + return true; + } + +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java new file mode 100644 index 0000000..16963cc --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/PosUnifier.java @@ -0,0 +1,203 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.CompactLongSet; +import btools.util.CompactLongMap; +import btools.util.FrozenLongSet; +import btools.util.FrozenLongMap; + +/** + * PosUnifier does 3 steps in map-processing: + * + * - unify positions + * - add srtm elevation data + * - make a bordernodes file containing net data + * from the bordernids-file just containing ids + * + * @author ab + */ +public class PosUnifier extends MapCreatorBase +{ + private DataOutputStream nodesOutStream; + private DataOutputStream borderNodesOut; + private File nodeTilesOut; + private CompactLongSet positionSet; + + private HashMap srtmmap ; + private int lastStrmLonIdx; + private int lastStrmLatIdx; + private SrtmData lastSrtmData; + private String srtmdir; + + private int totalLatSteps = 0; + private int totalLonSteps = 0; + + private CompactLongSet borderNids; + + + public static void main(String[] args) throws Exception + { + System.out.println("*** PosUnifier: Unify position values and enhance elevation"); + if (args.length != 5) + { + System.out.println("usage: java PosUnifier " ); + return; + } + new PosUnifier().process( new File( args[0] ), new File( args[1] ), new File( args[2] ), new File( args[3] ), args[4] ); + } + + public void process( File nodeTilesIn, File nodeTilesOut, File bordernidsinfile, File bordernodesoutfile, String srtmdir ) throws Exception + { + this.nodeTilesOut = nodeTilesOut; + this.srtmdir = srtmdir; + + // read border nids set + DataInputStream dis = createInStream( bordernidsinfile ); + borderNids = new CompactLongSet(); + try + { + for(;;) + { + long nid = readId( dis ); + if ( !borderNids.contains( nid ) ) borderNids.fastAdd( nid ); + } + } + catch( EOFException eof ) + { + dis.close(); + } + borderNids = new FrozenLongSet( borderNids ); + + // process all files + borderNodesOut = createOutStream( bordernodesoutfile ); + new NodeIterator( this, true ).processDir( nodeTilesIn, ".n5d" ); + borderNodesOut.close(); + } + + @Override + public void nodeFileStart( File nodefile ) throws Exception + { + resetSrtm(); + + nodesOutStream = createOutStream( fileFromTemplate( nodefile, nodeTilesOut, "u5d" ) ); + + positionSet = new CompactLongSet(); + } + + @Override + public void nextNode( NodeData n ) throws Exception + { + SrtmData srtm = srtmForNode( n.ilon, n.ilat ); + n.selev = srtm == null ? Short.MIN_VALUE : srtm.getElevation( n.ilon, n.ilat); + + findUniquePos( n ); + + n.writeTo( nodesOutStream ); + if ( borderNids.contains( n.nid ) ) + { + n.writeTo( borderNodesOut ); + } + } + + @Override + public void nodeFileEnd( File nodeFile ) throws Exception + { + nodesOutStream.close(); + } + + private void findUniquePos( NodeData n ) + { + // fix the position for uniqueness + int lonmod = n.ilon % 1000000; + int londelta = lonmod < 500000 ? 1 : -1; + int latmod = n.ilat % 1000000; + int latdelta = latmod < 500000 ? 1 : -1; + for(int latsteps = 0; latsteps < 100; latsteps++) + { + for(int lonsteps = 0; lonsteps <= latsteps; lonsteps++) + { + int lon = n.ilon + lonsteps*londelta; + int lat = n.ilat + latsteps*latdelta; + long pid = ((long)lon)<<32 | lat; // id from position + if ( !positionSet.contains( pid ) ) + { + totalLonSteps += lonsteps; + totalLatSteps += latsteps; + positionSet.fastAdd( pid ); + n.ilon = lon; + n.ilat = lat; + return; + } + } + } + System.out.println( "*** WARNING: cannot unify position for: " + n.ilon + " " + n.ilat ); + } + + + /** + * get the srtm data set for a position + * srtm coords are srtm__ + * where srtmLon = 180 + lon, srtmLat = 60 - lat + */ + private SrtmData srtmForNode( int ilon, int ilat ) throws Exception + { + int srtmLonIdx = (ilon+5000000)/5000000; + int srtmLatIdx = (154999999-ilat)/5000000; + + if ( srtmLatIdx < 1 || srtmLatIdx > 24 || srtmLonIdx < 1 || srtmLonIdx > 72 ) + { + return null; + } + if ( srtmLonIdx == lastStrmLonIdx && srtmLatIdx == lastStrmLatIdx ) + { + return lastSrtmData; + } + lastStrmLonIdx = srtmLonIdx; + lastStrmLatIdx = srtmLatIdx; + + StringBuilder sb = new StringBuilder( 16 ); + sb.append( "srtm_" ); + sb.append( (char)('0' + srtmLonIdx/10 ) ).append( (char)('0' + srtmLonIdx%10 ) ).append( '_' ); + sb.append( (char)('0' + srtmLatIdx/10 ) ).append( (char)('0' + srtmLatIdx%10 ) ).append( ".zip" ); + String filename = sb.toString(); + + + lastSrtmData = srtmmap.get( filename ); + if ( lastSrtmData == null && !srtmmap.containsKey( filename ) ) + { + File f = new File( new File( srtmdir ), filename ); + System.out.println( "reading: " + f + " ilon=" + ilon + " ilat=" + ilat ); + if ( f.exists() ) + { + try + { + lastSrtmData = new SrtmData( f ); + } + catch( Exception e ) + { + System.out.println( "**** ERROR reading " + f + " ****" ); + } + } + srtmmap.put( filename, lastSrtmData ); + } + return lastSrtmData; + } + + private void resetSrtm() + { + srtmmap = new HashMap(); + lastStrmLonIdx = -1; + lastStrmLatIdx = -1; + lastSrtmData = null; + } + +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/RelationData.java b/brouter-map-creator/src/main/java/btools/mapcreator/RelationData.java new file mode 100644 index 0000000..864204f --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/RelationData.java @@ -0,0 +1,37 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * Container for relation data on the preprocessor level + * + * @author ab + */ +public class RelationData extends MapCreatorBase +{ + public long rid; + public long description; + public LongList ways; + + public RelationData( long id ) + { + rid = id; + ways = new LongList( 16 ); + } + + public RelationData( long id, LongList ways ) + { + rid = id; + this.ways = ways; + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/RelationListener.java b/brouter-map-creator/src/main/java/btools/mapcreator/RelationListener.java new file mode 100644 index 0000000..2789714 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/RelationListener.java @@ -0,0 +1,13 @@ +package btools.mapcreator; + +import java.io.File; + +/** + * Callbacklistener for Relations + * + * @author ab + */ +public interface RelationListener +{ + void nextRelation( RelationData data ) throws Exception; +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java b/brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java new file mode 100644 index 0000000..fca1b3a --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/SrtmData.java @@ -0,0 +1,174 @@ +/** + * This is a wrapper for a 5*5 degree srtm file in ascii/zip-format + * + * - filter out unused nodes according to the way file + * - enhance with SRTM elevation data + * - split further in smaller (5*5 degree) tiles + * + * @author ab + */ +package btools.mapcreator; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + + +public class SrtmData +{ + public int ncols; + public int nrows; + public double xllcorner; + public double yllcorner; + public double cellsize; + public int nodata_value; + public short[] eval_array; + + private double minlon; + private double maxlon; + private double minlat; + private double maxlat; + + public void init() + { + minlon = xllcorner; + maxlon = minlon + cellsize*ncols; + minlat = yllcorner; + maxlat = minlat + cellsize*nrows; + } + + private boolean missingData = false; + + public short getElevation( int ilon, int ilat ) + { + double lon = ilon / 1000000. - 180.; + double lat = ilat / 1000000. - 90.; + + double dcol = (lon - minlon)/cellsize -0.5; + double drow = (lat - minlat)/cellsize -0.5; + int row = (int)drow; + int col = (int)dcol; + if ( col < 0 ) col = 0; + if ( col >= ncols-1 ) col = ncols - 2; + if ( row < 0 ) row = 0; + if ( row >= nrows-1 ) row = nrows - 2; + double wrow = drow-row; + double wcol = dcol-col; + missingData = false; + double eval = (1.-wrow)*(1.-wcol)*get(row ,col ) + + ( wrow)*(1.-wcol)*get(row+1,col ) + + (1.-wrow)*( wcol)*get(row ,col+1) + + ( wrow)*( wcol)*get(row+1,col+1); + return missingData ? Short.MIN_VALUE : (short)(eval); + } + + private short get( int r, int c ) + { + short e = eval_array[r*ncols + c ]; + if ( e == Short.MIN_VALUE ) missingData = true; + return e; + } + + public SrtmData( File file ) throws Exception + { + ZipInputStream zis = new ZipInputStream( new FileInputStream( file ) ); + try + { + for(;;) + { + ZipEntry ze = zis.getNextEntry(); + if ( ze.getName().endsWith( ".asc" ) ) + { + readFromStream( zis ); + +/* // test + int[] ca = new int[]{ 50477121, 8051915, // 181 + 50477742, 8047408, // 154 + 50477189, 8047308, // 159 + }; + for( int i=0; i " ); + + return; + } + new WayCutter().process( new File( args[0] ), new File( args[1] ), new File( args[2] ), new File( args[3] ) ); + } + + public void process( File nodeTilesIn, File wayFileIn, File wayTilesOut, File relationFileIn ) throws Exception + { + this.outTileDir = wayTilesOut; + + // *** read the relation file into a set (currently cycleway processing only) + cyclewayset = new CompactLongSet(); + DataInputStream dis = createInStream( relationFileIn ); + try + { + for(;;) + { + long wid = readId( dis ); + if ( !cyclewayset.contains( wid ) ) cyclewayset.add( wid ); + } + } + catch( EOFException eof ) + { + dis.close(); + } + System.out.println( "marked cycleways: " + cyclewayset.size() ); + + + // *** read all nodes into tileIndexMap + tileIndexMap = Boolean.getBoolean( "useDenseMaps" ) ? new DenseLongMap( 6 ) : new TinyDenseLongMap(); + new NodeIterator( this, false ).processDir( nodeTilesIn, ".tlf" ); + + // *** finally process the way-file, cutting into pieces + new WayIterator( this, true ).processFile( wayFileIn ); + closeTileOutStreams(); + } + + @Override + public void nextNode( NodeData n ) throws Exception + { + tileIndexMap.put( n.nid, getTileIndex( n.ilon, n.ilat ) ); + } + + @Override + public void nextWay( WayData data ) throws Exception + { + // propagate the cycleway-bit + if ( cyclewayset.contains( data.wid ) ) + { + data.description |= 2; + } + + long waytileset = 0; + int nnodes = data.nodes.size(); + + // determine the tile-index for each node + for (int i=0; i 7 || lat < 0 || lat > 5 ) throw new IllegalArgumentException( "illegal pos: " + ilon + "," + ilat ); + return lon*6 + lat; + } + + protected String getNameForTile( int tileIndex ) + { + int lon = (tileIndex / 6 ) * 45 - 180; + int lat = (tileIndex % 6 ) * 30 - 90; + String slon = lon < 0 ? "W" + (-lon) : "E" + lon; + String slat = lat < 0 ? "S" + (-lat) : "N" + lat; + return slon + "_" + slat + ".wtl"; + } + +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayCutter5.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayCutter5.java new file mode 100644 index 0000000..ea4aea5 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayCutter5.java @@ -0,0 +1,146 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * WayCutter5 does 2 step in map-processing: + * + * - cut the 45*30 way files into 5*5 pieces + * - create a file containing all border node ids + * + * @author ab + */ +public class WayCutter5 extends MapCreatorBase +{ + private DataOutputStream borderNidsOutStream; + private DenseLongMap tileIndexMap; + private File nodeTilesIn; + private int lonoffset; + private int latoffset; + + public static void main(String[] args) throws Exception + { + System.out.println("*** WayCutter5: Soft-Cut way-data into tiles"); + if (args.length != 4) + { + System.out.println("usage: java WayCutter5 " ); + return; + } + new WayCutter5().process( new File( args[0] ), new File( args[1] ), new File( args[2] ), new File( args[3] ) ); + } + + public void process( File nodeTilesIn, File wayTilesIn, File wayTilesOut, File borderNidsOut ) throws Exception + { + this.nodeTilesIn = nodeTilesIn; + this.outTileDir = wayTilesOut; + + borderNidsOutStream = createOutStream( borderNidsOut ); + + new WayIterator( this, true ).processDir( wayTilesIn, ".wtl" ); + + borderNidsOutStream.close(); + } + + @Override + public void wayFileStart( File wayfile ) throws Exception + { + // read corresponding node-file into tileIndexMap + String name = wayfile.getName(); + String nodefilename = name.substring( 0, name.length()-3 ) + "tlf"; + File nodefile = new File( nodeTilesIn, nodefilename ); + + tileIndexMap = Boolean.getBoolean( "useDenseMaps" ) ? new DenseLongMap( 6 ) : new TinyDenseLongMap(); + lonoffset = -1; + latoffset = -1; + new NodeIterator( this, false ).processFile( nodefile ); + } + + @Override + public void nextNode( NodeData n ) throws Exception + { + tileIndexMap.put( n.nid, getTileIndex( n.ilon, n.ilat ) ); + } + + @Override + public void nextWay( WayData data ) throws Exception + { + long waytileset = 0; + int nnodes = data.nodes.size(); + int[] tiForNode = new int[nnodes]; + + // determine the tile-index for each node + for (int i=0; i 0 && tiForNode[i-1] != ti ) || (i+1 < nnodes && tiForNode[i+1] != ti ) ) + { + writeId( borderNidsOutStream, data.nodes.get(i) ); + } + } + } + } + + @Override + public void wayFileEnd( File wayFile ) throws Exception + { + closeTileOutStreams(); + } + + private int getTileIndex( int ilon, int ilat ) + { + int lonoff = (ilon / 45000000 ) * 45; + int latoff = (ilat / 30000000 ) * 30; + if ( lonoffset == -1 ) lonoffset = lonoff; + if ( latoffset == -1 ) latoffset = latoff; + if ( lonoff != lonoffset || latoff != latoffset ) + throw new IllegalArgumentException( "inconsistent node: " + ilon + " " + ilat ); + + int lon = (ilon / 5000000) % 9; + int lat = (ilat / 5000000) % 6; + if ( lon < 0 || lon > 8 || lat < 0 || lat > 5 ) throw new IllegalArgumentException( "illegal pos: " + ilon + "," + ilat ); + return lon*6 + lat; + } + + + protected String getNameForTile( int tileIndex ) + { + int lon = (tileIndex / 6 ) * 5 + lonoffset - 180; + int lat = (tileIndex % 6 ) * 5 + latoffset - 90; + String slon = lon < 0 ? "W" + (-lon) : "E" + lon; + String slat = lat < 0 ? "S" + (-lat) : "N" + lat; + return slon + "_" + slat + ".wt5"; + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayData.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayData.java new file mode 100644 index 0000000..05e691c --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayData.java @@ -0,0 +1,62 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * Container for waydata on the preprocessor level + * + * @author ab + */ +public class WayData extends MapCreatorBase +{ + public long wid; + public long description; + public LongList nodes; + + public WayData( long id ) + { + wid = id; + nodes = new LongList( 16 ); + } + + public WayData( long id, LongList nodes ) + { + wid = id; + this.nodes = nodes; + } + + public WayData( DataInputStream di ) throws Exception + { + nodes = new LongList( 16 ); + wid = readId( di) ; + description = di.readLong(); + for (;;) + { + long nid = readId( di ); + if ( nid == -1 ) break; + nodes.add( nid ); + } + } + + public void writeTo( DataOutputStream dos ) throws Exception + { + writeId( dos, wid ); + dos.writeLong( description ); + int size = nodes.size(); + for( int i=0; i < size; i++ ) + { + writeId( dos, nodes.get( i ) ); + } + writeId( dos, -1 ); // stopbyte + } +} diff --git a/brouter-map-creator/src/main/java/btools/mapcreator/WayIterator.java b/brouter-map-creator/src/main/java/btools/mapcreator/WayIterator.java new file mode 100644 index 0000000..89fed53 --- /dev/null +++ b/brouter-map-creator/src/main/java/btools/mapcreator/WayIterator.java @@ -0,0 +1,76 @@ +package btools.mapcreator; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.HashMap; + +import btools.util.*; + +/** + * Iterate over a singe wayfile or a directory + * of waytiles and feed the ways to the callback listener + * + * @author ab + */ +public class WayIterator extends MapCreatorBase +{ + private WayListener listener; + private boolean delete; + + public WayIterator( WayListener wayListener, boolean deleteAfterReading ) + { + listener = wayListener; + delete = deleteAfterReading; + } + + public void processDir( File indir, String inSuffix ) throws Exception + { + if ( !indir.isDirectory() ) + { + throw new IllegalArgumentException( "not a directory: " + indir ); + } + + File[] af = sortBySizeAsc( indir.listFiles() ); + for( int i=0; i nodesMap; + private List nodesList; + private CompactLongSet borderSet; + private short lookupVersion; + + private BExpressionContext expctxWay; + + private int minLon; + private int minLat; + + private void reset() + { + minLon = -1; + minLat = -1; + nodesMap = new CompactLongMap(); + borderSet = new CompactLongSet(); + } + + public static void main(String[] args) throws Exception + { + System.out.println("*** WayLinker: Format a regionof an OSM map for routing"); + if (args.length != 7) + { + System.out.println("usage: java WayLinker "); + return; + } + 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] ), args[6] ); + } + + public void process( File nodeTilesIn, File wayTilesIn, File borderFileIn, File lookupFile, File profileFile, File dataTilesOut, String dataTilesSuffix ) throws Exception + { + this.nodeTilesIn = nodeTilesIn; + this.dataTilesOut = dataTilesOut; + this.borderFileIn = borderFileIn; + this.dataTilesSuffix = dataTilesSuffix; + + // read lookup file to get the lookup-version + expctxWay = new BExpressionContext("way"); + expctxWay.readMetaData( lookupFile ); + lookupVersion = expctxWay.lookupVersion; + expctxWay.parseFile( profileFile, "global" ); + + // then process all segments + new WayIterator( this, true ).processDir( wayTilesIn, ".wt5" ); + } + + @Override + public void wayFileStart( File wayfile ) throws Exception + { + // process corresponding node-file, if any + File nodeFile = fileFromTemplate( wayfile, nodeTilesIn, "u5d" ); + if ( nodeFile.exists() ) + { + reset(); + + // read the border file + readingBorder = true; + new NodeIterator( this, false ).processFile( borderFileIn ); + borderSet = new FrozenLongSet( borderSet ); + + // read this tile's nodes + readingBorder = false; + new NodeIterator( this, false ).processFile( nodeFile ); + + // freeze the nodes-map + FrozenLongMap nodesMapFrozen = new FrozenLongMap( nodesMap ); + nodesMap = nodesMapFrozen; + nodesList = nodesMapFrozen.getValueList(); + } + } + + @Override + public void nextNode( NodeData data ) throws Exception + { + OsmNodeP n = data.description == 0L ? new OsmNodeP() : new OsmNodePT(data.description); + n.ilon = data.ilon; + n.ilat = data.ilat; + n.selev = data.selev; + n.isBorder = readingBorder; + if ( readingBorder || (!borderSet.contains( data.nid )) ) + { + nodesMap.fastPut( data.nid, n ); + } + + if ( readingBorder ) + { + borderSet.fastAdd( data.nid ); + return; + } + + // remember the segment coords + int min_lon = (n.ilon / 5000000 ) * 5000000; + int min_lat = (n.ilat / 5000000 ) * 5000000; + if ( minLon == -1 ) minLon = min_lon; + if ( minLat == -1 ) minLat = min_lat; + if ( minLat != min_lat || minLon != min_lon ) + throw new IllegalArgumentException( "inconsistent node: " + n.ilon + " " + n.ilat ); + } + + @Override + public void nextWay( WayData way ) throws Exception + { + long description = way.description; + long reverseDescription = description | 1L; // (add reverse bit) + + // filter according to profile + expctxWay.evaluate( description, null ); + boolean ok = expctxWay.getCostfactor() < 10000.; + expctxWay.evaluate( reverseDescription, null ); + ok |= expctxWay.getCostfactor() < 10000.; + + if ( !ok ) return; + + byte lowbyte = (byte)description; + + OsmNodeP n1 = null; + OsmNodeP n2 = null; + for (int i=0; i seglists = new LazyArrayOfLists(nLonSegs*nLatSegs); + for( OsmNodeP n : nodesList ) + { + if ( n == null || n.firstlink == null || n.isTransferNode() ) continue; + if ( n.ilon < minLon || n.ilon >= maxLon + || n.ilat < minLat || n.ilat >= maxLat ) continue; + int lonIdx = (n.ilon-minLon)/1000000; + int latIdx = (n.ilat-minLat)/1000000; + + int tileIndex = lonIdx * nLatSegs + latIdx; + seglists.getList(tileIndex).add( n ); + } + nodesList = null; + seglists.trimAll(); + + // open the output file + File outfile = fileFromTemplate( wayfile, dataTilesOut, dataTilesSuffix ); + DataOutputStream os = createOutStream( outfile ); + + // write 5*5 index dummy + long[] fileIndex = new long[25]; + for( int i55=0; i55<25; i55++) + { + os.writeLong( 0 ); + } + long filepos = 200L; + + // sort further in 1/80-degree squares + for( int lonIdx = 0; lonIdx < nLonSegs; lonIdx++ ) + { + for( int latIdx = 0; latIdx < nLatSegs; latIdx++ ) + { + int tileIndex = lonIdx * nLatSegs + latIdx; + if ( seglists.getSize(tileIndex) > 0 ) + { + List nlist = seglists.getList(tileIndex); + + LazyArrayOfLists subs = new LazyArrayOfLists(6400); + byte[][] subByteArrays = new byte[6400][]; + for( int ni=0; ni subList = subs.getList(si); + if ( subList.size() > 0 ) + { + Collections.sort( subList ); + + ByteArrayOutputStream bos = new ByteArrayOutputStream( ); + DataOutputStream dos = new DataOutputStream( bos ); + dos.writeInt( subList.size() ); + for( int ni=0; ni + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-mapaccess + jar + diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/ByteDataReader.java b/brouter-mapaccess/src/main/java/btools/mapaccess/ByteDataReader.java new file mode 100644 index 0000000..b1776ff --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/ByteDataReader.java @@ -0,0 +1,59 @@ +/** + * fast data-reading from a byte-array + * + * @author ab + */ +package btools.mapaccess; + + +final class ByteDataReader +{ + private byte[] ab; + private int aboffset; + + public ByteDataReader( byte[] byteArray ) + { + ab = byteArray; + } + + public int readInt() + { + int i3 = ab[aboffset++]& 0xff; + int i2 = ab[aboffset++]& 0xff; + int i1 = ab[aboffset++]& 0xff; + int i0 = ab[aboffset++]& 0xff; + return (i3 << 24) + (i2 << 16) + (i1 << 8) + i0; + } + + public long readLong() + { + long i7 = ab[aboffset++]& 0xff; + long i6 = ab[aboffset++]& 0xff; + long i5 = ab[aboffset++]& 0xff; + long i4 = ab[aboffset++]& 0xff; + long i3 = ab[aboffset++]& 0xff; + long i2 = ab[aboffset++]& 0xff; + long i1 = ab[aboffset++]& 0xff; + long i0 = ab[aboffset++]& 0xff; + return (i7 << 56) + (i6 << 48) + (i5 << 40) + (i4 << 32) + (i3 << 24) + (i2 << 16) + (i1 << 8) + i0; + } + + public boolean readBoolean() + { + int i0 = ab[aboffset++]& 0xff; + return i0 != 0; + } + + public byte readByte() + { + int i0 = ab[aboffset++] & 0xff; + return (byte)(i0); + } + + public short readShort() + { + int i1 = ab[aboffset++] & 0xff; + int i0 = ab[aboffset++] & 0xff; + return (short)( (i1 << 8) | i0); + } +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/ByteDataWriter.java b/brouter-mapaccess/src/main/java/btools/mapaccess/ByteDataWriter.java new file mode 100644 index 0000000..9c6636b --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/ByteDataWriter.java @@ -0,0 +1,54 @@ +/** + * fast data-reading from a byte-array + * + * @author ab + */ +package btools.mapaccess; + + +final class ByteDataWriter +{ + private byte[] ab; + private int aboffset; + + public ByteDataWriter( byte[] byteArray ) + { + ab = byteArray; + } + + public void writeInt( int v ) + { + ab[aboffset++] = (byte)( (v >> 24) & 0xff ); + ab[aboffset++] = (byte)( (v >> 16) & 0xff ); + ab[aboffset++] = (byte)( (v >> 8) & 0xff ); + ab[aboffset++] = (byte)( (v ) & 0xff ); + } + + public void writeLong( long v ) + { + ab[aboffset++] = (byte)( (v >> 56) & 0xff ); + ab[aboffset++] = (byte)( (v >> 48) & 0xff ); + ab[aboffset++] = (byte)( (v >> 40) & 0xff ); + ab[aboffset++] = (byte)( (v >> 32) & 0xff ); + ab[aboffset++] = (byte)( (v >> 24) & 0xff ); + ab[aboffset++] = (byte)( (v >> 16) & 0xff ); + ab[aboffset++] = (byte)( (v >> 8) & 0xff ); + ab[aboffset++] = (byte)( (v ) & 0xff ); + } + + public void writeBoolean( boolean v) + { + ab[aboffset++] = (byte)( v ? 1 : 0 ); + } + + public void writeByte( int v ) + { + ab[aboffset++] = (byte)( (v ) & 0xff ); + } + + public void writeShort( int v ) + { + ab[aboffset++] = (byte)( (v >> 8) & 0xff ); + ab[aboffset++] = (byte)( (v ) & 0xff ); + } +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/DistanceChecker.java b/brouter-mapaccess/src/main/java/btools/mapaccess/DistanceChecker.java new file mode 100644 index 0000000..4966926 --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/DistanceChecker.java @@ -0,0 +1,16 @@ +/** + * Container for routig configs + * + * @author ab + */ +package btools.mapaccess; + +public interface DistanceChecker +{ + /** + * Checks whether the given path is within a maximum distance + * known to the distance checker + * @return true if close enough + */ + boolean isWithinRadius( int ilon0, int ilat0, OsmTransferNode firstTransfer, int ilon1, int ilat1 ); +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/MicroCache.java b/brouter-mapaccess/src/main/java/btools/mapaccess/MicroCache.java new file mode 100644 index 0000000..eb6a752 --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/MicroCache.java @@ -0,0 +1,232 @@ +/** + * cache for a single square + * + * @author ab + */ +package btools.mapaccess; + +import java.util.*; +import java.io.*; + +final class MicroCache +{ + private long[] faid; + private int[] fapos; + private int size = 0; + private int delcount = 0; + private int delbytes = 0; + private int p2size; // next power of 2 of size + + // the object parsing position and length + private byte[] ab; + private int aboffset; + private int ablength; + + public MicroCache( OsmFile segfile, int lonIdx80, int latIdx80, byte[] iobuffer ) throws Exception + { + int lonDegree = lonIdx80/80; + int latDegree = latIdx80/80; + + int lonIdxBase = (lonIdx80/5)*62500 + 31250; + int latIdxBase = (latIdx80/5)*62500 + 31250; + + int subIdx = (latIdx80-80*latDegree)*80 + (lonIdx80-80*lonDegree); + + try + { + ab = iobuffer; + int asize = segfile.getDataInputForSubIdx(subIdx, ab); + if ( asize == 0 ) + { + return; + } + if ( asize > iobuffer.length ) + { + ab = new byte[asize]; + asize = segfile.getDataInputForSubIdx(subIdx, ab); + } + aboffset = 0; + size = readInt(); + + // new array with only net data + byte[] nab = new byte[asize - 4 - size*8]; + int noffset = 0; + faid = new long[size]; + fapos = new int[size]; + p2size = 0x40000000; + while( p2size > size ) p2size >>= 1; + + for(int i = 0; i 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + if ( ( fapos[n] & 0x80000000 ) == 0 ) + { + aboffset = fapos[n]; + ablength = ( n+1 < size ? fapos[n+1] & 0x7fffffff : ab.length ) - aboffset; + fapos[n] |= 0x80000000; // mark deleted + delbytes+= ablength; + delcount++; + return true; + } + else + { + throw new RuntimeException( "MicroCache: node already consumed: id=" + id ); + } + } + return false; + } + + public void fillNode( OsmNode node, OsmNodesMap nodesMap, DistanceChecker dc ) + { + long id = node.getIdFromPos(); + if ( getAndClear( id ) ) + { + node.parseNodeBody( this, ablength, nodesMap, dc ); + } + + if ( delcount > size / 2 ) // garbage collection + { + int nsize = size - delcount; + if ( nsize == 0 ) + { + faid = null; + fapos = null; + } + else + { + long[] nfaid = new long[nsize]; + int[] nfapos = new int[nsize]; + int idx = 0; + + byte[] nab = new byte[ab.length - delbytes]; + int nab_off = 0; + for( int i=0; i size ) p2size >>= 1; + } + } + + public List getPositions( OsmNodesMap nodesMap ) + { + ArrayList positions = new ArrayList(); + + for( int i=0; i fileCache; + private HashMap indexCache; + private byte[] iobuffer; + + private OsmFile[][] fileRows = new OsmFile[180][]; + private ArrayList segmentList = new ArrayList(); + + public DistanceChecker distanceChecker; + + public boolean oom_carsubset_hint = false; + + public NodesCache( String segmentDir, OsmNodesMap nodesMap, int lookupVersion, boolean carMode, NodesCache oldCache ) + { + this.segmentDir = segmentDir; + this.nodesMap = nodesMap; + this.lookupVersion = lookupVersion; + this.carMode = carMode; + + if ( oldCache != null ) + { + fileCache = oldCache.fileCache; + indexCache = oldCache.indexCache; + iobuffer = oldCache.iobuffer; + oom_carsubset_hint = oldCache.oom_carsubset_hint; + } + else + { + fileCache = new HashMap(4); + indexCache = new HashMap(4); + iobuffer = new byte[65636]; + } + } + + public int loadSegmentFor( int ilon, int ilat ) + { + MicroCache mc = getSegmentFor( ilon, ilat ); + return mc == null ? 0 : mc.getSize(); + } + + public MicroCache getSegmentFor( int ilon, int ilat ) + { + try + { + int lonIdx80 = ilon/12500; + int latIdx80 = ilat/12500; + int lonDegree = lonIdx80/80; + int latDegree = latIdx80/80; + OsmFile osmf = null; + OsmFile[] fileRow = fileRows[latDegree]; + int ndegrees = fileRow == null ? 0 : fileRow.length; + for( int i=0; i> 48); + if ( readVersion != lookupVersion ) + { + throw new IllegalArgumentException( "lookup version mismatch (old rd5?) lookups.dat=" + + lookupVersion + " " + f. getAbsolutePath() + "=" + readVersion ); + } + fileIndex[i] = lv & 0xffffffffffffL; + } + indexCache.put( filenameBase, fileIndex ); + } + } + RandomAccessFile ra = fileCache.get( filenameBase ); + long startPos = 0L; + if ( ra != null ) + { + long[] index = indexCache.get( filenameBase ); + startPos = tileIndex > 0 ? index[ tileIndex-1 ] : 200L; + if ( startPos == index[ tileIndex] ) ra = null; + } + OsmFile osmf = new OsmFile( ra, startPos, iobuffer ); + osmf.lonDegree = lonDegree; + osmf.latDegree = latDegree; + osmf.filename = currentFileName; + return osmf; + } + + public List getAllNodes() + { + List all = new ArrayList(); + for( MicroCache segment : segmentList ) + { + List positions = segment.getPositions( nodesMap ); + all.addAll( positions ); + } + return all; + } + + + public void close() + { + for( RandomAccessFile f: fileCache.values() ) + { + try + { + f.close(); + } + catch( IOException ioe ) + { + // ignore + } + } + } +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/NodesList.java b/brouter-mapaccess/src/main/java/btools/mapaccess/NodesList.java new file mode 100644 index 0000000..1ab0752 --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/NodesList.java @@ -0,0 +1,14 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.mapaccess; + +import java.util.*; + +final class NodesList +{ + public OsmNode node; + public NodesList next; +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java new file mode 100644 index 0000000..4327e09 --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmFile.java @@ -0,0 +1,67 @@ +/** + * cache for a single square + * + * @author ab + */ +package btools.mapaccess; + +import java.io.IOException; +import java.io.RandomAccessFile; + +final class OsmFile +{ + private RandomAccessFile is = null; + private long fileOffset; + + private int[] posIdx; + public MicroCache[] microCaches; + + public int lonDegree; + public int latDegree; + + public String filename; + + public OsmFile( RandomAccessFile rafile, long startPos, byte[] iobuffer ) throws Exception + { + fileOffset = startPos; + if ( rafile != null ) + { + is = rafile; + posIdx = new int[6400]; + microCaches = new MicroCache[6400]; + is.seek( fileOffset ); + is.readFully( iobuffer, 0, 25600 ); + ByteDataReader dis = new ByteDataReader( iobuffer ); + for( int i=0; i<6400; i++ ) + { + posIdx[i] = dis.readInt(); + } + } + } + + private int getPosIdx( int idx ) + { + return idx == -1 ? 25600 : posIdx[idx]; + } + + public int getDataInputForSubIdx( int subIdx, byte[] iobuffer ) throws Exception + { + int startPos = getPosIdx(subIdx-1); + int endPos = getPosIdx(subIdx); + int size = endPos-startPos; + if ( size > 0 ) + { + is.seek( fileOffset + startPos ); + if ( size <= iobuffer.length ) + { + is.readFully( iobuffer ); + } + } + return size; + } + + public void close() + { + try { is.close(); } catch( IOException e ) { throw new RuntimeException( e ); } + } +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmLink.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmLink.java new file mode 100644 index 0000000..70d92bd --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmLink.java @@ -0,0 +1,53 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.mapaccess; + +import java.util.*; + +public final class OsmLink +{ + /** + * The description bitmap is mainly the way description + * used to calculate the costfactor + */ + public long descriptionBitmap; + + /** + * The target is either the next link or the target node + */ + public OsmNode targetNode; + + /** + * The origin position + */ + public int ilatOrigin; + public int ilonOrigin; + + public OsmLink next; + + public byte[] firsttransferBytes; + + public OsmTransferNode decodeFirsttransfer() + { + return firsttransferBytes == null ? null : OsmTransferNode.decode( firsttransferBytes ); + } + + public void encodeFirsttransfer( OsmTransferNode firsttransfer ) + { + if ( firsttransfer == null ) firsttransferBytes = null; + else firsttransferBytes = OsmTransferNode.encode( firsttransfer ); + } + + public boolean counterLinkWritten; + + public OsmLinkHolder firstlinkholder = null; + + public void addLinkHolder( OsmLinkHolder holder ) + { + if ( firstlinkholder != null ) { holder.setNextForLink( firstlinkholder ); } + firstlinkholder = holder; + } +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmLinkHolder.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmLinkHolder.java new file mode 100644 index 0000000..da572d3 --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmLinkHolder.java @@ -0,0 +1,13 @@ +/** + * Container for routig configs + * + * @author ab + */ +package btools.mapaccess; + +public interface OsmLinkHolder +{ + void setNextForLink( OsmLinkHolder holder ); + + OsmLinkHolder getNextForLink(); +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java new file mode 100644 index 0000000..a97856b --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNode.java @@ -0,0 +1,397 @@ +/** + * Container for an osm node + * + * @author ab + */ +package btools.mapaccess; + + + +public class OsmNode implements OsmPos, Comparable +{ + private static final long serialVersionUID = -4166565134085275556L; + + public static final int EXTERNAL_BITMASK = 0x80; + public static final int FIRSTFORWAY_BITMASK = 0x40; + public static final int TRANSFERNODE_BITMASK = 0x20; + public static final int WRITEDESC_BITMASK = 0x10; + public static final int SKIPDETAILS_BITMASK = 0x08; + public static final int NODEDESC_BITMASK = 0x04; + + public OsmNode() + { + } + + public OsmNode( int ilon, int ilat ) + { + this.ilon = ilon; + this.ilat = ilat; + } + + public OsmNode( long id ) + { + ilon = (int)(id >> 32); + ilat = (int)(id & 0xffffffff); + } + + /** + * The latitude + */ + public int ilat; + + /** + * The longitude + */ + public int ilon; + + /** + * The elevation + */ + public short selev; + + public long nodeDescription; + + // interface OsmPos + public int getILat() + { + return ilat; + } + + public int getILon() + { + return ilon; + } + + public short getSElev() + { + return selev; + } + + public double getElev() + { + return selev / 4.; + } + + /** + * Whether there's a traffic signal + */ + + /** + * The links to other nodes + */ + public OsmLink firstlink = null; + + public OsmLink firstreverse = null; + + // whether this node is completed and registerd for map-removal + public boolean completed; + + public boolean wasProcessed; + public int maxcost; // maximum cost to consider for that node + + public void addLink( OsmLink link ) + { + if ( firstlink != null ) link.next = firstlink; + firstlink = link; + } + + public int calcDistance( OsmPos p ) + { + double l = (ilat-90000000) * 0.00000001234134; + double l2 = l*l; + double l4 = l2*l2; + double coslat = 1.- l2 + l4 / 6.; + + double dlat = (ilat - p.getILat() )/1000000.; + double dlon = (ilon - p.getILon() )/1000000. * coslat; + double d = Math.sqrt( dlat*dlat + dlon*dlon ) * (6378000. / 57.3); + return (int)(d + 1.0 ); + } + + + public void parseNodeBody( MicroCache is, int bodySize, OsmNodesMap hollowNodes, DistanceChecker dc ) + { + selev = is.readShort(); + bodySize -= 2; + + OsmLink lastlink = null; + + int lonIdx = ilon/62500; + int latIdx = ilat/62500; + + while( bodySize > 0 ) + { + OsmLink link = new OsmLink(); + OsmTransferNode firstTransferNode = null; + OsmTransferNode lastTransferNode = null; + int linklon; + int linklat; + long description = 0L; + for(;;) + { + int bitField = is.readByte(); + bodySize -= 1; + if ( (bitField & EXTERNAL_BITMASK) != 0 ) + { + // full position for external target + bodySize -= 8; + linklon = is.readInt(); + linklat = is.readInt(); + } + else + { + // reduced position for internal target + bodySize -= 4; + linklon = is.readShort(); + linklat = is.readShort(); + linklon += lonIdx*62500 + 31250; + linklat += latIdx*62500 + 31250; + } + if ( (bitField & WRITEDESC_BITMASK ) != 0 ) + { + description = is.readLong(); + bodySize -= 8; + } + if ( (bitField & NODEDESC_BITMASK ) != 0 ) + { + nodeDescription = is.readLong(); + bodySize -= 8; + } + if ( (bitField & SKIPDETAILS_BITMASK ) != 0 ) + { + link.counterLinkWritten = true; + } + boolean isTransfer = (bitField & TRANSFERNODE_BITMASK ) != 0; + if ( isTransfer ) + { + OsmTransferNode trans = new OsmTransferNode(); + trans.ilon = linklon; + trans.ilat = linklat; + trans.descriptionBitmap = description; + bodySize -= 2; + trans.selev = is.readShort(); + if ( lastTransferNode == null ) + { + firstTransferNode = trans; + } + else + { + lastTransferNode.next = trans; + } + lastTransferNode = trans; + } + else + { + link.descriptionBitmap = description; + break; + } + } + + // performance shortcut: ignore link if out of reach + if ( dc != null && !link.counterLinkWritten ) + { + if ( !dc.isWithinRadius( ilon, ilat, firstTransferNode, linklon, linklat ) ) + { + continue; + } + } + + if ( linklon == ilon && linklat == ilat ) + { + continue; // skip self-ref + } + + if ( lastlink == null ) + { + firstlink = link; + } + else + { + lastlink.next = link; + } + lastlink = link; + + + long targetNodeId = ((long)linklon)<<32 | linklat; + OsmNode tn = hollowNodes.get( targetNodeId ); // target node + + if ( tn == null ) + { + // node not yet known, create a hollow proxy + tn = new OsmNode(linklon, linklat); + tn.setHollow(); + hollowNodes.put( targetNodeId, tn ); + } + else + { + if ( !( tn.isHollow() || tn.hasHollowLinks() ) ) + { + hollowNodes.registerCompletedNode( tn ); + } + } + link.targetNode = tn; + + link.encodeFirsttransfer(firstTransferNode); + + // compute the reverse link + if ( !link.counterLinkWritten ) + { + OsmLink rlink = new OsmLink(); + long rerverseLinkBitmap = link.descriptionBitmap ^ 1L; + + rlink.ilonOrigin = tn.ilon; + rlink.ilatOrigin = tn.ilat; + rlink.targetNode = this; + rlink.descriptionBitmap = rerverseLinkBitmap; // default for no transfer-nodes + OsmTransferNode previous = null; + OsmTransferNode rtrans = null; + for( OsmTransferNode trans = firstTransferNode; trans != null; trans = trans.next ) + { + long rerverseTransBitmap = trans.descriptionBitmap ^ 1L; + if ( previous == null ) + { + rlink.descriptionBitmap = rerverseTransBitmap; + } + else + { + previous.descriptionBitmap = rerverseTransBitmap; + } + rtrans = new OsmTransferNode(); + rtrans.ilon = trans.ilon; + rtrans.ilat = trans.ilat; + rtrans.selev = trans.selev; + rtrans.next = previous; + rtrans.descriptionBitmap = rerverseLinkBitmap; + previous = rtrans; + } + rlink.encodeFirsttransfer(rtrans); + rlink.next = firstreverse; + firstreverse = rlink; + } + + } + + if ( !hasHollowLinks() ) + { + hollowNodes.registerCompletedNode( this ); + } + } + + public boolean isHollow() + { + return selev == -12345; + } + + public void setHollow() + { + selev = -12345; + } + + public long getIdFromPos() + { + return ((long)ilon)<<32 | ilat; + } + + public boolean hasHollowLinks() + { + for( OsmLink link = firstlink; link != null; link = link.next ) + { + if ( link.targetNode.isHollow() ) return true; + } + return false; + } + + + public int linkCnt() + { + int cnt = 0; + + for( OsmLink link = firstlink; link != null; link = link.next ) + { + cnt++; + } + return cnt; + } + + public void unlinkLink( OsmLink link ) + { + if ( link == firstlink ) + { + firstlink = link.next; + return; + } + for( OsmLink l = firstlink; l != null; l = l.next ) + { + if ( l.next == link ) + { + l.next = link.next; + return; + } + } + } + + /** + * Compares two OsmNodes for position ordering. + * + * @return -1,0,1 depending an comparson result + */ + public int compareTo( Object o ) + { + OsmNode n = (OsmNode)o; + long id1 = getIdFromPos(); + long id2 = n.getIdFromPos(); + if ( id1 < id2 ) return -1; + if ( id1 > id2 ) return 1; + return 0; + } + + /** + * @return if equals in the sense of compareTo == 0 + */ + public boolean equals( Object o ) + { + return compareTo( o ) == 0; + } + + // mark the link to the given node as written, + // don't want to write the counter-direction + // in full details + public void markLinkWritten( OsmNode t ) + { + for( OsmLink link = firstlink; link != null; link = link.next ) + { + if ( link.targetNode == t) link.counterLinkWritten = true; + } + } + + public OsmLink getReverseLink( int lon, int lat ) + { + for( OsmLink rlink = firstreverse; rlink != null; rlink = rlink.next ) + { + if ( rlink.ilonOrigin == lon && rlink.ilatOrigin == lat ) + { + unlinkRLink( rlink ); + return rlink; + } + } + return null; + } + + public void unlinkRLink( OsmLink rlink ) + { + if ( rlink == firstreverse ) + { + firstreverse = rlink.next; + return; + } + for( OsmLink l = firstreverse; l != null; l = l.next ) + { + if ( l.next == rlink ) + { + l.next = rlink.next; + return; + } + } + } + +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodesMap.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodesMap.java new file mode 100644 index 0000000..d9db767 --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmNodesMap.java @@ -0,0 +1,109 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.mapaccess; + +import java.util.*; + +public final class OsmNodesMap +{ + private HashMap hmap = new HashMap(); + + private NodesList completedNodes = null; + + /** + * Get a node from the map + * @return the node for the given id if exist, else null + */ + public OsmNode get( long id ) + { + return hmap.get( new Long( id ) ); + } + + + public void remove( long id ) + { + hmap.remove( new Long( id ) ); + } + + public void removeCompletedNodes() + { + for( NodesList le = completedNodes; le != null; le = le.next ) + { + remove( le.node.getIdFromPos() ); + } + completedNodes = null; + } + + public void registerCompletedNode( OsmNode n ) + { + if ( n.completed ) return; + n.completed = true; + NodesList le = new NodesList(); + le.node = n; + if ( completedNodes != null ) le.next = completedNodes; + completedNodes = le; + } + + /** + * Put a node into the map + * @return the previous node if that id existed, else null + */ + public OsmNode put( long id, OsmNode node ) + { + return hmap.put( new Long( id ), node ); + } + + /** + * Return the internal list. + * A reference is returned, not a copy- + * @return the nodes list + */ + public Collection nodes() + { + return hmap.values(); + } + + /** + * @return the number of nodes in that map + */ + public int size() + { + return hmap.size(); + } + + /** + * cleanup the map by removing the nodes + * with no hollow issues + */ + + private int dontCareCount = 0; + + public void removeCompleteNodes() + { + if ( ++dontCareCount < 5 ) return; + dontCareCount = 0; + + ArrayList delNodes = new ArrayList(); + + for( OsmNode n : hmap.values() ) + { + if ( n.isHollow() || n.hasHollowLinks() ) + { + continue; + } + delNodes.add( n ); + } + + if ( delNodes.size() > 0 ) + { +// System.out.println( "removing " + delNodes.size() + " nodes" ); + for( OsmNode n : delNodes ) + { + hmap.remove( new Long( n.getIdFromPos() ) ); + } + } + } +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmPos.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmPos.java new file mode 100644 index 0000000..e6f48cb --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmPos.java @@ -0,0 +1,23 @@ +/** + * Interface for a position (OsmNode or OsmPath) + * + * @author ab + */ +package btools.mapaccess; + + +public interface OsmPos +{ + public int getILat(); + + public int getILon(); + + public short getSElev(); + + public double getElev(); + + public int calcDistance( OsmPos p ); + + public long getIdFromPos(); + +} diff --git a/brouter-mapaccess/src/main/java/btools/mapaccess/OsmTransferNode.java b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmTransferNode.java new file mode 100644 index 0000000..a4744ba --- /dev/null +++ b/brouter-mapaccess/src/main/java/btools/mapaccess/OsmTransferNode.java @@ -0,0 +1,144 @@ +/** + * Container for link between two Osm nodes + * + * @author ab + */ +package btools.mapaccess; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + + +public final class OsmTransferNode +{ + /** + * The description bitmap is mainly the way description + * used to calculate the costfactor + */ + public long descriptionBitmap; + + public OsmTransferNode next; + + public int ilon; + public int ilat; + public short selev; + + private static final int BIT_DESC = 1; + private static final int BIT_ILONHIGH = 2; + private static final int BIT_ILATHIGH = 4; + private static final int BIT_STOP = 8; + + // encode this transfer-node into a byte array + public static byte[] encode( OsmTransferNode tn ) + { + long currentDesc = 0; + int currentILonHigh = 0; + int currentILatHigh = 0; + OsmTransferNode n = tn; + + // first loop to calc size + int size = 1; // stop-bit + + while( n != null ) + { + if( n.descriptionBitmap != currentDesc ) + { + size += 8; + currentDesc = n.descriptionBitmap; + } + if( ( n.ilon >> 16 ) != currentILonHigh ) + { + size += 2; + currentILonHigh = n.ilon >> 16; + } + if( (n.ilat >> 16) != currentILatHigh ) + { + size += 2; + currentILatHigh = n.ilat >> 16; + } + size += 7; + n = n.next; + } + + byte[] ab = new byte[size]; + ByteDataWriter os = new ByteDataWriter( ab ); + + currentDesc = 0; + currentILonHigh = 0; + currentILatHigh = 0; + n = tn; + while( n != null ) + { + int mode = 0; + if( n.descriptionBitmap != currentDesc ) + { + mode |= BIT_DESC; + currentDesc = n.descriptionBitmap; + } + if( ( n.ilon >> 16 ) != currentILonHigh ) + { + mode |= BIT_ILONHIGH; + currentILonHigh = n.ilon >> 16; + } + if( (n.ilat >> 16) != currentILatHigh ) + { + mode |= BIT_ILATHIGH; + currentILatHigh = n.ilat >> 16; + } + os.writeByte( mode); + if ( (mode & BIT_DESC) != 0 ) os.writeLong( currentDesc ); + if ( (mode & BIT_ILONHIGH) != 0 ) os.writeShort( currentILonHigh ); + if ( (mode & BIT_ILATHIGH) != 0 ) os.writeShort( currentILatHigh ); + os.writeShort( n.ilon ); + os.writeShort( n.ilat ); + os.writeShort( n.selev ); + n = n.next; + } + os.writeByte( BIT_STOP ); + return ab; + } + + // decode a transfer-node from a byte array + public static OsmTransferNode decode( byte[] ab ) + { + ByteDataReader is = new ByteDataReader( ab ); + + OsmTransferNode firstNode = null; + OsmTransferNode lastNode = null; + long currentDesc = 0; + int currentILonHigh = 0; + int currentILatHigh = 0; + for(;;) + { + byte mode = is.readByte(); + if ( (mode & BIT_STOP ) != 0 ) break; + + OsmTransferNode n = new OsmTransferNode(); + if ( (mode & BIT_DESC) != 0 ) currentDesc = is.readLong(); + if ( (mode & BIT_ILONHIGH) != 0 ) currentILonHigh = is.readShort(); + if ( (mode & BIT_ILATHIGH) != 0 ) currentILatHigh = is.readShort(); + n.descriptionBitmap = currentDesc; + int ilon = is.readShort() & 0xffff; ilon |= currentILonHigh << 16; + int ilat = is.readShort() & 0xffff; ilat |= currentILatHigh << 16; + n.ilon = ilon; + n.ilat = ilat; + n.selev = is.readShort(); + + if ( ilon != n.ilon ) System.out.println( "ilon=" + ilon + " n.ilon=" + n.ilon ); + if ( ilat != n.ilat ) System.out.println( "ilat=" + ilat + " n.ilat=" + n.ilat ); + + if ( lastNode != null ) + { + lastNode.next = n; + } + else + { + firstNode = n; + } + lastNode = n; + } + return firstNode; + } + +} diff --git a/brouter-routing-app/AndroidManifest.xml b/brouter-routing-app/AndroidManifest.xml new file mode 100644 index 0000000..1908474 --- /dev/null +++ b/brouter-routing-app/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/brouter-routing-app/classpath b/brouter-routing-app/classpath new file mode 100644 index 0000000..a662f00 --- /dev/null +++ b/brouter-routing-app/classpath @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/brouter-routing-app/gen/btools/routingapp/BuildConfig.java b/brouter-routing-app/gen/btools/routingapp/BuildConfig.java new file mode 100644 index 0000000..ba033d3 --- /dev/null +++ b/brouter-routing-app/gen/btools/routingapp/BuildConfig.java @@ -0,0 +1,5 @@ +package btools.routingapp; + +public final class BuildConfig { + public static final boolean DEBUG = true; +} diff --git a/brouter-routing-app/gen/btools/routingapp/IBRouterService.java b/brouter-routing-app/gen/btools/routingapp/IBRouterService.java new file mode 100644 index 0000000..93ec321 --- /dev/null +++ b/brouter-routing-app/gen/btools/routingapp/IBRouterService.java @@ -0,0 +1,136 @@ +/* + * This file is auto-generated. DO NOT MODIFY. + * Original file: C:\\brouter\\brouter-routing-app\\src\\main\\java\\btools\\routingapp\\IBRouterService.aidl + */ +package btools.routingapp; +public interface IBRouterService extends android.os.IInterface +{ +/** Local-side IPC implementation stub class. */ +public static abstract class Stub extends android.os.Binder implements btools.routingapp.IBRouterService +{ +private static final java.lang.String DESCRIPTOR = "btools.routingapp.IBRouterService"; +/** Construct the stub at attach it to the interface. */ +public Stub() +{ +this.attachInterface(this, DESCRIPTOR); +} +/** + * Cast an IBinder object into an btools.routingapp.IBRouterService interface, + * generating a proxy if needed. + */ +public static btools.routingapp.IBRouterService asInterface(android.os.IBinder obj) +{ +if ((obj==null)) { +return null; +} +android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR); +if (((iin!=null)&&(iin instanceof btools.routingapp.IBRouterService))) { +return ((btools.routingapp.IBRouterService)iin); +} +return new btools.routingapp.IBRouterService.Stub.Proxy(obj); +} +@Override public android.os.IBinder asBinder() +{ +return this; +} +@Override public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException +{ +switch (code) +{ +case INTERFACE_TRANSACTION: +{ +reply.writeString(DESCRIPTOR); +return true; +} +case TRANSACTION_getTrackFromParams: +{ +data.enforceInterface(DESCRIPTOR); +android.os.Bundle _arg0; +if ((0!=data.readInt())) { +_arg0 = android.os.Bundle.CREATOR.createFromParcel(data); +} +else { +_arg0 = null; +} +java.lang.String _result = this.getTrackFromParams(_arg0); +reply.writeNoException(); +reply.writeString(_result); +return true; +} +} +return super.onTransact(code, data, reply, flags); +} +private static class Proxy implements btools.routingapp.IBRouterService +{ +private android.os.IBinder mRemote; +Proxy(android.os.IBinder remote) +{ +mRemote = remote; +} +@Override public android.os.IBinder asBinder() +{ +return mRemote; +} +public java.lang.String getInterfaceDescriptor() +{ +return DESCRIPTOR; +} +//param params--> Map of params: +// "pathToFileResult"-->String with the path to where the result must be saved, including file name and extension +// -->if null, the track is passed via the return argument +// "maxRunningTime"-->String with a number of seconds for the routing timeout, default = 60 +// "trackFormat"-->[kml|gpx] default = gpx +// "lats"-->double[] array of latitudes; 2 values at least. +// "lons"-->double[] array of longitudes; 2 values at least. +// "nogoLats"-->double[] array of nogo latitudes; may be null. +// "nogoLons"-->double[] array of nogo longitudes; may be null. +// "nogoRadi"-->double[] array of nogo radius in meters; may be null. +// "fast"-->[0|1] +// "v"-->[motorcar|bicycle|foot] +//return null if all ok and no path given, the track if ok and path given, an error message if it was wrong +//call in a background thread, heavy task! + +@Override public java.lang.String getTrackFromParams(android.os.Bundle params) throws android.os.RemoteException +{ +android.os.Parcel _data = android.os.Parcel.obtain(); +android.os.Parcel _reply = android.os.Parcel.obtain(); +java.lang.String _result; +try { +_data.writeInterfaceToken(DESCRIPTOR); +if ((params!=null)) { +_data.writeInt(1); +params.writeToParcel(_data, 0); +} +else { +_data.writeInt(0); +} +mRemote.transact(Stub.TRANSACTION_getTrackFromParams, _data, _reply, 0); +_reply.readException(); +_result = _reply.readString(); +} +finally { +_reply.recycle(); +_data.recycle(); +} +return _result; +} +} +static final int TRANSACTION_getTrackFromParams = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0); +} +//param params--> Map of params: +// "pathToFileResult"-->String with the path to where the result must be saved, including file name and extension +// -->if null, the track is passed via the return argument +// "maxRunningTime"-->String with a number of seconds for the routing timeout, default = 60 +// "trackFormat"-->[kml|gpx] default = gpx +// "lats"-->double[] array of latitudes; 2 values at least. +// "lons"-->double[] array of longitudes; 2 values at least. +// "nogoLats"-->double[] array of nogo latitudes; may be null. +// "nogoLons"-->double[] array of nogo longitudes; may be null. +// "nogoRadi"-->double[] array of nogo radius in meters; may be null. +// "fast"-->[0|1] +// "v"-->[motorcar|bicycle|foot] +//return null if all ok and no path given, the track if ok and path given, an error message if it was wrong +//call in a background thread, heavy task! + +public java.lang.String getTrackFromParams(android.os.Bundle params) throws android.os.RemoteException; +} diff --git a/brouter-routing-app/gen/btools/routingapp/R.java b/brouter-routing-app/gen/btools/routingapp/R.java new file mode 100644 index 0000000..cf5a5e4 --- /dev/null +++ b/brouter-routing-app/gen/btools/routingapp/R.java @@ -0,0 +1,22 @@ +/* AUTO-GENERATED FILE. DO NOT MODIFY. + * + * This class was automatically generated by the + * aapt tool from the resource data it found. It + * should not be modified by hand. + */ + +package btools.routingapp; + +public final class R { + public static final class attr { + } + public static final class drawable { + public static final int icon=0x7f020000; + } + public static final class layout { + public static final int main=0x7f030000; + } + public static final class string { + public static final int app_name=0x7f040000; + } +} diff --git a/brouter-routing-app/pom.xml b/brouter-routing-app/pom.xml new file mode 100644 index 0000000..b72774c --- /dev/null +++ b/brouter-routing-app/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-routing-app + apk + + + + com.google.android + android + 4.1.1.4 + provided + + + org.btools + brouter-core + ${project.version} + + + + + + + com.jayway.maven.plugins.android.generation2 + android-maven-plugin + + + + diff --git a/brouter-routing-app/project b/brouter-routing-app/project new file mode 100644 index 0000000..951383c --- /dev/null +++ b/brouter-routing-app/project @@ -0,0 +1,33 @@ + + + AccelerometerPlay + + + + + + com.android.ide.eclipse.adt.ResourceManagerBuilder + + + + + com.android.ide.eclipse.adt.PreCompilerBuilder + + + + + org.eclipse.jdt.core.javabuilder + + + + + com.android.ide.eclipse.adt.ApkBuilder + + + + + + com.android.ide.eclipse.adt.AndroidNature + org.eclipse.jdt.core.javanature + + diff --git a/brouter-routing-app/project.properties b/brouter-routing-app/project.properties new file mode 100644 index 0000000..f049142 --- /dev/null +++ b/brouter-routing-app/project.properties @@ -0,0 +1,11 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "ant.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-10 diff --git a/brouter-routing-app/res/drawable-hdpi/icon.png b/brouter-routing-app/res/drawable-hdpi/icon.png new file mode 100644 index 0000000..5d890ca Binary files /dev/null and b/brouter-routing-app/res/drawable-hdpi/icon.png differ diff --git a/brouter-routing-app/res/drawable-ldpi/icon.png b/brouter-routing-app/res/drawable-ldpi/icon.png new file mode 100644 index 0000000..811b686 Binary files /dev/null and b/brouter-routing-app/res/drawable-ldpi/icon.png differ diff --git a/brouter-routing-app/res/drawable-mdpi/icon.png b/brouter-routing-app/res/drawable-mdpi/icon.png new file mode 100644 index 0000000..88e31c1 Binary files /dev/null and b/brouter-routing-app/res/drawable-mdpi/icon.png differ diff --git a/brouter-routing-app/res/layout/main.xml b/brouter-routing-app/res/layout/main.xml new file mode 100644 index 0000000..8166ca6 --- /dev/null +++ b/brouter-routing-app/res/layout/main.xml @@ -0,0 +1,22 @@ + + + + + diff --git a/brouter-routing-app/res/values/strings.xml b/brouter-routing-app/res/values/strings.xml new file mode 100644 index 0000000..3f03430 --- /dev/null +++ b/brouter-routing-app/res/values/strings.xml @@ -0,0 +1,19 @@ + + + + + BRouter + diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java new file mode 100644 index 0000000..431dcf8 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterActivity.java @@ -0,0 +1,375 @@ +package btools.routingapp; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorManager; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.PowerManager.WakeLock; +import android.speech.tts.TextToSpeech.OnInitListener; +import android.view.Display; +import android.view.WindowManager; +import android.widget.EditText; +import btools.router.OsmNodeNamed; + +public class BRouterActivity extends Activity implements OnInitListener { + + private static final int DIALOG_SELECTPROFILE_ID = 1; + private static final int DIALOG_EXCEPTION_ID = 2; + private static final int DIALOG_WARNEXPIRY_ID = 3; + private static final int DIALOG_TEXTENTRY_ID = 4; + private static final int DIALOG_VIASELECT_ID = 5; + private static final int DIALOG_NOGOSELECT_ID = 6; + private static final int DIALOG_SHOWRESULT_ID = 7; + private static final int DIALOG_ROUTINGMODES_ID = 8; + private static final int DIALOG_MODECONFIGOVERVIEW_ID = 9; + private static final int DIALOG_PICKWAYPOINT_ID = 10; + + private BRouterView mBRouterView; + private PowerManager mPowerManager; + private WindowManager mWindowManager; + private Display mDisplay; + private WakeLock mWakeLock; + + /** Called when the activity is first created. */ + @Override + @SuppressWarnings("deprecation") + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Get an instance of the PowerManager + mPowerManager = (PowerManager) getSystemService(POWER_SERVICE); + + // Get an instance of the WindowManager + mWindowManager = (WindowManager) getSystemService(WINDOW_SERVICE); + mDisplay = mWindowManager.getDefaultDisplay(); + + // Create a bright wake lock + mWakeLock = mPowerManager.newWakeLock(PowerManager.SCREEN_BRIGHT_WAKE_LOCK, getClass() + .getName()); + + // instantiate our simulation view and set it as the activity's content + mBRouterView = new BRouterView(this); + setContentView(mBRouterView); + } + + @Override + @SuppressWarnings("deprecation") + protected Dialog onCreateDialog(int id) + { + AlertDialog.Builder builder; + switch(id) + { + case DIALOG_SELECTPROFILE_ID: + builder = new AlertDialog.Builder(this); + builder.setTitle("Select a routing profile"); + builder.setItems(availableProfiles, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + selectedProfile = availableProfiles[item]; + mBRouterView.startProcessing(selectedProfile); + } + }); + return builder.create(); + case DIALOG_ROUTINGMODES_ID: + builder = new AlertDialog.Builder(this); + builder.setTitle( message ); + builder.setMultiChoiceItems(routingModes, routingModesChecked, new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + routingModesChecked[which] = isChecked; + } + }); + builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + mBRouterView.configureService(routingModes,routingModesChecked); + } + }); + return builder.create(); + case DIALOG_EXCEPTION_ID: + builder = new AlertDialog.Builder(this); + builder.setTitle( "An Error occured" ) + .setMessage( errorMessage ) + .setPositiveButton( "OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mBRouterView.continueProcessing(); + } + }); + return builder.create(); + case DIALOG_WARNEXPIRY_ID: + builder = new AlertDialog.Builder(this); + builder.setMessage( errorMessage ) + .setPositiveButton( "OK", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + mBRouterView.startProcessing(selectedProfile); + } + }); + return builder.create(); + case DIALOG_TEXTENTRY_ID: + builder = new AlertDialog.Builder(this); + builder.setTitle("Enter SDCARD base dir:"); + builder.setMessage(message); + final EditText input = new EditText(this); + input.setText( defaultbasedir ); + builder.setView(input); + builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String basedir = input.getText().toString(); + mBRouterView.startSetup(basedir, true ); + } + }); + return builder.create(); + case DIALOG_VIASELECT_ID: + builder = new AlertDialog.Builder(this); + builder.setTitle("Check VIA Selection:"); + builder.setMultiChoiceItems(availableVias, getCheckedBooleanArray( availableVias.length ), + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + if (isChecked) + { + selectedVias.add(availableVias[which]); + } + else + { + selectedVias.remove(availableVias[which]); + } + } + }); + builder.setPositiveButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + mBRouterView.updateViaList( selectedVias ); + mBRouterView.startProcessing(selectedProfile); + } + }); + return builder.create(); + case DIALOG_NOGOSELECT_ID: + builder = new AlertDialog.Builder(this); + builder.setTitle("Check NoGo Selection:"); + String[] nogoNames = new String[nogoList.size()]; + for( int i=0; i 0 ? "Select to/via" : "Select from" ); + builder.setItems(availableWaypoints, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + mBRouterView.updateWaypointList( availableWaypoints[item] ); + mBRouterView.startProcessing(selectedProfile); + } + }); + return builder.create(); + + default: + return null; + } + + } + + private boolean[] getCheckedBooleanArray( int size ) + { + boolean[] checked = new boolean[size]; + for( int i=0; i selectedVias; + + private List nogoList; + + @SuppressWarnings("deprecation") + public void selectProfile( String[] items ) + { + availableProfiles = items; + showDialog( DIALOG_SELECTPROFILE_ID ); + } + + @SuppressWarnings("deprecation") + public void selectRoutingModes( String[] modes, boolean[] modesChecked, String message ) + { + routingModes = modes; + routingModesChecked = modesChecked; + this.message = message; + showDialog( DIALOG_ROUTINGMODES_ID ); + } + + @SuppressWarnings("deprecation") + public void showModeConfigOverview( String message ) + { + this.message = message; + showDialog( DIALOG_MODECONFIGOVERVIEW_ID ); + } + + + @SuppressWarnings("deprecation") + public void selectBasedir( String defaultBasedir, String message ) + { + this.defaultbasedir = defaultBasedir; + this.message = message; + showDialog( DIALOG_TEXTENTRY_ID ); + } + + @SuppressWarnings("deprecation") + public void selectVias( String[] items ) + { + availableVias = items; + selectedVias = new HashSet(availableVias.length); + for( String via : items ) selectedVias.add( via ); + showDialog( DIALOG_VIASELECT_ID ); + } + + @SuppressWarnings("deprecation") + public void selectWaypoint( String[] items ) + { + availableWaypoints = items; + showNewDialog( DIALOG_PICKWAYPOINT_ID ); + } + + @SuppressWarnings("deprecation") + public void selectNogos( List nogoList ) + { + this.nogoList = nogoList; + showDialog( DIALOG_NOGOSELECT_ID ); + } + + private Set dialogIds = new HashSet(); + + private void showNewDialog( int id ) + { + if ( dialogIds.contains( new Integer( id ) ) ) + { + removeDialog( id ); + } + dialogIds.add( new Integer( id ) ); + showDialog( id ); + } + + private String errorMessage; + private String title; + private int wpCount; + + @SuppressWarnings("deprecation") + public void showErrorMessage( String msg ) + { + errorMessage = msg; + showNewDialog( DIALOG_EXCEPTION_ID ); + } + + @SuppressWarnings("deprecation") + public void showResultMessage( String title, String msg, int wpCount ) + { + errorMessage = msg; + this.title = title; + this.wpCount = wpCount; + showNewDialog( DIALOG_SHOWRESULT_ID ); + } + + @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(); + + // Start the simulation + mBRouterView.startSimulation(); + + + } + + @Override + protected void onPause() { + super.onPause(); + /* + * When the activity is paused, we make sure to stop the simulation, + * release our sensor resources and wake locks + */ + + // Stop the simulation + mBRouterView.stopSimulation(); + + // and release our wake-lock + mWakeLock.release(); + + + } + + @Override + public void onInit(int i) + { + } + +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java new file mode 100644 index 0000000..230170a --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterService.java @@ -0,0 +1,146 @@ +package btools.routingapp; + + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.StringTokenizer; + +import android.app.Service; +import android.content.Intent; +import android.os.Bundle; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import btools.router.OsmNodeNamed; + +public class BRouterService extends Service +{ + + @Override + public IBinder onBind(Intent arg0) { + Log.d(getClass().getSimpleName(), "onBind()"); + return myBRouterServiceStub; + } + + private IBRouterService.Stub myBRouterServiceStub = new IBRouterService.Stub() + { + @Override + public String getTrackFromParams(Bundle params) throws RemoteException + { + BRouterWorker worker = new BRouterWorker(); + + // get base dir from private file + String baseDir = null; + InputStream configInput = null; + try + { + configInput = openFileInput( "config.dat" ); + BufferedReader br = new BufferedReader( new InputStreamReader (configInput ) ); + baseDir = br.readLine(); + } + catch( Exception e ) {} + finally + { + if ( configInput != null ) try { configInput.close(); } catch( Exception ee ) {} + } + + String fast = params.getString( "fast" ); + boolean isFast = "1".equals( fast ) || "true".equals( fast ) || "yes".equals( fast ); + String mode_key = params.getString( "v" ) + "_" + (isFast ? "fast" : "short"); + + boolean configFound = false; + + BufferedReader br = null; + try + { + String modesFile = baseDir + "/brouter/modes/serviceconfig.dat"; + br = new BufferedReader( new FileReader (modesFile ) ); + worker.segmentDir = baseDir + "/brouter/segments2"; + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + ServiceModeConfig smc = new ServiceModeConfig( line ); + if ( !smc.mode.equals( mode_key ) ) continue; + worker.profilePath = baseDir + "/brouter/profiles2/" + smc.profile + ".brf"; + worker.rawTrackPath = baseDir + "/brouter/modes/" + mode_key + "_rawtrack.dat"; + + CoordinateReader cor = CoordinateReader.obtainValidReader( baseDir ); + worker.nogoList = new ArrayList(); + // veto nogos by profiles veto list + for(OsmNodeNamed nogo : cor.nogopoints ) + { + if ( !smc.nogoVetos.contains( nogo.ilon + "," + nogo.ilat ) ) + { + worker.nogoList.add( nogo ); + } + } + configFound = true; + } + } + catch( Exception e ) + { + return "no brouter service config found, mode " + mode_key; + } + finally + { + if ( br != null ) try { br.close(); } catch( Exception ee ) {} + } + + if ( !configFound ) + { + return "no brouter service config found for mode " + mode_key; + } + + try + { + return worker.getTrackFromParams(params); + } + catch( IllegalArgumentException iae ) + { + return iae.getMessage(); + } + } + }; + + @Override + public void onCreate() + { + super.onCreate(); + Log.d(getClass().getSimpleName(),"onCreate()"); + } + + @Override + public void onDestroy() + { + super.onDestroy(); + Log.d(getClass().getSimpleName(),"onDestroy()"); + } + + + // This is the old onStart method that will be called on the pre-2.0 + // platform. On 2.0 or later we override onStartCommand() so this + // method will not be called. + @Override + @SuppressWarnings("deprecation") + public void onStart(Intent intent, int startId) + { + Log.d(getClass().getSimpleName(), "onStart()"); + handleStart(intent, startId); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) + { + handleStart(intent, startId); + return START_STICKY; + } + + void handleStart(Intent intent, int startId) + { + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java new file mode 100644 index 0000000..4890792 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterView.java @@ -0,0 +1,760 @@ +package btools.routingapp; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.os.Environment; +import android.util.DisplayMetrics; +import android.view.View; +import android.widget.Toast; +import btools.expressions.BExpressionContext; +import btools.mapaccess.OsmNode; +import btools.router.OsmNodeNamed; +import btools.router.OsmTrack; +import btools.router.RoutingContext; +import btools.router.RoutingEngine; + +public class BRouterView extends View +{ + RoutingEngine cr; + private int imgw; + private int imgh; + + private int centerLon; + private int centerLat; + private double scaleLon; + private double scaleLat; + private List wpList; + private List nogoList; + private List nogoVetoList; + private OsmTrack rawTrack; + + private String modesDir; + private String tracksDir; + private String segmentDir; + private String profileDir; + private String profilePath; + private String profileName; + private String sourceHint; + private boolean waitingForSelection = false; + + private boolean needsViaSelection; + private boolean needsNogoSelection; + private boolean needsWaypointSelection; + + private long lastDataTime = System.currentTimeMillis(); + + private CoordinateReader cor; + + private int[] imgPixels; + + public void startSimulation() { + } + + public void stopSimulation() { + if ( cr != null ) cr.terminate(); + } + + public BRouterView(Context context) { + super(context); + + DisplayMetrics metrics = new DisplayMetrics(); + ((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(metrics); + imgw = metrics.widthPixels; + imgh = metrics.heightPixels; + + // get base dir from private file + String baseDir = null; + InputStream configInput = null; + try + { + configInput = getContext().openFileInput( "config.dat" ); + BufferedReader br = new BufferedReader( new InputStreamReader (configInput ) ); + baseDir = br.readLine(); + } + catch( Exception e ) {} + finally + { + if ( configInput != null ) try { configInput.close(); } catch( Exception ee ) {} + } + // check if valid + boolean bdValid = false; + if ( baseDir != null ) + { + File bd = new File( baseDir ); + bdValid = bd.isDirectory(); + File brd = new File( bd, "brouter" ); + if ( brd.isDirectory() ) + { + startSetup( baseDir, false ); + return; + } + } + String message = baseDir == null ? + "(no basedir configured previously)" : + "(previous basedir " + baseDir + + ( bdValid ? " does not contain 'brouter' subfolder)" + : " is not valid)" ); + + ((BRouterActivity)getContext()).selectBasedir( guessBaseDir(), message ); + waitingForSelection = true; + } + + public void startSetup( String baseDir, boolean storeBasedir ) + { + File fbd = new File( baseDir ); + if ( !fbd.isDirectory() ) + { + throw new IllegalArgumentException( "Base-directory " + baseDir + " is not a directory " ); + } + String basedir = fbd.getAbsolutePath(); + + if ( storeBasedir ) + { + BufferedWriter bw = null; + try + { + OutputStream configOutput = getContext().openFileOutput( "config.dat", Context.MODE_PRIVATE ); + bw = new BufferedWriter( new OutputStreamWriter (configOutput ) ); + bw.write( baseDir ); + bw.write( '\n' ); + } + catch( Exception e ) {} + finally + { + if ( bw != null ) try { bw.close(); } catch( Exception ee ) {} + } + } + + cor = null; + try + { + // create missing directories + assertDirectoryExists( "project directory", basedir + "/brouter" ); + segmentDir = basedir + "/brouter/segments2"; + assertDirectoryExists( "map directory", segmentDir ); + profileDir = basedir + "/brouter/profiles2"; + assertDirectoryExists( "profile directory", profileDir ); + modesDir = basedir + "/brouter/modes"; + assertDirectoryExists( "modes directory", modesDir ); + + cor = CoordinateReader.obtainValidReader( basedir ); + wpList = cor.waypoints; + nogoList = cor.nogopoints; + nogoVetoList = new ArrayList(); + + sourceHint = "(coordinate-source: " + cor.basedir + cor.rootdir + ")"; + + needsViaSelection = wpList.size() > 2; + needsNogoSelection = nogoList.size() > 0; + needsWaypointSelection = wpList.size() == 0; + + if ( cor.tracksdir != null ) + { + tracksDir = cor.basedir + cor.tracksdir; + assertDirectoryExists( "track directory", tracksDir ); + + // output redirect: look for a pointerfile in tracksdir + File tracksDirPointer = new File( tracksDir + "/brouter.redirect" ); + if ( tracksDirPointer.isFile() ) + { + tracksDir = readSingleLineFile( tracksDirPointer ); + if ( tracksDir == null ) throw new IllegalArgumentException( "redirect pointer file is empty: " + tracksDirPointer ); + if ( !(new File( tracksDir ).isDirectory()) ) throw new IllegalArgumentException( + "redirect pointer file " + tracksDirPointer + " does not point to a directory: " + tracksDir ); + } + } + + boolean segmentFound = false; + String[] fileNames = new File( segmentDir ).list(); + for( String fileName : fileNames ) + { + if ( fileName.endsWith( ".rd5" ) ) segmentFound = true; + } + File carSubset = new File( segmentDir, "carsubset" ); + if ( carSubset.isDirectory() ) + { + fileNames = carSubset.list(); + for( String fileName : fileNames ) + { + if ( fileName.endsWith( ".cd5" ) ) segmentFound = true; + } + } + if ( !segmentFound ) + { + throw new IllegalArgumentException( "The segments-directory " + segmentDir + + " contains no routing data files (*.rd5)." + + " see www.dr-brenschede.de/brouter for setup instructions." ); + } + + fileNames = new File( profileDir ).list(); + ArrayList profiles = new ArrayList(); + + boolean lookupsFound = false; + for( String fileName : fileNames ) + { + if ( fileName.endsWith( ".brf" ) ) + { + profiles.add( fileName.substring( 0, fileName.length()-4 ) ); + } + if ( fileName.equals( "lookups.dat" ) ) lookupsFound = true; + } + if ( !lookupsFound ) + { + throw new IllegalArgumentException( "The profile-directory " + profileDir + + " does not contain the lookups.dat file." + + " see www.dr-brenschede.de/brouter for setup instructions." ); + } + if ( profiles.size() == 0 ) + { + throw new IllegalArgumentException( "The profile-directory " + profileDir + + " contains no routing profiles (*.brf)." + + " see www.dr-brenschede.de/brouter for setup instructions." ); + } + ((BRouterActivity)getContext()).selectProfile( profiles.toArray( new String[0]) ); + } + catch( Exception e ) + { + String msg = e instanceof IllegalArgumentException + ? e.getMessage() + ( cor == null ? "" : " (coordinate-source: " + cor.basedir + cor.rootdir + ")" ) + : e.toString(); + ((BRouterActivity)getContext()).showErrorMessage( msg ); + } + waitingForSelection = true; + } + + public void continueProcessing() + { + waitingForSelection = false; + invalidate(); + } + + public void updateViaList( Set selectedVias ) + { + ArrayList filtered = new ArrayList(wpList.size()); + for( OsmNodeNamed n : wpList ) + { + String name = n.name; + if ( "from".equals( name ) || "to".equals(name) || selectedVias.contains( name ) ) + filtered.add( n ); + } + wpList = filtered; + } + + public void updateNogoList( boolean[] enabled ) + { + for( int i=nogoList.size()-1; i >= 0; i-- ) + { + if ( !enabled[i] ) + { + nogoVetoList.add( nogoList.get(i) ); + nogoList.remove( i ); + } + } + } + + public void pickWaypoints() + { + String msg = null; + + Map allpoints = cor.allpoints; + if ( allpoints == null ) + { + allpoints = new TreeMap(); + cor.allpoints = allpoints; + try { cor.readFromTo(); } catch ( Exception e ) { msg = "Error reading waypoints: " + e.toString(); } + if ( allpoints.size() < 2 ) msg = "coordinate source does not contain enough waypoints: " + allpoints.size(); + if ( allpoints.size() > 100 ) msg = "coordinate source contains too much waypoints: " + allpoints.size() + "(please use from/to/via names)"; + } + if ( allpoints.size() < 1 ) msg = "no more wayoints available!"; + + if ( msg != null ) + { + ((BRouterActivity)getContext()).showErrorMessage( msg ); + } + else + { + String[] wpts = new String[allpoints.size()]; + int i = 0; + for( OsmNodeNamed wp : allpoints.values() ) wpts[i++] = wp.name; +System.out.println( "calling selectWaypoint..." ); + ((BRouterActivity)getContext()).selectWaypoint( wpts ); + } + } + + public void updateWaypointList( String waypoint ) + { + wpList.add( cor.allpoints.get( waypoint ) ); + cor.allpoints.remove( waypoint ); +System.out.println( "updateWaypointList: " + waypoint + " wpList.size()=" + wpList.size() ); + } + + public void finishWaypointSelection() + { + needsWaypointSelection = false; + } + + public void startProcessing( String profile ) + { + profilePath = profileDir + "/" + profile + ".brf"; + profileName = profile; + + if ( needsViaSelection ) + { + needsViaSelection = false; + String[] availableVias = new String[wpList.size()-2]; + for( int viaidx=0; viaidx0?"->" : "") + wpList.get(i).name; + } + ((BRouterActivity)getContext()).showResultMessage( "Select Action", msg, wpList.size() ); + return; + } + + try + { + waitingForSelection = false; + + RoutingContext rc = new RoutingContext(); + + // TODO: TEST! + // rc.rawTrackPath = "/mnt/sdcard/brouter/modes/bicycle_fast_rawtrack.dat"; + + rc.localFunction = profilePath; + + int plain_distance = 0; + int maxlon = Integer.MIN_VALUE; + int minlon = Integer.MAX_VALUE; + int maxlat = Integer.MIN_VALUE; + int minlat = Integer.MAX_VALUE; + + OsmNode prev = null; + for( OsmNode n : wpList ) + { + maxlon = n.ilon > maxlon ? n.ilon : maxlon; + minlon = n.ilon < minlon ? n.ilon : minlon; + maxlat = n.ilat > maxlat ? n.ilat : maxlat; + minlat = n.ilat < minlat ? n.ilat : minlat; + if ( prev != null ) + { + plain_distance += n.calcDistance( prev ); + } + prev = n; + } + toast( "Plain distance = " + plain_distance/1000. + " km" ); + + centerLon = (maxlon + minlon)/2; + centerLat = (maxlat + minlat)/2; + + double coslat = Math.cos( ((centerLat / 1000000.) - 90.) / 57.3 ) ; + double difflon = maxlon - minlon; + double difflat = maxlat - minlat; + + scaleLon = imgw / (difflon*1.5); + scaleLat = imgh / (difflat*1.5); + if ( scaleLon < scaleLat*coslat ) scaleLat = scaleLon/coslat; + else scaleLon = scaleLat*coslat; + + startTime = System.currentTimeMillis(); + rc.prepareNogoPoints( nogoList ); + rc.nogopoints = nogoList; + + cr = new RoutingEngine( tracksDir + "/brouter", null, segmentDir, wpList, rc ); + cr.start(); + invalidate(); + + } + catch( Exception e ) + { + String msg = e instanceof IllegalArgumentException ? e.getMessage() : e.toString(); + toast( msg ); + } + } + + private void assertDirectoryExists( String message, String path ) + { + File f = new File( path ); + f.mkdirs(); + if ( !f.exists() || !f.isDirectory() ) throw new IllegalArgumentException( message + ": " + path + " cannot be created" ); + } + + private void paintPosition( int ilon, int ilat, int color, int with ) + { + int lon = ilon - centerLon; + int lat = ilat - centerLat; + int x = imgw/2 + (int)(scaleLon*lon); + int y = imgh/2 - (int)(scaleLat*lat); + for( int nx=x-with; nx<=x+with; nx++) + for( int ny=y-with; ny<=y+with; ny++) + { + if ( nx >= 0 && nx < imgw && ny >= 0 && ny < imgh ) + { + imgPixels[ nx+imgw*ny] = color; + } + } + } + + private void paintCircle( Canvas canvas, OsmNodeNamed n, int color, int minradius ) + { + int lon = n.ilon - centerLon; + int lat = n.ilat - centerLat; + int x = imgw/2 + (int)(scaleLon*lon); + int y = imgh/2 - (int)(scaleLat*lat); + int ir = (int)(n.radius * 1000000. * scaleLat); + if ( ir > minradius ) + { + Paint paint = new Paint(); + paint.setColor( Color.RED ); + paint.setStyle( Paint.Style.STROKE ); + canvas.drawCircle( (float)x, (float)y, (float)ir, paint ); + } + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + } + + private void toast( String msg ) + { + Toast.makeText(getContext(), msg, Toast.LENGTH_LONG ).show(); + lastDataTime += 4000; // give time for the toast before exiting + } + + +private long lastTs = System.currentTimeMillis(); +private long startTime = 0L; + + @Override + protected void onDraw(Canvas canvas) { + try + { + _onDraw( canvas ); + } + catch( Throwable t ) + { + // on out of mem, try to stop the show + String hint = ""; + if ( cr != null ) hint = cr.cleanOnOOM(); + cr = null; + try { Thread.sleep( 2000 ); } catch( InterruptedException ie ) {} + ((BRouterActivity)getContext()).showErrorMessage( t.toString() + hint ); + waitingForSelection = true; + } + } + + private void _onDraw(Canvas canvas) { + + if ( waitingForSelection ) return; + + long currentTs = System.currentTimeMillis(); + long diffTs = currentTs - lastTs; + long sleeptime = 500 - diffTs; + while ( sleeptime < 200 ) sleeptime += 500; + + try { Thread.sleep( sleeptime ); } catch ( InterruptedException ie ) {} + lastTs = System.currentTimeMillis(); + + if ( cr == null || cr.isFinished() ) + { + if ( cr != null ) + { + if ( cr.getErrorMessage() != null ) + { + ((BRouterActivity)getContext()).showErrorMessage( cr.getErrorMessage() ); + cr = null; + waitingForSelection = true; + return; + } + else + { + String result = "version = BRouter-0.98\n" + + "distance = " + cr.getDistance()/1000. + " km\n" + + "filtered ascend = " + cr.getAscend() + " m\n" + + "plain ascend = " + cr.getPlainAscend(); + + rawTrack = cr.getFoundRawTrack(); + + String title = "Success"; + if ( cr.getAlternativeIndex() > 0 ) title += " / " + cr.getAlternativeIndex() + ". Alternative"; + + ((BRouterActivity)getContext()).showResultMessage( title, result, -1 ); + cr = null; + waitingForSelection = true; + return; + } + } + else if ( System.currentTimeMillis() > lastDataTime ) + { + System.exit(0); + } + } + else + { + lastDataTime = System.currentTimeMillis(); + imgPixels = new int[imgw*imgh]; + + int[] openSet = cr.getOpenSet(); + for( int si = 0; si < openSet.length; si += 2 ) + { + paintPosition( openSet[si], openSet[si+1], 0xffffff, 1 ); + } + // paint nogos on top (red) + for( int ngi=0; ngi basedirGuesses = new ArrayList(); + basedirGuesses.add( basedir.getAbsolutePath() ); + + if ( bd2.exists() ) + { + basedir = bd2; + basedirGuesses.add( basedir.getAbsolutePath() ); + } + + ArrayList rl = new ArrayList(); + for( String bdg : basedirGuesses ) + { + rl.add( new CoordinateReaderOsmAnd(bdg) ); + rl.add( new CoordinateReaderLocus(bdg) ); + rl.add( new CoordinateReaderOrux(bdg) ); + } + long tmax = 0; + CoordinateReader cor = null; + for( CoordinateReader r : rl ) + { + long t = r.getTimeStamp(); + if ( t > tmax ) + { + tmax = t; + cor = r; + } + } + if ( cor != null ) + { + return cor.basedir; + } + } + catch( Exception e ) + { + System.out.println( "guessBaseDir:" + e ); + } + return basedir.getAbsolutePath(); + } + + public void writeRawTrackToMode( String mode ) + { + // plus eventually the raw track for re-use + String rawTrackPath = modesDir + "/" + mode + "_rawtrack.dat"; + if ( rawTrack != null ) + { + try + { + rawTrack.writeBinary( rawTrackPath ); + } + catch( Exception e ) {} + } + else + { + new File( rawTrackPath ).delete(); + } + } + + public void startConfigureService() + { + String[] modes = new String[] { + "foot_short", "foot_fast", + "bicycle_short", "bicycle_fast", + "motorcar_short", "motorcar_fast" + }; + boolean[] modesChecked = new boolean[6]; + + // parse global section of profile for mode preselection + BExpressionContext expctxGlobal = new BExpressionContext( "global" ); + expctxGlobal.readMetaData( new File( profileDir, "lookups.dat" ) ); + expctxGlobal.parseFile( new File( profilePath ), null ); + expctxGlobal.evaluate( 1L, null ); + boolean isFoot = 0.f != expctxGlobal.getVariableValue( "validForFoot" ); + boolean isBike = 0.f != expctxGlobal.getVariableValue( "validForBikes" ); + boolean isCar = 0.f != expctxGlobal.getVariableValue( "validForCars" ); + + if ( isFoot || isBike || isCar ) + { + modesChecked[ 0 ] = isFoot; + modesChecked[ 1 ] = isFoot; + modesChecked[ 2 ] = isBike; + modesChecked[ 3 ] = isBike; + modesChecked[ 4 ] = isCar; + modesChecked[ 5 ] = isCar; + } + else + { + for( int i=0; i<6; i++) + { + modesChecked[i] = true; + } + } + String msg = "Choose service-modes to configure (" + profileName + " [" + nogoVetoList.size() + "])"; + + ((BRouterActivity)getContext()).selectRoutingModes( modes, modesChecked, msg ); + } + + public void configureService(String[] routingModes, boolean[] checkedModes) + { + // read in current config + TreeMap map = new TreeMap(); + BufferedReader br = null; + String modesFile = modesDir + "/serviceconfig.dat"; + try + { + br = new BufferedReader( new FileReader (modesFile ) ); + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + ServiceModeConfig smc = new ServiceModeConfig( line ); + map.put( smc.mode, smc ); + } + } + catch( Exception e ) {} + finally + { + if ( br != null ) try { br.close(); } catch( Exception ee ) {} + } + + // replace selected modes + for( int i=0; i<6; i++) + { + if ( checkedModes[i] ) + { + writeRawTrackToMode( routingModes[i] ); + ServiceModeConfig smc = new ServiceModeConfig( routingModes[i], profileName); + for( OsmNodeNamed nogo : nogoVetoList) + { + smc.nogoVetos.add( nogo.ilon + "," + nogo.ilat ); + } + map.put( smc.mode, smc ); + } + } + + + // no write new config + BufferedWriter bw = null; + StringBuilder msg = new StringBuilder( "Mode mapping is now:\n" ); + msg.append( "( [..] counts nogo-vetos)\n" ); + try + { + bw = new BufferedWriter( new FileWriter ( modesFile ) ); + for( ServiceModeConfig smc : map.values() ) + { + bw.write( smc.toLine() ); + bw.write( '\n' ); + msg.append( smc.toString() ).append( '\n' ); + } + } + catch( Exception e ) {} + finally + { + if ( bw != null ) try { bw.close(); } catch( Exception ee ) {} + } + ((BRouterActivity)getContext()).showModeConfigOverview( msg.toString() ); + } + + private String readSingleLineFile( File f ) + { + BufferedReader br = null; + try + { + br = new BufferedReader( new InputStreamReader ( new FileInputStream( f ) ) ); + return br.readLine(); + } + catch( Exception e ) { return null; } + finally + { + if ( br != null ) try { br.close(); } catch( Exception ee ) {} + } + } + +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java new file mode 100644 index 0000000..7f8f11b --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/BRouterWorker.java @@ -0,0 +1,139 @@ +package btools.routingapp; + + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +import android.os.Bundle; +import btools.router.RoutingEngine; +import btools.router.OsmNodeNamed; +import btools.router.OsmTrack; +import btools.router.RoutingContext; + +public class BRouterWorker +{ + public String segmentDir; + public String profilePath; + public String rawTrackPath; + public List nogoList; + + public String getTrackFromParams(Bundle params) + { + String pathToFileResult = params.getString("pathToFileResult"); + + if (pathToFileResult != null) + { + File f = new File (pathToFileResult); + File dir = f.getParentFile(); + if (!dir.exists() || !dir.canWrite()){ + return "file folder does not exists or can not be written!"; + } + } + + long maxRunningTime = 60000; + String sMaxRunningTime = params.getString( "maxRunningTime" ); + if ( sMaxRunningTime != null ) + { + maxRunningTime = Integer.parseInt( sMaxRunningTime ) * 1000; + } + + RoutingContext rc = new RoutingContext(); + rc.rawTrackPath = rawTrackPath; + rc.localFunction = profilePath; + if ( nogoList != null ) + { + rc.prepareNogoPoints( nogoList ); + rc.nogopoints = nogoList; + } + + readNogos( params ); // add interface provides nogos + + RoutingEngine cr = new RoutingEngine( null, null, segmentDir, readPositions(params), rc ); + cr.quite = true; + cr.doRun( maxRunningTime ); + if ( cr.getErrorMessage() != null ) + { + return cr.getErrorMessage(); + } + + // store new reference track if any + if ( cr.getFoundRawTrack() != null ) + { + try + { + cr.getFoundRawTrack().writeBinary( rawTrackPath ); + } + catch( Exception e ) {} + } + + + String format = params.getString("trackFormat"); + boolean writeKml = format != null && "kml".equals( format ); + + OsmTrack track = cr.getFoundTrack(); + if ( track != null ) + { + if ( pathToFileResult == null ) + { + if ( writeKml ) return track.formatAsKml(); + return track.formatAsGpx(); + } + try + { + if ( writeKml ) track.writeKml(pathToFileResult); + else track.writeGpx(pathToFileResult); + } + catch( Exception e ) + { + return "error writing file: " + e; + } + } + return null; + } + + private List readPositions( Bundle params ) + { + List wplist = new ArrayList(); + + double[] lats = params.getDoubleArray("lats"); + double[] lons = params.getDoubleArray("lons"); + + if (lats == null || lats.length < 2 || lons == null || lons.length < 2) + { + throw new IllegalArgumentException( "we need two lat/lon points at least!" ); + } + + for( int i=0; i waypoints; + public List nogopoints; + public String basedir; + public String rootdir; + public String tracksdir; + + public Map allpoints; + private HashMap pointmap; + + protected static String[] posnames + = new String[]{ "from", "via1", "via2", "via3", "via4", "via5", "via6", "via7", "via8", "via9", "to" }; + + public CoordinateReader( String basedir ) + { + this.basedir = basedir; + } + + public abstract long getTimeStamp() throws Exception; + + /* + * read the from, to and via-positions from a gpx-file + */ + public void readFromTo() throws Exception + { + pointmap = new HashMap(); + waypoints = new ArrayList(); + nogopoints = new ArrayList(); + readPointmap(); + boolean fromToMissing = false; + for( int i=0; i rl = new ArrayList(); + rl.add( new CoordinateReaderOsmAnd(basedir) ); + rl.add( new CoordinateReaderLocus(basedir) ); + rl.add( new CoordinateReaderOrux(basedir) ); + + // eventually add standard-sd + File standardbase = Environment.getExternalStorageDirectory(); + if ( standardbase != null ) + { + String base2 = standardbase.getAbsolutePath(); + if ( !base2.equals( basedir ) ) + { + rl.add( new CoordinateReaderOsmAnd(base2) ); + rl.add( new CoordinateReaderLocus(base2) ); + rl.add( new CoordinateReaderOrux(base2) ); + } + } + + long tmax = 0; + for( CoordinateReader r : rl ) + { + long t = r.getTimeStamp(); + if ( t > tmax ) + { + tmax = t; + cor = r; + } + } + if ( cor == null ) + { + cor = new CoordinateReaderNone(); + } + cor.readFromTo(); + return cor; + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java new file mode 100644 index 0000000..21de52c --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderLocus.java @@ -0,0 +1,53 @@ +package btools.routingapp; + +import java.io.File; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; +import btools.router.OsmNodeNamed; + +/** + * Read coordinates from a gpx-file + */ +public class CoordinateReaderLocus extends CoordinateReader +{ + public CoordinateReaderLocus( String basedir ) + { + super( basedir ); + tracksdir = "/Locus/mapItems"; + rootdir = "/Locus"; + } + + @Override + public long getTimeStamp() throws Exception + { + long t1 = new File( basedir + "/Locus/data/database/waypoints.db" ).lastModified(); + return t1; + } + + /* + * read the from and to position from a ggx-file + * (with hardcoded name for now) + */ + @Override + public void readPointmap() throws Exception + { + _readPointmap( basedir + "/Locus/data/database/waypoints.db" ); + } + + private void _readPointmap( String filename ) throws Exception + { + SQLiteDatabase myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY); + Cursor c = myDataBase.rawQuery("SELECT name, longitude, latitude FROM waypoints", null); + while (c.moveToNext()) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = c.getString(0); + n.ilon = (int)( ( Double.parseDouble( c.getString(1) ) + 180. )*1000000. + 0.5); + n.ilat = (int)( ( Double.parseDouble( c.getString(2) ) + 90. )*1000000. + 0.5); + checkAddPoint( n ); + } + myDataBase.close(); + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderNone.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderNone.java new file mode 100644 index 0000000..a92f7e6 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderNone.java @@ -0,0 +1,26 @@ +package btools.routingapp; + + +/** + * Dummy coordinate reader if none found + */ +public class CoordinateReaderNone extends CoordinateReader +{ + public CoordinateReaderNone() + { + super( "" ); + rootdir = "none"; + } + + @Override + public long getTimeStamp() throws Exception + { + return 0L; + } + + @Override + public void readPointmap() throws Exception + { + } + +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java new file mode 100644 index 0000000..36f1af6 --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOrux.java @@ -0,0 +1,52 @@ +package btools.routingapp; + +import java.io.File; + +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import btools.router.OsmNodeNamed; + +/** + * Read coordinates from a gpx-file + */ +public class CoordinateReaderOrux extends CoordinateReader +{ + public CoordinateReaderOrux( String basedir ) + { + super( basedir ); + tracksdir = "/oruxmaps/tracklogs"; + rootdir = "/oruxmaps"; + } + + @Override + public long getTimeStamp() throws Exception + { + long t1 = new File( basedir + "/oruxmaps/tracklogs/oruxmapstracks.db" ).lastModified(); + return t1; + } + + /* + * read the from and to position from a ggx-file + * (with hardcoded name for now) + */ + @Override + public void readPointmap() throws Exception + { + _readPointmap( basedir + "/oruxmaps/tracklogs/oruxmapstracks.db" ); + } + + private void _readPointmap( String filename ) throws Exception + { + SQLiteDatabase myDataBase = SQLiteDatabase.openDatabase( filename, null, SQLiteDatabase.OPEN_READONLY); + Cursor c = myDataBase.rawQuery("SELECT poiname, poilon, poilat FROM pois", null); + while (c.moveToNext()) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = c.getString(0); + n.ilon = (int)( ( Double.parseDouble( c.getString(1) ) + 180. )*1000000. + 0.5); + n.ilat = (int)( ( Double.parseDouble( c.getString(2) ) + 90. )*1000000. + 0.5); + checkAddPoint( n ); + } + myDataBase.close(); + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java new file mode 100644 index 0000000..bf8a22f --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/CoordinateReaderOsmAnd.java @@ -0,0 +1,87 @@ +package btools.routingapp; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; + +import btools.router.OsmNodeNamed; + +/** + * Read coordinates from a gpx-file + */ +public class CoordinateReaderOsmAnd extends CoordinateReader +{ + public CoordinateReaderOsmAnd( String basedir ) + { + super( basedir ); + tracksdir = "/osmand/tracks"; + rootdir = "/osmand"; + } + + @Override + public long getTimeStamp() throws Exception + { + long t1 = new File( basedir + "/osmand/favourites_bak.gpx" ).lastModified(); + long t2 = new File( basedir + "/osmand/favourites.gpx" ).lastModified(); + return t1 > t2 ? t1 : t2; + } + + /* + * read the from and to position from a gpx-file + * (with hardcoded name for now) + */ + @Override + public void readPointmap() throws Exception + { + try + { + _readPointmap( basedir + "/osmand/favourites_bak.gpx" ); + } + catch( Exception e ) + { + _readPointmap( basedir + "/osmand/favourites.gpx" ); + } + } + + private void _readPointmap( String filename ) throws Exception + { + BufferedReader br = new BufferedReader( + new InputStreamReader( + new FileInputStream( filename ) ) ); + OsmNodeNamed n = null; + + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + + int idx0 = line.indexOf( "" ); + if ( idx0 >= 0 ) + { + n = new OsmNodeNamed(); + idx0 += 10; + int idx1 = line.indexOf( '"', idx0 ); + n.ilat = (int)( (Double.parseDouble( line.substring( idx0, idx1 ) ) + 90. )*1000000. + 0.5); + int idx2 = line.indexOf( " lon=\"" ); + if ( idx2 < 0 ) continue; + idx2 += 6; + int idx3 = line.indexOf( '"', idx2 ); + n.ilon = (int)( ( Double.parseDouble( line.substring( idx2, idx3 ) ) + 180. )*1000000. + 0.5); + continue; + } + if ( n != null && idx10 >= 0 ) + { + idx10 += 6; + int idx11 = line.indexOf( "", idx10 ); + if ( idx11 >= 0 ) + { + n.name = line.substring( idx10, idx11 ).trim(); + checkAddPoint( n ); + } + } + } + br.close(); + } +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/IBRouterService.aidl b/brouter-routing-app/src/main/java/btools/routingapp/IBRouterService.aidl new file mode 100644 index 0000000..55b225f --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/IBRouterService.aidl @@ -0,0 +1,23 @@ +package btools.routingapp; + + +interface IBRouterService { + + + //param params--> Map of params: + // "pathToFileResult"-->String with the path to where the result must be saved, including file name and extension + // -->if null, the track is passed via the return argument + // "maxRunningTime"-->String with a number of seconds for the routing timeout, default = 60 + // "trackFormat"-->[kml|gpx] default = gpx + // "lats"-->double[] array of latitudes; 2 values at least. + // "lons"-->double[] array of longitudes; 2 values at least. + // "nogoLats"-->double[] array of nogo latitudes; may be null. + // "nogoLons"-->double[] array of nogo longitudes; may be null. + // "nogoRadi"-->double[] array of nogo radius in meters; may be null. + // "fast"-->[0|1] + // "v"-->[motorcar|bicycle|foot] + //return null if all ok and no path given, the track if ok and path given, an error message if it was wrong + //call in a background thread, heavy task! + + String getTrackFromParams(in Bundle params); +} diff --git a/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java b/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java new file mode 100644 index 0000000..99c36ad --- /dev/null +++ b/brouter-routing-app/src/main/java/btools/routingapp/ServiceModeConfig.java @@ -0,0 +1,50 @@ +package btools.routingapp; + +import java.util.StringTokenizer; +import java.util.TreeSet; + + +/** + * Decsription of a service config + */ +public class ServiceModeConfig +{ + public String mode; + public String profile; + public TreeSet nogoVetos; + + public ServiceModeConfig( String line ) + { + StringTokenizer tk = new StringTokenizer( line ); + mode = tk.nextToken(); + profile = tk.nextToken(); + nogoVetos = new TreeSet(); + while( tk.hasMoreTokens() ) + { + nogoVetos.add( tk.nextToken() ); + } + } + + public ServiceModeConfig( String mode, String profile ) + { + this.mode = mode; + this.profile = profile; + nogoVetos = new TreeSet(); + } + + public String toLine() + { + StringBuilder sb = new StringBuilder( 100 ); + sb.append( mode ).append( ' ' ).append( profile ); + for( String veto: nogoVetos ) sb.append( ' ' ).append( veto ); + return sb.toString(); + } + + public String toString() + { + StringBuilder sb = new StringBuilder( 100 ); + sb.append( mode ).append( "->" ).append( profile ); + sb.append ( " [" + nogoVetos.size() + "]" ); + return sb.toString(); + } +} diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/car-test.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/car-test.brf new file mode 100644 index 0000000..b2c2ce7 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/car-test.brf @@ -0,0 +1,105 @@ +# +# 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 + +---context:way # following code refers to way-tags + +assign turncost 200 +assign initialcost switch highway=ferry 20000 0 + + +# +# calculate logical car access +# +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 highway=ferry 1 + switch or highway=residential highway=living_street 1 + switch highway=service 1 + 0 + or access=yes or access=designated access=destination + or vehicle=yes or vehicle=designated vehicle=destination + or motor_vehicle=yes or motor_vehicle=designated motor_vehicle=destination + or motorcar=yes 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 or highway=motorway highway=motorway_link 1 + switch or highway=trunk highway=trunk_link 1 + switch or highway=primary highway=primary_link switch maxspeed=30 2.0 switch maxspeed=50 1.5 1.2 + switch or highway=secondary highway=secondary_link 1.3 + switch or highway=tertiary highway=tertiary_link 1.4 + switch highway=unclassified 1.5 + switch highway=ferry 5.67 + switch highway=bridleway 5 + switch or highway=residential highway=living_street 2 + switch highway=service 2 + switch or highway=track or highway=road highway=path + switch tracktype=grade1 5 + switch ispaved 5 + 30 + 10000 + +---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=designated access=destination + or vehicle=yes or vehicle=designated vehicle=destination + or motor_vehicle=yes or motor_vehicle=designated motor_vehicle=destination + or motorcar=yes or motorcar=designated motorcar=destination + +assign initialcost + switch caraccess + 0 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/fastbike.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/fastbike.brf new file mode 100644 index 0000000..47d605e --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/fastbike.brf @@ -0,0 +1,164 @@ +# +# A fastbike could be a racing bike or a speed pedelec. +# But also at night or in rainy whether you might want +# to fallback to this one. +# +# Structure is similar to trekking.brf, see this for documenation. +# + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 1 # set to 0 to ignore elevation in routing +assign allow_steps 1 # set to 0 to disallow steps +assign allow_ferries 1 # set to 0 to disallow ferries +assign ignore_cycleroutes 0 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 0 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---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 turncost 90 + +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 6 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 6.0 + 0.0 + +assign costfactor + + add max onewaypenalty accesspenalty + + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + switch or highway=trunk highway=trunk_link 10 + switch or highway=primary highway=primary_link 1.2 + switch or highway=secondary highway=secondary_link 1.1 + switch or highway=tertiary highway=tertiary_link 1.0 + switch highway=unclassified 1.1 + switch highway=pedestrian 10 + switch highway=steps 1000 + switch highway=ferry 5.67 + switch highway=bridleway 5 + switch highway=cycleway 1.3 + switch or highway=residential highway=living_street switch isunpaved 10 1.2 + switch highway=service switch isunpaved 10 1.2 + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch isunpaved 3 1.2 + switch tracktype=grade2 switch isunpaved 10 3 + switch tracktype=grade3 10.0 + switch tracktype=grade4 20.0 + switch tracktype=grade5 30.0 + switch ispaved 2.0 100.0 + 10.0 + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 300 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/lookups.dat b/brouter-routing-app/src/main/resources/brouter/profiles2/lookups.dat new file mode 100644 index 0000000..6bf1a31 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/lookups.dat @@ -0,0 +1,317 @@ +---lookupversion:2 + +---context:way + +highway;0001731794 track +highway;0001457935 residential +highway;0000968516 service +highway;0000756237 footway +highway;0000521566 path +highway;0000261772 unclassified +highway;0000220315 secondary +highway;0000207585 tertiary +highway;0000103445 steps +highway;0000102114 primary +highway;0000094484 cycleway +highway;0000090388 living_street +highway;0000035041 motorway +highway;0000029965 pedestrian +highway;0000026875 motorway_link +highway;0000015054 trunk +highway;0000014604 primary_link +highway;0000012211 road +highway;0000011822 trunk_link +highway;0000005882 construction +highway;0000005425 bridleway +highway;0000005180 secondary_link +highway;0000003360 platform +highway;0000002616 proposed abandoned +highway;0000001374 tertiary_link +highway;0000000760 ferry +highway;0000000541 raceway +highway;0000000346 rest_area +highway;0000000300 bus_stop +highway;0000000184 services + +tracktype;0000356503 grade2 +tracktype;0000353482 grade3 +tracktype;0000281625 grade1 +tracktype;0000245193 grade4 +tracktype;0000179135 grade5 + +surface;0000363915 asphalt +surface;0000303589 paved +surface;0000196783 gravel +surface;0000137371 ground +surface;0000128215 grass +surface;0000092748 unpaved +surface;0000086579 paving_stones +surface;0000066111 cobblestone +surface;0000042061 dirt +surface;0000026551 concrete +surface;0000025631 compacted +surface;0000019861 sand +surface;0000009400 pebblestone +surface;0000003197 fine_gravel + +maxspeed;0000402224 30 +maxspeed;0000224685 50 +maxspeed;0000045177 100 +maxspeed;0000037529 70 +maxspeed;0000014237 none +maxspeed;0000014022 60 +maxspeed;0000011530 80 +maxspeed;0000009951 10 +maxspeed;0000008056 20 +maxspeed;0000005772 120 +maxspeed;0000003165 40 +maxspeed;0000002987 7 +maxspeed;0000002826 signals +maxspeed;0000001933 130 + +service;0000221481 parking_aisle +service;0000157110 driveway + +lit;0000132495 yes + +lanes;0000098207 2 +lanes;0000042192 1 +lanes;0000018533 3 +lanes;0000004577 4 +lanes;0000000448 5 +lanes;0000000318 1.5 + +access;0000044859 yes permissive +access;0000008452 designated official +access;0000028727 destination customers +access;0000076985 agricultural forestry +access;0000116270 private +access;0000028044 no + +foot;0000339384 yes allowed Yes +foot;0000125339 designated official +foot;0000018945 no +foot;0000001562 private +foot;0000000279 destination +foot;0000008172 permissive + +bicycle;0000302789 yes allowed permissive +bicycle;0000108056 designated official +bicycle;0000000265 destination +bicycle;0000003593 dismount +bicycle;0000001426 private +bicycle;0000070179 no + +motorcar;0000010111 yes permissive +motorcar;0000001537 designated official +motorcar;0000007102 destination +motorcar;0000016706 agricultural forestry agriculture +motorcar;0000002178 private +motorcar;0000077771 no + +motor_vehicle;0000013813 yes permissive +motor_vehicle;0000002098 designated official +motor_vehicle;0000009792 destination +motor_vehicle;0000019301 agricultural forestry +motor_vehicle;0000006563 private +motor_vehicle;0000025491 no + +motorcycle;0000005750 yes permissive +motorcycle;0000001158 designated official +motorcycle;0000005805 destination +motorcycle;0000012401 agricultural forestry +motorcycle;0000001180 private +motorcycle;0000053955 no + +vehicle;0000000505 yes permissive +vehicle;0000000027 designated +vehicle;0000007582 destination +vehicle;0000004357 agricultural forestry +vehicle;0000001155 private +vehicle;0000006487 no + +cycleway;0000033575 track +cycleway;0000012829 no +cycleway;0000011604 lane +cycleway;0000008938 opposite +cycleway;0000001503 none +cycleway;0000001146 right +cycleway;0000001031 opposite_track +cycleway;0000001029 yes +cycleway;0000000856 opposite_lane +cycleway;0000000675 both +cycleway;0000000665 left +cycleway;0000000521 shared +cycleway;0000000383 street +cycleway;0000000176 segregated + +mtb:scale;0000043968 0 +mtb:scale;0000019705 1 +mtb:scale;0000006436 2 +mtb:scale;0000002702 3 +mtb:scale;0000001083 4 +mtb:scale;0000000329 5 + +sac_scale;0000049626 hiking +sac_scale;0000007933 mountain_hiking +sac_scale;0000001160 demanding_mountain_hiking +sac_scale;0000000523 yes +sac_scale;0000000364 alpine_hiking +sac_scale;0000000117 demanding_alpine_hiking + +noexit;0000058492 yes + +motorroad;0000019250 yes + +oneway;0000330245 yes +oneway;0000075148 no +oneway;0000003679 -1 +oneway;0000000001 true +oneway;0000000001 1 + +junction;0000015929 roundabout + +bridge;0000182649 yes viaduct true suspension + +tunnel;0000031626 yes + +lcn;0000018999 yes + +longdistancecycleway;0000000001 yes + +reversedirection;0000000001 yes + +---context:node + +highway;0000100947 turning_circle +highway;0000067645 traffic_signals +highway;0000047209 crossing +highway;0000037164 bus_stop +highway;0000006577 motorway_junction +highway;0000003811 stop +highway;0000002331 mini_roundabout +highway;0000001789 milestone +highway;0000001692 passing_place +highway;0000001289 give_way +highway;0000001092 emergency_access_point +highway;0000000683 speed_camera +highway;0000000672 steps +highway;0000000658 incline_steep +highway;0000000620 elevator +highway;0000000506 street_lamp +highway;0000000490 ford +highway;0000000458 incline +highway;0000000135 rest_area +highway;0000000105 path +highway;0000000098 emergency_bay +highway;0000000096 road +highway;0000000087 platform +highway;0000000074 services +highway;0000000058 track +highway;0000000055 service +highway;0000000054 footway +highway;0000000053 traffic_calming +highway;0000000046 toll_bridge +highway;0000000037 city_entry + +barrier;0000076979 gate +barrier;0000069308 bollard +barrier;0000028131 lift_gate +barrier;0000017332 cycle_barrier +barrier;0000005693 entrance +barrier;0000002885 block +barrier;0000001065 kissing_gate +barrier;0000000828 cattle_grid +barrier;0000000602 stile +barrier;0000000561 turnstile +barrier;0000000512 no +barrier;0000000463 fence +barrier;0000000417 bump_gate +barrier;0000000324 sally_port +barrier;0000000283 yes +barrier;0000000283 hampshire_gate +barrier;0000000236 swing_gate +barrier;0000000203 chain +barrier;0000000181 toll_booth +barrier;0000000180 door +barrier;0000000104 chicane +barrier;0000000096 tree +barrier;0000000087 border_control +barrier;0000000077 log +barrier;0000000076 traffic_crossing_pole +barrier;0000000063 wall +barrier;0000000060 fallen_tree +barrier;0000000052 stone +barrier;0000000048 ditch +barrier;0000000031 spikes + +access;0000001309 yes permissive +access;0000000118 designated official +access;0000000405 destination customers +access;0000000276 agricultural forestry +access;0000008574 private +access;0000002145 no + +foot;0000080681 yes permissive +foot;0000000326 designated official +foot;0000000023 destination +foot;0000000156 private +foot;0000009170 no + +bicycle;0000076717 yes permissive +bicycle;0000000406 designated official +bicycle;0000000018 destination +bicycle;0000000081 dismount +bicycle;0000000051 private +bicycle;0000016121 no + +motorcar;0000005785 yes permissive +motorcar;0000000026 designated official +motorcar;0000000080 destination +motorcar;0000000112 agricultural forestry +motorcar;0000000171 private +motorcar;0000001817 no + +motor_vehicle;0000000066 yes permissive +motor_vehicle;0000000000 designated official +motor_vehicle;0000000030 destination +motor_vehicle;0000000073 agricultural forestry +motor_vehicle;0000000136 private +motor_vehicle;0000000469 no + +motorcycle;0000004515 yes permissive +motorcycle;0000000007 designated official +motorcycle;0000000054 destination +motorcycle;0000000027 agricultural forestry +motorcycle;0000000063 private +motorcycle;0000001637 no + +vehicle;0000000058 yes permissive +vehicle;0000000000 designated +vehicle;0000000081 destination +vehicle;0000000038 agricultural forestry +vehicle;0000000041 private +vehicle;0000000271 no + +crossing;0000032485 traffic_signals +crossing;0000014300 uncontrolled +crossing;0000005086 island +crossing;0000001565 unmarked +crossing;0000001066 no +crossing;0000000333 zebra + +railway;0000034039 level_crossing +railway;0000010175 crossing + +noexit;0000043010 yes + +entrance;0000015094 yes +entrance;0000007079 main +entrance;0000000554 service +entrance;0000000169 emergency +entrance;0000000063 exit +entrance;0000000008 private + +lcn;0000018999 yes + +longdistancecycleway;0000000001 yes diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/moped.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/moped.brf new file mode 100644 index 0000000..7343604 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/moped.brf @@ -0,0 +1,119 @@ +# +# Moped-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 + +---context:way # following code refers to way-tags + +assign turncost 90 +assign initialcost switch highway=ferry 20000 0 + + +# +# calculate logical car access +# +assign motorverhicleaccess + switch motor_vehicle= + switch vehicle= + switch access= + 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 highway=ferry 1 + switch or highway=residential highway=living_street 1 + switch highway=service 1 + 0 + or access=yes or access=designated access=destination + or vehicle=yes or vehicle=designated vehicle=destination + or motor_vehicle=yes or motor_vehicle=designated motor_vehicle=destination + +assign caraccess + switch motorcar= + motorverhicleaccess + or motorcar=yes or motorcar=designated motorcar=destination + +assign motorcycleaccess + switch motorcycle= + motorverhicleaccess + or motorcycle=yes or motorcycle=designated motorcycle=destination + +assign accesspenalty + switch or caraccess motorcycleaccess + switch motorroad=yes 10000 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 or highway=trunk highway=trunk_link 1.5 + switch or highway=primary highway=primary_link switch maxspeed=30 2.0 switch maxspeed=50 1.5 1.2 + switch or highway=secondary highway=secondary_link 1.4 + switch or highway=tertiary highway=tertiary_link 1.3 + switch highway=unclassified 1.2 + switch highway=ferry 5.67 + switch highway=bridleway 5 + switch or highway=residential highway=living_street 2 + switch highway=service 2 + switch or highway=track or highway=road highway=path + switch tracktype=grade1 5 + switch ispaved 5 + 30 + 10000 + +---context:node # following code refers to node tags + +# +# calculate logical car access to nodes +# +assign motorvehicleaccess + 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=designated access=destination + or vehicle=yes or vehicle=designated vehicle=destination + or motor_vehicle=yes or motor_vehicle=designated motor_vehicle=destination + +assign caraccess + switch motorcar= + motorvehicleaccess + or motorcar=yes or motorcar=designated motorcar=destination + +assign motorcycleaccess + switch motorcycle= + motorvehicleaccess + or motorcycle=yes or motorcycle=designated motorcycle=destination + +assign initialcost + switch or caraccess motorcycleaccess + 0 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/safety.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/safety.brf new file mode 100644 index 0000000..e759676 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/safety.brf @@ -0,0 +1,223 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 1 # set to 0 to ignore elevation in routing +assign allow_steps 1 # set to 0 to disallow steps +assign allow_ferries 1 # set to 0 to disallow ferries +assign ignore_cycleroutes 0 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 1 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +# +# pre-calculate some logical expressions +# +assign is_ldcr and longdistancecycleway=yes not ignore_cycleroutes +assign isbike or bicycle=yes or bicycle=designated lcn=yes +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 probablyGood or ispaved and isbike not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost switch is_ldcr 0 90 + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 4 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 4.0 + 0.0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign costfactor + + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the longdistancecycleway-switch, to be able + # to really exlude them be setting cost to infinity + # + switch highway=steps switch allow_steps 40 100000 + switch highway=ferry switch allow_ferries 5.67 100000 + + # + # handle long-distance cycle-routes. + # + switch is_ldcr 1 # always treated as perfect (=1) + add switch stick_to_cycleroutes 0.5 0.05 # everything else somewhat up + + # + # some other highway types + # + switch highway=pedestrian 3 + switch highway=bridleway 5 + switch highway=cycleway 1 + switch or highway=residential highway=living_street switch isunpaved 1.5 1.1 + switch highway=service switch isunpaved 1.6 1.3 + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch probablyGood 1.0 1.3 + switch tracktype=grade2 switch probablyGood 1.1 2.0 + switch tracktype=grade3 switch probablyGood 1.5 3.0 + switch tracktype=grade4 switch probablyGood 2.0 5.0 + switch tracktype=grade5 switch probablyGood 3.0 5.0 + switch probablyGood 1.0 5.0 + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + add switch and avoid_unsafe not isbike 2 0 + + # + # exclude motorways and proposed roads + # + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + + # + # actuals roads are o.k. if we have a bike hint + # + switch or highway=trunk highway=trunk_link switch isbike 1.5 10 + switch or highway=primary highway=primary_link switch isbike 1.2 3 + switch or highway=secondary highway=secondary_link switch isbike 1.1 1.6 + switch or highway=tertiary highway=tertiary_link switch isbike 1.0 1.4 + switch highway=unclassified switch isbike 1.0 1.3 + + # + # default for any other highway type not handled above + # + 2.0 + + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 100 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/shortest.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/shortest.brf new file mode 100644 index 0000000..000b506 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/shortest.brf @@ -0,0 +1,89 @@ +---context:global # following code refers to global config + +# the elevation parameters + +assign downhillcost 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +assign turncost 0 + +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +assign accesspenalty switch or bikeaccess footaccess 0 100000 + +assign costfactor + add accesspenalty + + switch highway=ferry 5.67 + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + 1 + +---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 or longdistancecycleway=yes lcn=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 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-ignore-cr.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-ignore-cr.brf new file mode 100644 index 0000000..8639f46 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-ignore-cr.brf @@ -0,0 +1,223 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 1 # set to 0 to ignore elevation in routing +assign allow_steps 1 # set to 0 to disallow steps +assign allow_ferries 1 # set to 0 to disallow ferries +assign ignore_cycleroutes 1 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 0 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +# +# pre-calculate some logical expressions +# +assign is_ldcr and longdistancecycleway=yes not ignore_cycleroutes +assign isbike or bicycle=yes or bicycle=designated lcn=yes +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 probablyGood or ispaved and isbike not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost switch is_ldcr 0 90 + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 4 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 4.0 + 0.0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign costfactor + + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the longdistancecycleway-switch, to be able + # to really exlude them be setting cost to infinity + # + switch highway=steps switch allow_steps 40 100000 + switch highway=ferry switch allow_ferries 5.67 100000 + + # + # handle long-distance cycle-routes. + # + switch is_ldcr 1 # always treated as perfect (=1) + add switch stick_to_cycleroutes 0.5 0.05 # everything else somewhat up + + # + # some other highway types + # + switch highway=pedestrian 3 + switch highway=bridleway 5 + switch highway=cycleway 1 + switch or highway=residential highway=living_street switch isunpaved 1.5 1.1 + switch highway=service switch isunpaved 1.6 1.3 + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch probablyGood 1.0 1.3 + switch tracktype=grade2 switch probablyGood 1.1 2.0 + switch tracktype=grade3 switch probablyGood 1.5 3.0 + switch tracktype=grade4 switch probablyGood 2.0 5.0 + switch tracktype=grade5 switch probablyGood 3.0 5.0 + switch probablyGood 1.0 5.0 + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + add switch and avoid_unsafe not isbike 2 0 + + # + # exclude motorways and proposed roads + # + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + + # + # actuals roads are o.k. if we have a bike hint + # + switch or highway=trunk highway=trunk_link switch isbike 1.5 10 + switch or highway=primary highway=primary_link switch isbike 1.2 3 + switch or highway=secondary highway=secondary_link switch isbike 1.1 1.6 + switch or highway=tertiary highway=tertiary_link switch isbike 1.0 1.4 + switch highway=unclassified switch isbike 1.0 1.3 + + # + # default for any other highway type not handled above + # + 2.0 + + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 100 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-noferries.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-noferries.brf new file mode 100644 index 0000000..ea1dba9 --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-noferries.brf @@ -0,0 +1,223 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 1 # set to 0 to ignore elevation in routing +assign allow_steps 1 # set to 0 to disallow steps +assign allow_ferries 0 # set to 0 to disallow ferries +assign ignore_cycleroutes 0 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 0 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +# +# pre-calculate some logical expressions +# +assign is_ldcr and longdistancecycleway=yes not ignore_cycleroutes +assign isbike or bicycle=yes or bicycle=designated lcn=yes +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 probablyGood or ispaved and isbike not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost switch is_ldcr 0 90 + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 4 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 4.0 + 0.0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign costfactor + + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the longdistancecycleway-switch, to be able + # to really exlude them be setting cost to infinity + # + switch highway=steps switch allow_steps 40 100000 + switch highway=ferry switch allow_ferries 5.67 100000 + + # + # handle long-distance cycle-routes. + # + switch is_ldcr 1 # always treated as perfect (=1) + add switch stick_to_cycleroutes 0.5 0.05 # everything else somewhat up + + # + # some other highway types + # + switch highway=pedestrian 3 + switch highway=bridleway 5 + switch highway=cycleway 1 + switch or highway=residential highway=living_street switch isunpaved 1.5 1.1 + switch highway=service switch isunpaved 1.6 1.3 + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch probablyGood 1.0 1.3 + switch tracktype=grade2 switch probablyGood 1.1 2.0 + switch tracktype=grade3 switch probablyGood 1.5 3.0 + switch tracktype=grade4 switch probablyGood 2.0 5.0 + switch tracktype=grade5 switch probablyGood 3.0 5.0 + switch probablyGood 1.0 5.0 + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + add switch and avoid_unsafe not isbike 2 0 + + # + # exclude motorways and proposed roads + # + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + + # + # actuals roads are o.k. if we have a bike hint + # + switch or highway=trunk highway=trunk_link switch isbike 1.5 10 + switch or highway=primary highway=primary_link switch isbike 1.2 3 + switch or highway=secondary highway=secondary_link switch isbike 1.1 1.6 + switch or highway=tertiary highway=tertiary_link switch isbike 1.0 1.4 + switch highway=unclassified switch isbike 1.0 1.3 + + # + # default for any other highway type not handled above + # + 2.0 + + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 100 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-nosteps.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-nosteps.brf new file mode 100644 index 0000000..1a70dcd --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-nosteps.brf @@ -0,0 +1,223 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 1 # set to 0 to ignore elevation in routing +assign allow_steps 0 # set to 0 to disallow steps +assign allow_ferries 1 # set to 0 to disallow ferries +assign ignore_cycleroutes 0 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 0 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +# +# pre-calculate some logical expressions +# +assign is_ldcr and longdistancecycleway=yes not ignore_cycleroutes +assign isbike or bicycle=yes or bicycle=designated lcn=yes +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 probablyGood or ispaved and isbike not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost switch is_ldcr 0 90 + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 4 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 4.0 + 0.0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign costfactor + + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the longdistancecycleway-switch, to be able + # to really exlude them be setting cost to infinity + # + switch highway=steps switch allow_steps 40 100000 + switch highway=ferry switch allow_ferries 5.67 100000 + + # + # handle long-distance cycle-routes. + # + switch is_ldcr 1 # always treated as perfect (=1) + add switch stick_to_cycleroutes 0.5 0.05 # everything else somewhat up + + # + # some other highway types + # + switch highway=pedestrian 3 + switch highway=bridleway 5 + switch highway=cycleway 1 + switch or highway=residential highway=living_street switch isunpaved 1.5 1.1 + switch highway=service switch isunpaved 1.6 1.3 + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch probablyGood 1.0 1.3 + switch tracktype=grade2 switch probablyGood 1.1 2.0 + switch tracktype=grade3 switch probablyGood 1.5 3.0 + switch tracktype=grade4 switch probablyGood 2.0 5.0 + switch tracktype=grade5 switch probablyGood 3.0 5.0 + switch probablyGood 1.0 5.0 + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + add switch and avoid_unsafe not isbike 2 0 + + # + # exclude motorways and proposed roads + # + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + + # + # actuals roads are o.k. if we have a bike hint + # + switch or highway=trunk highway=trunk_link switch isbike 1.5 10 + switch or highway=primary highway=primary_link switch isbike 1.2 3 + switch or highway=secondary highway=secondary_link switch isbike 1.1 1.6 + switch or highway=tertiary highway=tertiary_link switch isbike 1.0 1.4 + switch highway=unclassified switch isbike 1.0 1.3 + + # + # default for any other highway type not handled above + # + 2.0 + + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 100 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-steep.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-steep.brf new file mode 100644 index 0000000..822b1ec --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking-steep.brf @@ -0,0 +1,223 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 0 # set to 0 to ignore elevation in routing +assign allow_steps 1 # set to 0 to disallow steps +assign allow_ferries 1 # set to 0 to disallow ferries +assign ignore_cycleroutes 0 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 0 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +# +# pre-calculate some logical expressions +# +assign is_ldcr and longdistancecycleway=yes not ignore_cycleroutes +assign isbike or bicycle=yes or bicycle=designated lcn=yes +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 probablyGood or ispaved and isbike not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost switch is_ldcr 0 90 + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 4 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 4.0 + 0.0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign costfactor + + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the longdistancecycleway-switch, to be able + # to really exlude them be setting cost to infinity + # + switch highway=steps switch allow_steps 40 100000 + switch highway=ferry switch allow_ferries 5.67 100000 + + # + # handle long-distance cycle-routes. + # + switch is_ldcr 1 # always treated as perfect (=1) + add switch stick_to_cycleroutes 0.5 0.05 # everything else somewhat up + + # + # some other highway types + # + switch highway=pedestrian 3 + switch highway=bridleway 5 + switch highway=cycleway 1 + switch or highway=residential highway=living_street switch isunpaved 1.5 1.1 + switch highway=service switch isunpaved 1.6 1.3 + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch probablyGood 1.0 1.3 + switch tracktype=grade2 switch probablyGood 1.1 2.0 + switch tracktype=grade3 switch probablyGood 1.5 3.0 + switch tracktype=grade4 switch probablyGood 2.0 5.0 + switch tracktype=grade5 switch probablyGood 3.0 5.0 + switch probablyGood 1.0 5.0 + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + add switch and avoid_unsafe not isbike 2 0 + + # + # exclude motorways and proposed roads + # + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + + # + # actuals roads are o.k. if we have a bike hint + # + switch or highway=trunk highway=trunk_link switch isbike 1.5 10 + switch or highway=primary highway=primary_link switch isbike 1.2 3 + switch or highway=secondary highway=secondary_link switch isbike 1.1 1.6 + switch or highway=tertiary highway=tertiary_link switch isbike 1.0 1.4 + switch highway=unclassified switch isbike 1.0 1.3 + + # + # default for any other highway type not handled above + # + 2.0 + + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 100 + 1000000 diff --git a/brouter-routing-app/src/main/resources/brouter/profiles2/trekking.brf b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking.brf new file mode 100644 index 0000000..ec2940e --- /dev/null +++ b/brouter-routing-app/src/main/resources/brouter/profiles2/trekking.brf @@ -0,0 +1,223 @@ +# *** The trekking profile is for slow travel +# *** and avoiding car traffic, but still with +# *** a focus on approaching your destination +# *** efficiently. + +---context:global # following code refers to global config + +# Use the following switches to change behaviour +# (1=yes, 0=no): + +assign consider_elevation 1 # set to 0 to ignore elevation in routing +assign allow_steps 1 # set to 0 to disallow steps +assign allow_ferries 1 # set to 0 to disallow ferries +assign ignore_cycleroutes 0 # set to 1 for better elevation results +assign stick_to_cycleroutes 0 # set to 1 to just follow cycleroutes +assign avoid_unsafe 0 # set to 1 to avoid standard highways + +# the elevation parameters + +assign downhillcost switch consider_elevation 60 0 +assign downhillcutoff 1.5 +assign uphillcost 0 +assign uphillcutoff 1.5 + +---context:way # following code refers to way-tags + +# +# pre-calculate some logical expressions +# +assign is_ldcr and longdistancecycleway=yes not ignore_cycleroutes +assign isbike or bicycle=yes or bicycle=designated lcn=yes +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 probablyGood or ispaved and isbike not isunpaved + + +# +# this is the cost (in Meter) for a 90-degree turn +# The actual cost is calculated as turncost*cos(angle) +# (Suppressing turncost while following longdistance-cycleways +# makes them a little bit more magnetic) +# +assign turncost switch is_ldcr 0 90 + +# +# calculate the initial cost +# this is added to the total cost each time the costfactor +# changed +# +assign initialcost switch highway=ferry 10000 0 + +# +# implicit access here just from the motorroad tag +# (implicit access rules from highway tag handled elsewhere) +# +assign defaultaccess + switch access= + not motorroad=yes + switch or access=private access=no + 0 + 1 + +# +# calculate logical bike access +# +assign bikeaccess + or longdistancecycleway=yes + switch bicycle= + switch vehicle= + defaultaccess + switch or vehicle=private vehicle=no + 0 + 1 + not or bicycle=private or bicycle=no bicycle=dismount + +# +# calculate logical foot access +# +assign footaccess + or bikeaccess + or bicycle=dismount + switch foot= + defaultaccess + not or foot=private foot=no + +# +# if not bike-, but foot-acess, just a moderate penalty, +# otherwise access is forbidden +# +assign accesspenalty + switch bikeaccess + 0 + switch footaccess + 4 + 100000 + +# +# handle one-ways. On primary roads, wrong-oneways should +# be close to forbidden, while on other ways we just add +# 4 to the costfactor (making it at least 5 - you are allowed +# to push your bike) +# +assign oneway + switch oneway= + junction=roundabout + or oneway=yes or oneway=true oneway=1 +assign onewaypenalty + switch switch reversedirection=yes oneway oneway=-1 + switch or cycleway=opposite or cycleway=opposite_lane cycleway=opposite_track 0 + switch or highway=primary highway=primary_link 50 + switch or highway=secondary highway=secondary_link 30 + switch or highway=tertiary highway=tertiary_link 20 + 4.0 + 0.0 + +# +# calculate the cost-factor, which is the factor +# by which the distance of a way-segment is multiplied +# to calculate the cost of that segment. The costfactor +# must be >=1 and it's supposed to be close to 1 for +# the type of way the routing profile is searching for +# +assign costfactor + + add max onewaypenalty accesspenalty + + # + # steps and ferries are special. Note this is handled + # before the longdistancecycleway-switch, to be able + # to really exlude them be setting cost to infinity + # + switch highway=steps switch allow_steps 40 100000 + switch highway=ferry switch allow_ferries 5.67 100000 + + # + # handle long-distance cycle-routes. + # + switch is_ldcr 1 # always treated as perfect (=1) + add switch stick_to_cycleroutes 0.5 0.05 # everything else somewhat up + + # + # some other highway types + # + switch highway=pedestrian 3 + switch highway=bridleway 5 + switch highway=cycleway 1 + switch or highway=residential highway=living_street switch isunpaved 1.5 1.1 + switch highway=service switch isunpaved 1.6 1.3 + + # + # tracks and track-like ways are rated mainly be tracktype/grade + # But note that if no tracktype is given (mainly for road/path/footway) + # it can be o.k. if there's any other hint for quality + # + switch or highway=track or highway=road or highway=path highway=footway + switch tracktype=grade1 switch probablyGood 1.0 1.3 + switch tracktype=grade2 switch probablyGood 1.1 2.0 + switch tracktype=grade3 switch probablyGood 1.5 3.0 + switch tracktype=grade4 switch probablyGood 2.0 5.0 + switch tracktype=grade5 switch probablyGood 3.0 5.0 + switch probablyGood 1.0 5.0 + + # + # When avoiding unsafe ways, avoid highways without a bike hint + # + add switch and avoid_unsafe not isbike 2 0 + + # + # exclude motorways and proposed roads + # + switch or highway=motorway highway=motorway_link 100000 + switch highway=proposed 100000 + + # + # actuals roads are o.k. if we have a bike hint + # + switch or highway=trunk highway=trunk_link switch isbike 1.5 10 + switch or highway=primary highway=primary_link switch isbike 1.2 3 + switch or highway=secondary highway=secondary_link switch isbike 1.1 1.6 + switch or highway=tertiary highway=tertiary_link switch isbike 1.0 1.4 + switch highway=unclassified switch isbike 1.0 1.3 + + # + # default for any other highway type not handled above + # + 2.0 + + +---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 or longdistancecycleway=yes lcn=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 bikeaccess + 0 + switch footaccess + 100 + 1000000 diff --git a/brouter-server/pom.xml b/brouter-server/pom.xml new file mode 100644 index 0000000..ede752c --- /dev/null +++ b/brouter-server/pom.xml @@ -0,0 +1,55 @@ + + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-server + jar + + + + + org.apache.maven.plugins + maven-assembly-plugin + + + jar-with-dependencies + + + + true + btools.server.BRouter + + + + + + make-assembly + package + + single + + + + + + + + + + org.btools + brouter-core + ${project.version} + + + org.btools + brouter-map-creator + ${project.version} + + + diff --git a/brouter-server/src/main/java/btools/server/BRouter.java b/brouter-server/src/main/java/btools/server/BRouter.java new file mode 100644 index 0000000..7bd3dfc --- /dev/null +++ b/brouter-server/src/main/java/btools/server/BRouter.java @@ -0,0 +1,132 @@ +package btools.server; + +import java.io.File; +import java.net.URLDecoder; +import java.util.ArrayList; +import java.util.List; +import java.util.TreeSet; + +import btools.router.OsmNodeNamed; +import btools.router.RoutingEngine; +import btools.router.RoutingContext; +import btools.router.OsmTrack; + +public class BRouter +{ + public static void main(String[] args) throws Exception + { + if ( args.length == 2) // cgi-input-mode + { + try + { + String queryString = args[1]; + int sepIdx = queryString.indexOf( '=' ); + if ( sepIdx >= 0 ) queryString = queryString.substring( sepIdx + 1 ); + queryString = URLDecoder.decode( queryString, "ISO-8859-1" ); + int ntokens = 1; + for( int ic = 0; ic maxKm * 1000 ) + { + System.out.println( "airDistance " + (airDistance/1000) + "km exceeds limit for online router (" + maxKm + "km)" ); + return; + } + } + + long maxRunningTime = 60000; // the cgi gets a 1 Minute timeout + String sMaxRunningTime = System.getProperty( "maxRunningTime" ); + if ( sMaxRunningTime != null ) + { + maxRunningTime = Integer.parseInt( sMaxRunningTime ) * 1000; + } + + long startTime = System.currentTimeMillis(); + List wplist = new ArrayList(); + wplist.add( from ); + wplist.add( to ); + + RoutingEngine re = new RoutingEngine( null, null, args[0], wplist, readRoutingContext(a2) ); + re.doRun( maxRunningTime ); + if ( re.getErrorMessage() != null ) + { + System.out.println( re.getErrorMessage() ); + } + } + catch( Throwable e ) + { + System.out.println( "unexpected exception: " + e ); + } + System.exit(0); + } + System.out.println("BRouter 0.98 / 12012014 / abrensch"); + if ( args.length < 6 ) + { + System.out.println("Find routes in an OSM map"); + System.out.println("usage: java -jar brouter.jar "); + return; + } + List wplist = new ArrayList(); + wplist.add( readPosition( args, 1, "from" ) ); + wplist.add( readPosition( args, 3, "to" ) ); + RoutingEngine re = new RoutingEngine( "mytrack", "mylog", args[0], wplist, readRoutingContext(args) ); + re.doRun( 0 ); + if ( re.getErrorMessage() != null ) + { + System.out.println( re.getErrorMessage() ); + } + } + + + private static OsmNodeNamed readPosition( String[] args, int idx, String name ) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = name; + n.ilon = (int)( ( Double.parseDouble( args[idx ] ) + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( Double.parseDouble( args[idx+1] ) + 90. ) *1000000. + 0.5); + return n; + } + + private static RoutingContext readRoutingContext( String[] args ) + { + RoutingContext c = new RoutingContext(); + if ( args.length > 5 ) + { + c.localFunction = args[5]; + if ( args.length > 6 ) + { + c.setAlternativeIdx( Integer.parseInt( args[6] ) ); + } + } + return c; + } +} diff --git a/brouter-server/src/main/java/btools/server/CgiUpload.java b/brouter-server/src/main/java/btools/server/CgiUpload.java new file mode 100644 index 0000000..d71c77f --- /dev/null +++ b/brouter-server/src/main/java/btools/server/CgiUpload.java @@ -0,0 +1,111 @@ +package btools.server; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.URLDecoder; + +public class CgiUpload +{ + public static void main(String[] args) + { + try + { + _main(args); + } + catch( Exception e ) + { + System.out.println( "unexpected exception: " + e ); + } + } + + private static void _main(String[] args) throws Exception + { + String htmlTemplate = args[0]; + String customeProfileDir = args[1]; + + String id = "" + System.currentTimeMillis(); + + + // cgi-header + System.out.println( "Content-type: text/html" ); + System.out.println(); + + // write the post message to a file + BufferedWriter bw = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream( customeProfileDir + "/" + id + ".brf" ) ) ); + BufferedReader ir = new BufferedReader( new InputStreamReader( System.in ) ); + String postData = ir.readLine(); + String[] coordValues = new String[4]; + if( postData != null ) + { + int coordsIdx = postData.indexOf( "coords=" ); + if ( coordsIdx >= 0) + { + int coordsEnd = postData.indexOf( '&' ); + if ( coordsEnd >= 0) + { + String coordsString = postData.substring( coordsIdx + 7, coordsEnd ); + postData = postData.substring( coordsEnd+1 ); + int pos = 0; + for(int idx=0; idx<4; idx++) + { + int p = coordsString.indexOf( '_', pos ); + coordValues[idx] = coordsString.substring( pos, p ); + pos = p+1; + } + } + } + int sepIdx = postData.indexOf( '=' ); + if ( sepIdx >= 0 ) postData = postData.substring( sepIdx + 1 ); + postData = URLDecoder.decode( postData, "ISO-8859-1" ); + bw.write( postData ); + } + bw.close(); + + // echo the template with a custom select item + BufferedReader br = new BufferedReader( + new InputStreamReader( + new FileInputStream( htmlTemplate ) ) ); + + for(;;) + { + String line = br.readLine(); + if ( line == null ) break; + if ( line.indexOf( "" ) >= 0 ) + { + line = " "; + } + else if ( line.indexOf( "paste your profile here" ) >= 0 ) + { + System.out.println( ""; + } + else + { + line = replaceCoord( line, "lonfrom", coordValues[0] ); + line = replaceCoord( line, "latfrom", coordValues[1] ); + line = replaceCoord( line, "lonto", coordValues[2] ); + line = replaceCoord( line, "latto", coordValues[3] ); + } + + System.out.println( line ); + } + br.close(); + } + + private static String replaceCoord( String line, String name, String value ) + { + String inputTag = "= 0 ) + { + return inputTag + " value=\"" + value + "\">"; + } + return line; + } +} diff --git a/brouter-server/src/main/java/btools/server/RouteServer.java b/brouter-server/src/main/java/btools/server/RouteServer.java new file mode 100644 index 0000000..195b62c --- /dev/null +++ b/brouter-server/src/main/java/btools/server/RouteServer.java @@ -0,0 +1,267 @@ +package btools.server; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import btools.router.OsmNodeNamed; +import btools.router.OsmTrack; +import btools.router.RoutingContext; +import btools.router.RoutingEngine; +import btools.server.request.RequestHandler; +import btools.server.request.ServerHandler; +import btools.server.request.YoursHandler; + +public class RouteServer extends Thread +{ + public ServiceContext serviceContext; + public short port = 17777; + + private boolean serverStopped = false; + private ServerSocket serverSocket = null; + + public void close() + { + serverStopped = true; + try + { + ServerSocket ss = serverSocket; + serverSocket = null; + ss.close(); + } + catch( Throwable t ) {} + } + + private void killOtherServer() throws Exception + { + Socket socket = new Socket( "localhost", port ); + BufferedWriter bw = null; + try + { + bw = new BufferedWriter( new OutputStreamWriter( socket.getOutputStream() ) ); + bw.write( "EXIT\n" ); + } + finally + { + bw.close(); + socket.close(); + } + } + + public void run() + { + // first go an kill any other server on that port + + for(;;) + { + try + { + killOtherServer(); + System.out.println( "killed, waiting" ); + try { Thread.sleep( 3000 ); } catch( InterruptedException ie ) {} + } + catch( Throwable t ) { + System.out.println( "not killed: " + t ); + break; + } + } + try + { + serverSocket = new ServerSocket(port); + for(;;) + { + System.out.println("RouteServer accepting connections.."); + Socket clientSocket = serverSocket.accept(); + if ( !serveRequest( clientSocket ) ) break; + } + } + catch( Throwable e ) + { + System.out.println("RouteServer main loop got exception (exiting): "+e); + if ( serverSocket != null ) + { + try { serverSocket.close(); } catch( Throwable t ) {} + } + System.exit(0); + } + + } + + + + public boolean serveRequest( Socket clientSocket ) + { + BufferedReader br = null; + BufferedWriter bw = null; + try + { + br = new BufferedReader( new InputStreamReader( clientSocket.getInputStream() ) ); + bw = new BufferedWriter( new OutputStreamWriter( clientSocket.getOutputStream() ) ); + + // we just read the first line + String getline = br.readLine(); + if ( getline == null || getline.startsWith( "EXIT") ) + { + throw new RuntimeException( "socketExitRequest" ); + } + if ( getline.startsWith("GET /favicon.ico") ) + { + return true; + } + + String url = getline.split(" ")[1]; + HashMap params = getUrlParams(url); + + long maxRunningTime = getMaxRunningTime(); + long startTime = System.currentTimeMillis(); + + RequestHandler handler; + if ( params.containsKey( "lonlats" ) && params.containsKey( "profile" ) ) + { + handler = new ServerHandler( serviceContext, params ); + } + else + { + handler = new YoursHandler( serviceContext, params ); + } + RoutingContext rc = handler.readRoutingContext(); + List wplist = handler.readWayPointList(); + + RoutingEngine cr = new RoutingEngine( null, null, serviceContext.segmentDir, wplist, rc ); + cr.quite = true; + cr.doRun( maxRunningTime ); + + // http-header + bw.write( "HTTP/1.1 200 OK\n" ); + bw.write( "Connection: close\n" ); + bw.write( "Content-Type: text/xml; charset=utf-8\n" ); + bw.write( "Access-Control-Allow-Origin: *\n" ); + bw.write( "\n" ); + + if ( cr.getErrorMessage() != null ) + { + bw.write( cr.getErrorMessage() ); + bw.write( "\n" ); + } + else + { + OsmTrack track = cr.getFoundTrack(); + if ( track != null ) + { + bw.write( handler.formatTrack(track) ); + } + } + bw.flush(); + } + catch (Throwable e) + { + if ( "socketExitRequest".equals( e.getMessage() ) ) + { + return false; + } + System.out.println("RouteServer got exception (will continue): "+e); + e.printStackTrace(); + } + finally + { + if ( br != null ) try { br.close(); } catch( Exception e ) {} + if ( bw != null ) try { bw.close(); } catch( Exception e ) {} + } + return true; + } + + public static void main(String[] args) throws Exception + { + System.out.println("BRouter 0.98 / 12012014 / abrensch"); + if ( args.length != 3 ) + { + System.out.println("serve YOURS protocol for BRouter"); + System.out.println("usage: java RouteServer "); + System.out.println(""); + System.out.println("serve BRouter protocol"); + System.out.println("usage: java RouteServer "); + return; + } + + ServiceContext serviceContext = new ServiceContext(); + serviceContext.segmentDir = args[0]; + File profileMapOrDir = new File( args[1] ); + if ( profileMapOrDir.isDirectory() ) + { + System.setProperty( "profileBaseDir", args[1] ); + } + else + { + serviceContext.profileMap = loadProfileMap( profileMapOrDir ); + } + + ServerSocket serverSocket = new ServerSocket(Integer.parseInt(args[2])); + for (;;) + { + Socket clientSocket = serverSocket.accept(); + RouteServer server = new RouteServer(); + server.serviceContext = serviceContext; + server.serveRequest( clientSocket ); + } + } + + private static Map loadProfileMap( File file ) throws IOException + { + Map profileMap = new HashMap(); + + BufferedReader pr = new BufferedReader( new InputStreamReader( new FileInputStream( file ) ) ); + for(;;) + { + String key = pr.readLine(); + if ( key == null ) break; + key = key.trim(); + if ( key.length() == 0 ) continue; + String value = pr.readLine(); + value = value.trim(); + profileMap.put( key, value ); + } + + return profileMap; + } + + private static HashMap getUrlParams( String url ) + { + HashMap params = new HashMap(); + StringTokenizer tk = new StringTokenizer( url, "?&" ); + while( tk.hasMoreTokens() ) + { + String t = tk.nextToken(); + StringTokenizer tk2 = new StringTokenizer( t, "=" ); + if ( tk2.hasMoreTokens() ) + { + String key = tk2.nextToken(); + if ( tk2.hasMoreTokens() ) + { + String value = tk2.nextToken(); + params.put( key, value ); + } + } + } + return params; + } + + private static long getMaxRunningTime() { + long maxRunningTime = 60000; + String sMaxRunningTime = System.getProperty( "maxRunningTime" ); + if ( sMaxRunningTime != null ) + { + maxRunningTime = Integer.parseInt( sMaxRunningTime ) * 1000; + } + return maxRunningTime; + } +} diff --git a/brouter-server/src/main/java/btools/server/ServiceContext.java b/brouter-server/src/main/java/btools/server/ServiceContext.java new file mode 100644 index 0000000..096a78d --- /dev/null +++ b/brouter-server/src/main/java/btools/server/ServiceContext.java @@ -0,0 +1,16 @@ +package btools.server; + +import java.util.List; +import java.util.Map; + +import btools.router.OsmNodeNamed; + +/** + * Environment configuration that is initialized at server/service startup + */ +public class ServiceContext +{ + public String segmentDir; + public Map profileMap = null; + public List nogoList; +} diff --git a/brouter-server/src/main/java/btools/server/request/RequestHandler.java b/brouter-server/src/main/java/btools/server/request/RequestHandler.java new file mode 100644 index 0000000..060603b --- /dev/null +++ b/brouter-server/src/main/java/btools/server/request/RequestHandler.java @@ -0,0 +1,28 @@ +package btools.server.request; + +import java.util.HashMap; +import java.util.List; + +import btools.router.OsmNodeNamed; +import btools.router.OsmTrack; +import btools.router.RoutingContext; +import btools.server.ServiceContext; + +public abstract class RequestHandler +{ + protected ServiceContext serviceContext; + protected HashMap params; + + public RequestHandler( ServiceContext serviceContext, HashMap params ) + { + this.serviceContext = serviceContext; + this.params = params; + } + + public abstract RoutingContext readRoutingContext(); + + public abstract List readWayPointList(); + + public abstract String formatTrack(OsmTrack track); + +} \ No newline at end of file diff --git a/brouter-server/src/main/java/btools/server/request/ServerHandler.java b/brouter-server/src/main/java/btools/server/request/ServerHandler.java new file mode 100644 index 0000000..2ca962a --- /dev/null +++ b/brouter-server/src/main/java/btools/server/request/ServerHandler.java @@ -0,0 +1,149 @@ +package btools.server.request; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import btools.router.OsmNodeNamed; +import btools.router.OsmTrack; +import btools.router.RoutingContext; +import btools.server.ServiceContext; + +/** + * URL query parameter handler for web and standalone server. Supports all + * BRouter features without restrictions. + * + * Parameters: + * + * lonlats = lon,lat|... (unlimited list of lon,lat waypoints separated by |) + * nogos = lon,lat,radius|... (optional, radius in meters) + * profile = profile file name without .brf + * alternativeidx = [0|1|2|3] (optional, default 0) + * format = [kml|gpx] (optional, default gpx) + * + * Example URLs: + * http://localhost:17777/brouter?lonlats=8.799297,49.565883|8.811764,49.563606&nogos=&profile=trekking&alternativeidx=0&format=gpx + * http://localhost:17777/brouter?lonlats=1.1,1.2|2.1,2.2|3.1,3.2|4.1,4.2&nogos=-1.1,-1.2,1|-2.1,-2.2,2&profile=shortest&alternativeidx=1&format=kml + * + */ +public class ServerHandler extends RequestHandler { + + public ServerHandler( ServiceContext serviceContext, HashMap params ) + { + super( serviceContext, params ); + } + + @Override + public RoutingContext readRoutingContext() + { + RoutingContext rc = new RoutingContext(); + + rc.localFunction = params.get( "profile" ); + rc.setAlternativeIdx(Integer.parseInt(params.get( "alternativeidx" ))); + + List nogoList = readNogoList(); + if ( nogoList != null ) + { + rc.prepareNogoPoints( nogoList ); + rc.nogopoints = nogoList; + } + + return rc; + } + + @Override + public List readWayPointList() + { + // lon,lat|... + String lonLats = params.get( "lonlats" ); + if (lonLats == null) throw new IllegalArgumentException( "lonlats parameter not set" ); + + String[] coords = lonLats.split("\\|"); + if (coords.length < 2) throw new IllegalArgumentException( "we need two lat/lon points at least!" ); + + List wplist = new ArrayList(); + for (int i = 0; i < coords.length; i++) + { + String[] lonLat = coords[i].split(","); + wplist.add( readPosition( lonLat[0], lonLat[1], "via" + i ) ); + } + + wplist.get(0).name = "from"; + wplist.get(wplist.size()-1).name = "to"; + + return wplist; + } + + @Override + public String formatTrack(OsmTrack track) + { + String result; + // optional, may be null + String format = params.get( "format" ); + + if (format == null || "gpx".equals(format)) + { + result = track.formatAsGpx(); + } + else if ("kml".equals(format)) + { + result = track.formatAsKml(); + } + else { + System.out.println("unknown track format '" + format + "', using default"); + result = track.formatAsGpx(); + } + + return result; + } + + private static OsmNodeNamed readPosition( String vlon, String vlat, String name ) + { + if ( vlon == null ) throw new IllegalArgumentException( "lon " + name + " not found in input" ); + if ( vlat == null ) throw new IllegalArgumentException( "lat " + name + " not found in input" ); + + return readPosition(Double.parseDouble( vlon ), Double.parseDouble( vlat ), name); + } + + private static OsmNodeNamed readPosition( double lon, double lat, String name ) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = name; + n.ilon = (int)( ( lon + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( lat + 90. ) *1000000. + 0.5); + return n; + } + + private List readNogoList() + { + // lon,lat,radius|... + String nogos = params.get( "nogos" ); + if ( nogos == null ) return null; + + String[] lonLatRadList = nogos.split("\\|"); + + List nogoList = new ArrayList(); + for (int i = 0; i < lonLatRadList.length; i++) + { + String[] lonLatRad = lonLatRadList[i].split(","); + nogoList.add(readNogo(lonLatRad[0], lonLatRad[1], lonLatRad[2])); + } + + return nogoList; + } + + private static OsmNodeNamed readNogo( String lon, String lat, String radius ) + { + return readNogo(Double.parseDouble( lon ), Double.parseDouble( lat ), Integer.parseInt( radius ) ); + } + + private static OsmNodeNamed readNogo( double lon, double lat, int radius ) + { + OsmNodeNamed n = new OsmNodeNamed(); + n.name = "nogo" + radius; + n.ilon = (int)( ( lon + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( lat + 90. ) *1000000. + 0.5); + n.isNogo = true; + return n; + } +} diff --git a/brouter-server/src/main/java/btools/server/request/YoursHandler.java b/brouter-server/src/main/java/btools/server/request/YoursHandler.java new file mode 100644 index 0000000..4c28634 --- /dev/null +++ b/brouter-server/src/main/java/btools/server/request/YoursHandler.java @@ -0,0 +1,69 @@ +package btools.server.request; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import btools.router.OsmNodeNamed; +import btools.router.OsmTrack; +import btools.router.RoutingContext; +import btools.server.ServiceContext; + +public class YoursHandler extends RequestHandler { + + public YoursHandler( ServiceContext serviceContext, HashMap params ) + { + super(serviceContext, params); + } + + @Override + public RoutingContext readRoutingContext() + { + RoutingContext rc = new RoutingContext(); + + String profile_key = params.get( "v" ) + " " + params.get( "fast" ); + if (serviceContext.profileMap == null) throw new IllegalArgumentException( "no profile map loaded" ); + String profile_path = serviceContext.profileMap.get( profile_key ); + if ( profile_path == null ) profile_path = serviceContext.profileMap.get( "default" ); + if ( profile_path == null ) throw new IllegalArgumentException( "no profile for key: " + profile_key ); + rc.localFunction = profile_path; + + List nogoList = serviceContext.nogoList; + if ( nogoList != null ) + { + rc.prepareNogoPoints( nogoList ); + rc.nogopoints = nogoList; + } + + return rc; + } + + @Override + public List readWayPointList() + { + List wplist = new ArrayList(); + wplist.add( readPosition( params, "flon", "flat", "from" ) ); + wplist.add( readPosition( params, "tlon", "tlat", "to" ) ); + return wplist; + } + + @Override + public String formatTrack(OsmTrack track) + { + return track.formatAsKml(); + } + + private static OsmNodeNamed readPosition( HashMap params, String plon, String plat, String name ) + { + String vlon = params.get( plon ); + if ( vlon == null ) throw new IllegalArgumentException( "param " + plon + " bot found in input" ); + String vlat = params.get( plat ); + if ( vlat == null ) throw new IllegalArgumentException( "param " + plat + " bot found in input" ); + + OsmNodeNamed n = new OsmNodeNamed(); + n.name = name; + n.ilon = (int)( ( Double.parseDouble( vlon ) + 180. ) *1000000. + 0.5); + n.ilat = (int)( ( Double.parseDouble( vlat ) + 90. ) *1000000. + 0.5); + return n; + } +} diff --git a/brouter-util/pom.xml b/brouter-util/pom.xml new file mode 100644 index 0000000..4bad419 --- /dev/null +++ b/brouter-util/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.btools + brouter + 0.98 + ../pom.xml + + brouter-util + jar + + + + junit + junit + + + + diff --git a/brouter-util/src/main/java/btools/util/CompactLongMap.java b/brouter-util/src/main/java/btools/util/CompactLongMap.java new file mode 100644 index 0000000..314772b --- /dev/null +++ b/brouter-util/src/main/java/btools/util/CompactLongMap.java @@ -0,0 +1,328 @@ +package btools.util; + +import java.util.ArrayList; + +/** + * Memory efficient Map to map a long-key to an object-value + * + * Implementation is such that basically the 12 bytes + * per entry is allocated that's needed to store + * a long- and an object-value. + * This class does not implement the Map interface + * because it's not complete (remove() is not implemented, + * CompactLongMap can only grow.) + * + * @author ab + */ +public class CompactLongMap +{ + private long[][] al; + private int[] pa; + private int size = 0; + private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays + + private V value_in; + protected V value_out; + + protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE + private static boolean earlyDuplicateCheck; + + public CompactLongMap() + { + // pointer array + pa = new int[MAXLISTS]; + + // allocate key lists + al = new long[MAXLISTS][]; + al[0] = new long[1]; // make the first array (the transient buffer) + + // same for the values + vla = new Object[MAXLISTS][]; + vla[0] = new Object[1]; + + earlyDuplicateCheck = Boolean.getBoolean( "earlyDuplicateCheck" ); + } + + + /* + * + * The Map extension: + * next 5 protected methods are needed to implement value-support + * overwrite them all to support value structures other than the + * long-values implemented here as a sample. + * + * Furthermore, put() and get() method need to be implemented + * to access the values. + * + * Note that this map does not behave exactly like java.util.Map + * - put(..) with already existing key throws exception + * - get(..) with non-existing key thros exception + * + * If you have keys that cannot easily be mapped on long's, use + * a hash-function to do the mapping. But note that, in comparison + * to java.util.HashMap, in that case the keys itself are not saved, + * only the hash-values, so you need to be sure that random duplicate + * hashs are either excluded by the structure of your data or that + * you can handle the possible IllegalArgumentException + * + */ + + private Object[][] vla; // value list array + + + public boolean put( long id, V value ) + { + try + { + value_in = value; + if ( contains( id, true ) ) + { + return true; + } + vla[0][0] = value; + _add( id ); + return false; + } + finally + { + value_in = null; + value_out = null; + } + } + + /** + * Same as put( id, value ) but duplicate check + * is skipped for performance. Be aware that you + * can get a duplicate exception later on if the + * map is restructured! + * with System parameter earlyDuplicateCheck=true you + * can enforce the early duplicate check for debugging + * + * @param id the key to insert + * @param value the value to insert object + * @exception IllegalArgumentException for duplicates if enabled + */ + public void fastPut( long id, V value ) + { + if ( earlyDuplicateCheck && contains( id ) ) + { + throw new IllegalArgumentException( "duplicate key found in early check: " + id ); + } + vla[0][0] = value; + _add( id ); + } + + /** + * Get the value for the given id + * @param id the key to query + * @return the object, or null if id not known + */ + public V get( long id ) + { + try + { + if ( contains( id, false ) ) + { + return value_out; + } + return null; + } + finally + { + value_out = null; + } + } + + + /** + * @return the number of entries in this map + */ + public int size() + { + return size; + } + + + private boolean _add( long id ) + { + if ( size == Integer.MAX_VALUE ) + { + throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" ); + } + + // put the new entry in the first array + al[0][0] = id; + + // determine the first empty array + int bp = size++; // treat size as bitpattern + int idx = 1; + int n = 1; + + pa[0] = 1; + pa[1] = 1; + + while ( (bp&1) == 1 ) + { + bp >>= 1; + pa[idx++] = n; + n <<= 1; + } + + // create it if not existant + if ( al[idx] == null ) + { + al[idx] = new long[n]; + vla[idx] = new Object[n]; + } + + // now merge the contents of arrays 0...idx-1 into idx + while ( n > 0 ) + { + long maxId = 0; + int maxIdx = -1; + + for ( int i=0; i 0 ) + { + long currentId = al[i][p-1]; + if ( maxIdx < 0 || currentId > maxId ) + { + maxIdx = i; + maxId = currentId; + } + } + } + + // current maximum found, copy to target array + if ( n < al[idx].length && maxId == al[idx][n] ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + maxId ); + } + --n; + al[idx][n] = maxId; + vla[idx][n] = vla[maxIdx][pa[maxIdx]-1]; + + --pa[maxIdx]; + } + + // de-allocate empty arrays of a certain size (fix at 64kByte) + while ( idx-- > _maxKeepExponent ) + { + al[idx] = null; + vla[idx] = null; + } + + return false; + } + + /** + * @return true if "id" is contained in this set. + */ + public boolean contains( long id ) + { + try + { + return contains( id, false ); + } + finally + { + value_out = null; + } + } + + protected boolean contains( long id, boolean doPut ) + { + // determine the first empty array + int bp = size; // treat size as bitpattern + int idx = 1; + + while ( bp != 0 ) + { + if ( (bp&1) == 1 ) + { + // array at idx is valid, check + if ( contains( idx, id, doPut ) ) + { + return true; + } + } + idx++; + bp >>= 1; + } + return false; + } + + + // does sorted array "a" contain "id" ? + private boolean contains( int idx, long id, boolean doPut ) + { + long[] a = al[idx]; + int offset = a.length; + int n = 0; + + while ( (offset >>= 1) > 0 ) + { + int nn = n + offset; + if ( a[nn] <= id ) + { + n = nn; + } + } + if ( a[n] == id ) + { + value_out = (V)vla[idx][n]; + if ( doPut ) vla[idx][n] = value_in; + return true; + } + return false; + } + + protected void moveToFrozenArrays( long[] faid, ArrayList flv ) + { + for( int i=1; i>= 1; + } + faid[ti] = minId; + flv.add( (V)vla[minIdx][pa[minIdx]] ); + pa[minIdx]++; + + if ( ti > 0 && faid[ti-1] == minId ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + minId ); + } + } + + // free the non-frozen arrays + al = null; + vla = null; + } + +} diff --git a/brouter-util/src/main/java/btools/util/CompactLongSet.java b/brouter-util/src/main/java/btools/util/CompactLongSet.java new file mode 100644 index 0000000..08dace4 --- /dev/null +++ b/brouter-util/src/main/java/btools/util/CompactLongSet.java @@ -0,0 +1,220 @@ +package btools.util; + +/** + * Memory efficient Set for long-keys + * + * @author ab + */ +public class CompactLongSet +{ + private long[][] al; + private int[] pa; + private int size = 0; + private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays + + protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE + private static boolean earlyDuplicateCheck; + + public CompactLongSet() + { + // pointer array + pa = new int[MAXLISTS]; + + // allocate key lists + al = new long[MAXLISTS][]; + al[0] = new long[1]; // make the first array (the transient buffer) + + earlyDuplicateCheck = Boolean.getBoolean( "earlyDuplicateCheck" ); + } + + + /** + * @return the number of entries in this set + */ + public int size() + { + return size; + } + + /** + * add a long value to this set if not yet in. + * @param id the value to add to this set. + * @return true if "id" already contained in this set. + */ + public boolean add( long id ) + { + if ( contains( id ) ) + { + return true; + } + _add( id ); + return false; + } + + public void fastAdd( long id ) + { + _add( id ); + } + + private void _add( long id ) + { + if ( size == Integer.MAX_VALUE ) + { + throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" ); + } + + // put the new entry in the first array + al[0][0] = id; + + // determine the first empty array + int bp = size++; // treat size as bitpattern + int idx = 1; + int n = 1; + + pa[0] = 1; + pa[1] = 1; + + while ( (bp&1) == 1 ) + { + bp >>= 1; + pa[idx++] = n; + n <<= 1; + } + + // create it if not existant + if ( al[idx] == null ) + { + al[idx] = new long[n]; + } + + // now merge the contents of arrays 0...idx-1 into idx + while ( n > 0 ) + { + long maxId = 0; + int maxIdx = -1; + + for ( int i=0; i 0 ) + { + long currentId = al[i][p-1]; + if ( maxIdx < 0 || currentId > maxId ) + { + maxIdx = i; + maxId = currentId; + } + } + } + + // current maximum found, copy to target array + if ( n < al[idx].length && maxId == al[idx][n] ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + maxId ); + } + --n; + al[idx][n] = maxId; + + --pa[maxIdx]; + } + + // de-allocate empty arrays of a certain size (fix at 64kByte) + while ( idx-- > _maxKeepExponent ) + { + al[idx] = null; + } + } + + /** + * @return true if "id" is contained in this set. + */ + public boolean contains( long id ) + { + // determine the first empty array + int bp = size; // treat size as bitpattern + int idx = 1; + + while ( bp != 0 ) + { + if ( (bp&1) == 1 ) + { + // array at idx is valid, check + if ( contains( idx, id ) ) + { + return true; + } + } + idx++; + bp >>= 1; + } + return false; + } + + + // does sorted array "a" contain "id" ? + private boolean contains( int idx, long id ) + { + long[] a = al[idx]; + int offset = a.length; + int n = 0; + + while ( (offset >>= 1) > 0 ) + { + int nn = n + offset; + if ( a[nn] <= id ) + { + n = nn; + } + } + if ( a[n] == id ) + { + return true; + } + return false; + } + + protected void moveToFrozenArray( long[] faid ) + { + for( int i=1; i>= 1; + } + faid[ti] = minId; + pa[minIdx]++; + + if ( ti > 0 && faid[ti-1] == minId ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + minId ); + } + } + + // free the non-frozen array + al = null; + } + +} diff --git a/brouter-util/src/main/java/btools/util/DenseLongMap.java b/brouter-util/src/main/java/btools/util/DenseLongMap.java new file mode 100644 index 0000000..00b7dcc --- /dev/null +++ b/brouter-util/src/main/java/btools/util/DenseLongMap.java @@ -0,0 +1,140 @@ +package btools.util; + +import java.util.ArrayList; + +/** + * Special Memory efficient Map to map a long-key to + * a "small" value (some bits only) where it is expected + * that the keys are dense, so that we can use more or less + * a simple array as the best-fit data model (except for + * the 32-bit limit of arrays!) + * + * Additionally, to enable small-memory unit testing + * of code using this map this one has a fallback for a small map + * where we have only few keys, but not dense. In this + * case, we use the mechanics of the CompactLongMap + * + * Target application are osm-node ids which are in the + * range 0...3 billion and basically dense (=only few + * nodes deleted) + * + * @author ab + */ +public class DenseLongMap +{ + private ArrayList blocklist = new ArrayList(1024); + + private static final int BLOCKSIZE = 0x10000; // 64k * 32 bits + private int valuebits; + private int maxvalue; + private long maxkey; + private long maxmemory; + + /** + * Creates a DenseLongMap for the given value range + * Note that one value is reserved for the "unset" state, + * so with 6 value bits you can store values in the + * range 0..62 only + * + * @param valuebits number of bits to use per value + */ + public DenseLongMap( int valuebits ) + { + if ( valuebits < 1 || valuebits > 32 ) + { + throw new IllegalArgumentException( "invalid valuebits (1..32): " + valuebits ); + } + this.valuebits = valuebits; + maxmemory = (Runtime.getRuntime().maxMemory() / 8) * 7; // assume most of it for our map + maxvalue = (1 << valuebits) - 2; + maxkey = ( maxmemory / valuebits ) * 8; + } + + + + public void put( long key, int value ) + { + if ( key < 0L || key > maxkey ) + { + throw new IllegalArgumentException( "key out of range (0.." + maxkey + "): " + key + + " give more memory (currently " + (maxmemory / 0x100000) + + "MB) to extend key range" ); + } + if ( value < 0 || value > maxvalue ) + { + throw new IllegalArgumentException( "value out of range (0.." + maxvalue + "): " + value ); + } + + int blockn = (int)(key >> 21); + int offset = (int)(key & 0x1fffff); + + int[] block = blockn < blocklist.size() ? blocklist.get( blockn ) : null; + + if ( block == null ) + { + block = new int[BLOCKSIZE * valuebits]; + + while (blocklist.size() < blockn+1 ) + { + blocklist.add(null); + } + blocklist.set( blockn, block ); + } + + int bitmask = 1 << (offset & 0x1f); + int invmask = bitmask ^ 0xffffffff; + int probebit = 1; + int blockidx = (offset >> 5)*valuebits; + int blockend = blockidx + valuebits; + int v = value + 1; // 0 is reserved (=unset) + + while( blockidx < blockend ) + { + if ( ( v & probebit ) != 0 ) + { + block[blockidx] |= bitmask; + } + else + { + block[blockidx] &= invmask; + } + probebit <<= 1; + blockidx++; + } + } + + + public int getInt( long key ) + { + if ( key < 0 ) + { + return -1; + } + int blockn = (int)(key >> 21); + int offset = (int)(key & 0x1fffff); + + int[] block = blockn < blocklist.size() ? blocklist.get( blockn ) : null; + + if ( block == null ) + { + return -1; + } + int bitmask = 1 << (offset & 0x1f); + int probebit = 1; + int blockidx = (offset >> 5)*valuebits; + int blockend = blockidx + valuebits; + int v = 0; // 0 is reserved (=unset) + + while( blockidx < blockend ) + { + if ( ( block[blockidx] & bitmask ) != 0 ) + { + v |= probebit; + } + probebit <<= 1; + blockidx++; + } + return v-1; + } + +} diff --git a/brouter-util/src/main/java/btools/util/FrozenLongMap.java b/brouter-util/src/main/java/btools/util/FrozenLongMap.java new file mode 100644 index 0000000..b308173 --- /dev/null +++ b/brouter-util/src/main/java/btools/util/FrozenLongMap.java @@ -0,0 +1,122 @@ +package btools.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Frozen instance of Memory efficient Map + * + * This one is readily sorted into a singe array for faster access + * + * @author ab + */ +public class FrozenLongMap extends CompactLongMap +{ + private long[] faid; + private ArrayList flv; + private int size = 0; + private int p2size; // next power of 2 of size + + public FrozenLongMap( CompactLongMap map ) + { + size = map.size(); + + faid = new long[size]; + flv = new ArrayList(size); + + map.moveToFrozenArrays( faid, flv ); + + p2size = 0x40000000; + while( p2size > size ) p2size >>= 1; + } + + @Override + public boolean put( long id, V value ) + { + throw new RuntimeException( "cannot put on FrozenLongIntMap" ); + } + + @Override + public void fastPut( long id, V value ) + { + throw new RuntimeException( "cannot put on FrozenLongIntMap" ); + } + + /** + * @return the number of entries in this set + */ + @Override + public int size() + { + return size; + } + + + + /** + * @return true if "id" is contained in this set. + */ + @Override + protected boolean contains( long id, boolean doPut ) + { + if ( size == 0 ) + { + return false; + } + long[] a = faid; + int offset = p2size; + int n = 0; + + while ( offset> 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + value_out = flv.get(n); + return true; + } + return false; + } + + /** + * @return the value for "id", + * Throw an exception if not contained in the map. + */ + @Override + public V get( long id ) + { + if ( size == 0 ) + { + return null; + } + long[] a = faid; + int offset = p2size; + int n = 0; + + while ( offset> 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + return flv.get(n); + } + return null; + } + + public List getValueList() + { + return flv; + } +} diff --git a/brouter-util/src/main/java/btools/util/FrozenLongSet.java b/brouter-util/src/main/java/btools/util/FrozenLongSet.java new file mode 100644 index 0000000..f6ef6b3 --- /dev/null +++ b/brouter-util/src/main/java/btools/util/FrozenLongSet.java @@ -0,0 +1,81 @@ +package btools.util; + +/** + * Frozen instance of Memory efficient Set + * + * This one is readily sorted into a singe array for faster access + * + * @author ab + */ +public class FrozenLongSet extends CompactLongSet +{ + private long[] faid; + private int size = 0; + private int p2size; // next power of 2 of size + + public FrozenLongSet( CompactLongSet set ) + { + size = set.size(); + + faid = new long[size]; + + set.moveToFrozenArray( faid ); + + p2size = 0x40000000; + while( p2size > size ) p2size >>= 1; + } + + @Override + public boolean add( long id ) + { + throw new RuntimeException( "cannot add on FrozenLongSet" ); + } + + @Override + public void fastAdd( long id ) + { + throw new RuntimeException( "cannot add on FrozenLongSet" ); + } + + /** + * @return the number of entries in this set + */ + @Override + public int size() + { + return size; + } + + + + /** + * @return true if "id" is contained in this set. + */ + @Override + public boolean contains( long id ) + { + if ( size == 0 ) + { + return false; + } + long[] a = faid; + int offset = p2size; + int n = 0; + + while ( offset> 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + return true; + } + return false; + } + +} diff --git a/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java b/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java new file mode 100644 index 0000000..850504b --- /dev/null +++ b/brouter-util/src/main/java/btools/util/LazyArrayOfLists.java @@ -0,0 +1,53 @@ +package btools.util; + +import java.util.List; +import java.util.ArrayList; + +/** + * Behaves like an Array of list + * with lazy list-allocation at getList + * + * @author ab + */ +public class LazyArrayOfLists +{ + private ArrayList> lists; + + public LazyArrayOfLists( int size ) + { + lists = new ArrayList>( size ); + for ( int i = 0; i< size; i++ ) + { + lists.add( null ); + } + } + + public List getList( int idx ) + { + ArrayList list = lists.get( idx ); + if ( list == null ) + { + list = new ArrayList(); + lists.set( idx, list ); + } + return list; + } + + public int getSize( int idx ) + { + List list = lists.get( idx ); + return list == null ? 0 : list.size(); + } + + public void trimAll() + { + for ( int idx = 0; idx< lists.size(); idx++ ) + { + ArrayList list = lists.get( idx ); + if ( list != null ) + { + list.trimToSize(); + } + } + } +} diff --git a/brouter-util/src/main/java/btools/util/LongList.java b/brouter-util/src/main/java/btools/util/LongList.java new file mode 100644 index 0000000..2da9914 --- /dev/null +++ b/brouter-util/src/main/java/btools/util/LongList.java @@ -0,0 +1,43 @@ +package btools.util; + +/** + * dynamic list of primitive longs + * + * @author ab + */ +public class LongList +{ + private long[] a; + private int size; + + public LongList( int capacity ) + { + a = capacity < 4 ? new long[4] : new long[capacity]; + } + + public void add( long value ) + { + if ( size == a.length ) + { + long[] aa = new long[2*size]; + System.arraycopy( a, 0, aa, 0, size ); + a = aa; + } + a[size++] = value; + } + + public long get( int idx ) + { + if ( idx >= size ) + { + throw new IndexOutOfBoundsException( "list size=" + size + " idx=" + idx ); + } + return a[idx]; + } + + public int size() + { + return size; + } + +} diff --git a/brouter-util/src/main/java/btools/util/TinyDenseLongMap.java b/brouter-util/src/main/java/btools/util/TinyDenseLongMap.java new file mode 100644 index 0000000..472b3c3 --- /dev/null +++ b/brouter-util/src/main/java/btools/util/TinyDenseLongMap.java @@ -0,0 +1,206 @@ +package btools.util; + +/** + * TinyDenseLongMap implements the DenseLongMap interface + * but actually is made for a medium count of non-dense keys + * + * It's used as a replacement for DenseLongMap where we + * have limited memory and far less keys than maykey + * + * @author ab + */ +public class TinyDenseLongMap extends DenseLongMap +{ + private long[][] al; + private int[] pa; + private int size = 0; + private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays + + protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE + + public TinyDenseLongMap() + { + super(1); + + // pointer array + pa = new int[MAXLISTS]; + + // allocate key lists + al = new long[MAXLISTS][]; + al[0] = new long[1]; // make the first array (the transient buffer) + + // same for the values + vla = new byte[MAXLISTS][]; + vla[0] = new byte[1]; + } + + + private byte[][] vla; // value list array + + private void fillReturnValue(byte[] rv, int idx, int p ) + { + rv[0] = vla[idx][p]; + if ( rv.length == 2 ) + { + vla[idx][p] = rv[1]; + } + } + + @Override + public void put( long id, int value ) + { + byte[] rv = new byte[2]; + rv[1] = (byte)value; + if ( contains( id, rv ) ) + { + return; + } + + vla[0][0] = (byte)value; + _add( id ); + } + + + /** + * Get the byte for the given id + * @param id the key to query + * @return the object + * @exception IllegalArgumentException if id is unknown + */ + @Override + public int getInt( long id ) + { + byte[] rv = new byte[1]; + if ( contains( id, rv ) ) + { + return rv[0]; + } + return -1; + } + + + private boolean _add( long id ) + { + if ( size == Integer.MAX_VALUE ) + { + throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" ); + } + + // put the new entry in the first array + al[0][0] = id; + + // determine the first empty array + int bp = size++; // treat size as bitpattern + int idx = 1; + int n = 1; + + pa[0] = 1; + pa[1] = 1; + + while ( (bp&1) == 1 ) + { + bp >>= 1; + pa[idx++] = n; + n <<= 1; + } + + // create it if not existant + if ( al[idx] == null ) + { + al[idx] = new long[n]; + vla[idx] = new byte[n]; + } + + // now merge the contents of arrays 0...idx-1 into idx + while ( n > 0 ) + { + long maxId = 0; + int maxIdx = -1; + + for ( int i=0; i 0 ) + { + long currentId = al[i][p-1]; + if ( maxIdx < 0 || currentId > maxId ) + { + maxIdx = i; + maxId = currentId; + } + } + } + + // current maximum found, copy to target array + if ( n < al[idx].length && maxId == al[idx][n] ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + maxId ); + } + --n; + al[idx][n] = maxId; + vla[idx][n] = vla[maxIdx][pa[maxIdx]-1]; + + --pa[maxIdx]; + } + + // de-allocate empty arrays of a certain size (fix at 64kByte) + while ( idx-- > _maxKeepExponent ) + { + al[idx] = null; + vla[idx] = null; + } + + return false; + } + + + private boolean contains( long id, byte[] rv ) + { + // determine the first empty array + int bp = size; // treat size as bitpattern + int idx = 1; + + while ( bp != 0 ) + { + if ( (bp&1) == 1 ) + { + // array at idx is valid, check + if ( contains( idx, id, rv ) ) + { + return true; + } + } + idx++; + bp >>= 1; + } + return false; + } + + + // does sorted array "a" contain "id" ? + private boolean contains( int idx, long id, byte[] rv ) + { + long[] a = al[idx]; + int offset = a.length; + int n = 0; + + while ( (offset >>= 1) > 0 ) + { + int nn = n + offset; + if ( a[nn] <= id ) + { + n = nn; + } + } + if ( a[n] == id ) + { + if ( rv != null ) + { + fillReturnValue( rv, idx, n ); + } + return true; + } + return false; + } + +} diff --git a/brouter-util/src/test/java/btools/util/CompactLongMap.java b/brouter-util/src/test/java/btools/util/CompactLongMap.java new file mode 100644 index 0000000..314772b --- /dev/null +++ b/brouter-util/src/test/java/btools/util/CompactLongMap.java @@ -0,0 +1,328 @@ +package btools.util; + +import java.util.ArrayList; + +/** + * Memory efficient Map to map a long-key to an object-value + * + * Implementation is such that basically the 12 bytes + * per entry is allocated that's needed to store + * a long- and an object-value. + * This class does not implement the Map interface + * because it's not complete (remove() is not implemented, + * CompactLongMap can only grow.) + * + * @author ab + */ +public class CompactLongMap +{ + private long[][] al; + private int[] pa; + private int size = 0; + private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays + + private V value_in; + protected V value_out; + + protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE + private static boolean earlyDuplicateCheck; + + public CompactLongMap() + { + // pointer array + pa = new int[MAXLISTS]; + + // allocate key lists + al = new long[MAXLISTS][]; + al[0] = new long[1]; // make the first array (the transient buffer) + + // same for the values + vla = new Object[MAXLISTS][]; + vla[0] = new Object[1]; + + earlyDuplicateCheck = Boolean.getBoolean( "earlyDuplicateCheck" ); + } + + + /* + * + * The Map extension: + * next 5 protected methods are needed to implement value-support + * overwrite them all to support value structures other than the + * long-values implemented here as a sample. + * + * Furthermore, put() and get() method need to be implemented + * to access the values. + * + * Note that this map does not behave exactly like java.util.Map + * - put(..) with already existing key throws exception + * - get(..) with non-existing key thros exception + * + * If you have keys that cannot easily be mapped on long's, use + * a hash-function to do the mapping. But note that, in comparison + * to java.util.HashMap, in that case the keys itself are not saved, + * only the hash-values, so you need to be sure that random duplicate + * hashs are either excluded by the structure of your data or that + * you can handle the possible IllegalArgumentException + * + */ + + private Object[][] vla; // value list array + + + public boolean put( long id, V value ) + { + try + { + value_in = value; + if ( contains( id, true ) ) + { + return true; + } + vla[0][0] = value; + _add( id ); + return false; + } + finally + { + value_in = null; + value_out = null; + } + } + + /** + * Same as put( id, value ) but duplicate check + * is skipped for performance. Be aware that you + * can get a duplicate exception later on if the + * map is restructured! + * with System parameter earlyDuplicateCheck=true you + * can enforce the early duplicate check for debugging + * + * @param id the key to insert + * @param value the value to insert object + * @exception IllegalArgumentException for duplicates if enabled + */ + public void fastPut( long id, V value ) + { + if ( earlyDuplicateCheck && contains( id ) ) + { + throw new IllegalArgumentException( "duplicate key found in early check: " + id ); + } + vla[0][0] = value; + _add( id ); + } + + /** + * Get the value for the given id + * @param id the key to query + * @return the object, or null if id not known + */ + public V get( long id ) + { + try + { + if ( contains( id, false ) ) + { + return value_out; + } + return null; + } + finally + { + value_out = null; + } + } + + + /** + * @return the number of entries in this map + */ + public int size() + { + return size; + } + + + private boolean _add( long id ) + { + if ( size == Integer.MAX_VALUE ) + { + throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" ); + } + + // put the new entry in the first array + al[0][0] = id; + + // determine the first empty array + int bp = size++; // treat size as bitpattern + int idx = 1; + int n = 1; + + pa[0] = 1; + pa[1] = 1; + + while ( (bp&1) == 1 ) + { + bp >>= 1; + pa[idx++] = n; + n <<= 1; + } + + // create it if not existant + if ( al[idx] == null ) + { + al[idx] = new long[n]; + vla[idx] = new Object[n]; + } + + // now merge the contents of arrays 0...idx-1 into idx + while ( n > 0 ) + { + long maxId = 0; + int maxIdx = -1; + + for ( int i=0; i 0 ) + { + long currentId = al[i][p-1]; + if ( maxIdx < 0 || currentId > maxId ) + { + maxIdx = i; + maxId = currentId; + } + } + } + + // current maximum found, copy to target array + if ( n < al[idx].length && maxId == al[idx][n] ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + maxId ); + } + --n; + al[idx][n] = maxId; + vla[idx][n] = vla[maxIdx][pa[maxIdx]-1]; + + --pa[maxIdx]; + } + + // de-allocate empty arrays of a certain size (fix at 64kByte) + while ( idx-- > _maxKeepExponent ) + { + al[idx] = null; + vla[idx] = null; + } + + return false; + } + + /** + * @return true if "id" is contained in this set. + */ + public boolean contains( long id ) + { + try + { + return contains( id, false ); + } + finally + { + value_out = null; + } + } + + protected boolean contains( long id, boolean doPut ) + { + // determine the first empty array + int bp = size; // treat size as bitpattern + int idx = 1; + + while ( bp != 0 ) + { + if ( (bp&1) == 1 ) + { + // array at idx is valid, check + if ( contains( idx, id, doPut ) ) + { + return true; + } + } + idx++; + bp >>= 1; + } + return false; + } + + + // does sorted array "a" contain "id" ? + private boolean contains( int idx, long id, boolean doPut ) + { + long[] a = al[idx]; + int offset = a.length; + int n = 0; + + while ( (offset >>= 1) > 0 ) + { + int nn = n + offset; + if ( a[nn] <= id ) + { + n = nn; + } + } + if ( a[n] == id ) + { + value_out = (V)vla[idx][n]; + if ( doPut ) vla[idx][n] = value_in; + return true; + } + return false; + } + + protected void moveToFrozenArrays( long[] faid, ArrayList flv ) + { + for( int i=1; i>= 1; + } + faid[ti] = minId; + flv.add( (V)vla[minIdx][pa[minIdx]] ); + pa[minIdx]++; + + if ( ti > 0 && faid[ti-1] == minId ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + minId ); + } + } + + // free the non-frozen arrays + al = null; + vla = null; + } + +} diff --git a/brouter-util/src/test/java/btools/util/CompactLongSet.java b/brouter-util/src/test/java/btools/util/CompactLongSet.java new file mode 100644 index 0000000..08dace4 --- /dev/null +++ b/brouter-util/src/test/java/btools/util/CompactLongSet.java @@ -0,0 +1,220 @@ +package btools.util; + +/** + * Memory efficient Set for long-keys + * + * @author ab + */ +public class CompactLongSet +{ + private long[][] al; + private int[] pa; + private int size = 0; + private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays + + protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE + private static boolean earlyDuplicateCheck; + + public CompactLongSet() + { + // pointer array + pa = new int[MAXLISTS]; + + // allocate key lists + al = new long[MAXLISTS][]; + al[0] = new long[1]; // make the first array (the transient buffer) + + earlyDuplicateCheck = Boolean.getBoolean( "earlyDuplicateCheck" ); + } + + + /** + * @return the number of entries in this set + */ + public int size() + { + return size; + } + + /** + * add a long value to this set if not yet in. + * @param id the value to add to this set. + * @return true if "id" already contained in this set. + */ + public boolean add( long id ) + { + if ( contains( id ) ) + { + return true; + } + _add( id ); + return false; + } + + public void fastAdd( long id ) + { + _add( id ); + } + + private void _add( long id ) + { + if ( size == Integer.MAX_VALUE ) + { + throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" ); + } + + // put the new entry in the first array + al[0][0] = id; + + // determine the first empty array + int bp = size++; // treat size as bitpattern + int idx = 1; + int n = 1; + + pa[0] = 1; + pa[1] = 1; + + while ( (bp&1) == 1 ) + { + bp >>= 1; + pa[idx++] = n; + n <<= 1; + } + + // create it if not existant + if ( al[idx] == null ) + { + al[idx] = new long[n]; + } + + // now merge the contents of arrays 0...idx-1 into idx + while ( n > 0 ) + { + long maxId = 0; + int maxIdx = -1; + + for ( int i=0; i 0 ) + { + long currentId = al[i][p-1]; + if ( maxIdx < 0 || currentId > maxId ) + { + maxIdx = i; + maxId = currentId; + } + } + } + + // current maximum found, copy to target array + if ( n < al[idx].length && maxId == al[idx][n] ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + maxId ); + } + --n; + al[idx][n] = maxId; + + --pa[maxIdx]; + } + + // de-allocate empty arrays of a certain size (fix at 64kByte) + while ( idx-- > _maxKeepExponent ) + { + al[idx] = null; + } + } + + /** + * @return true if "id" is contained in this set. + */ + public boolean contains( long id ) + { + // determine the first empty array + int bp = size; // treat size as bitpattern + int idx = 1; + + while ( bp != 0 ) + { + if ( (bp&1) == 1 ) + { + // array at idx is valid, check + if ( contains( idx, id ) ) + { + return true; + } + } + idx++; + bp >>= 1; + } + return false; + } + + + // does sorted array "a" contain "id" ? + private boolean contains( int idx, long id ) + { + long[] a = al[idx]; + int offset = a.length; + int n = 0; + + while ( (offset >>= 1) > 0 ) + { + int nn = n + offset; + if ( a[nn] <= id ) + { + n = nn; + } + } + if ( a[n] == id ) + { + return true; + } + return false; + } + + protected void moveToFrozenArray( long[] faid ) + { + for( int i=1; i>= 1; + } + faid[ti] = minId; + pa[minIdx]++; + + if ( ti > 0 && faid[ti-1] == minId ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + minId ); + } + } + + // free the non-frozen array + al = null; + } + +} diff --git a/brouter-util/src/test/java/btools/util/DenseLongMap.java b/brouter-util/src/test/java/btools/util/DenseLongMap.java new file mode 100644 index 0000000..00b7dcc --- /dev/null +++ b/brouter-util/src/test/java/btools/util/DenseLongMap.java @@ -0,0 +1,140 @@ +package btools.util; + +import java.util.ArrayList; + +/** + * Special Memory efficient Map to map a long-key to + * a "small" value (some bits only) where it is expected + * that the keys are dense, so that we can use more or less + * a simple array as the best-fit data model (except for + * the 32-bit limit of arrays!) + * + * Additionally, to enable small-memory unit testing + * of code using this map this one has a fallback for a small map + * where we have only few keys, but not dense. In this + * case, we use the mechanics of the CompactLongMap + * + * Target application are osm-node ids which are in the + * range 0...3 billion and basically dense (=only few + * nodes deleted) + * + * @author ab + */ +public class DenseLongMap +{ + private ArrayList blocklist = new ArrayList(1024); + + private static final int BLOCKSIZE = 0x10000; // 64k * 32 bits + private int valuebits; + private int maxvalue; + private long maxkey; + private long maxmemory; + + /** + * Creates a DenseLongMap for the given value range + * Note that one value is reserved for the "unset" state, + * so with 6 value bits you can store values in the + * range 0..62 only + * + * @param valuebits number of bits to use per value + */ + public DenseLongMap( int valuebits ) + { + if ( valuebits < 1 || valuebits > 32 ) + { + throw new IllegalArgumentException( "invalid valuebits (1..32): " + valuebits ); + } + this.valuebits = valuebits; + maxmemory = (Runtime.getRuntime().maxMemory() / 8) * 7; // assume most of it for our map + maxvalue = (1 << valuebits) - 2; + maxkey = ( maxmemory / valuebits ) * 8; + } + + + + public void put( long key, int value ) + { + if ( key < 0L || key > maxkey ) + { + throw new IllegalArgumentException( "key out of range (0.." + maxkey + "): " + key + + " give more memory (currently " + (maxmemory / 0x100000) + + "MB) to extend key range" ); + } + if ( value < 0 || value > maxvalue ) + { + throw new IllegalArgumentException( "value out of range (0.." + maxvalue + "): " + value ); + } + + int blockn = (int)(key >> 21); + int offset = (int)(key & 0x1fffff); + + int[] block = blockn < blocklist.size() ? blocklist.get( blockn ) : null; + + if ( block == null ) + { + block = new int[BLOCKSIZE * valuebits]; + + while (blocklist.size() < blockn+1 ) + { + blocklist.add(null); + } + blocklist.set( blockn, block ); + } + + int bitmask = 1 << (offset & 0x1f); + int invmask = bitmask ^ 0xffffffff; + int probebit = 1; + int blockidx = (offset >> 5)*valuebits; + int blockend = blockidx + valuebits; + int v = value + 1; // 0 is reserved (=unset) + + while( blockidx < blockend ) + { + if ( ( v & probebit ) != 0 ) + { + block[blockidx] |= bitmask; + } + else + { + block[blockidx] &= invmask; + } + probebit <<= 1; + blockidx++; + } + } + + + public int getInt( long key ) + { + if ( key < 0 ) + { + return -1; + } + int blockn = (int)(key >> 21); + int offset = (int)(key & 0x1fffff); + + int[] block = blockn < blocklist.size() ? blocklist.get( blockn ) : null; + + if ( block == null ) + { + return -1; + } + int bitmask = 1 << (offset & 0x1f); + int probebit = 1; + int blockidx = (offset >> 5)*valuebits; + int blockend = blockidx + valuebits; + int v = 0; // 0 is reserved (=unset) + + while( blockidx < blockend ) + { + if ( ( block[blockidx] & bitmask ) != 0 ) + { + v |= probebit; + } + probebit <<= 1; + blockidx++; + } + return v-1; + } + +} diff --git a/brouter-util/src/test/java/btools/util/FrozenLongMap.java b/brouter-util/src/test/java/btools/util/FrozenLongMap.java new file mode 100644 index 0000000..b308173 --- /dev/null +++ b/brouter-util/src/test/java/btools/util/FrozenLongMap.java @@ -0,0 +1,122 @@ +package btools.util; + +import java.util.ArrayList; +import java.util.List; + +/** + * Frozen instance of Memory efficient Map + * + * This one is readily sorted into a singe array for faster access + * + * @author ab + */ +public class FrozenLongMap extends CompactLongMap +{ + private long[] faid; + private ArrayList flv; + private int size = 0; + private int p2size; // next power of 2 of size + + public FrozenLongMap( CompactLongMap map ) + { + size = map.size(); + + faid = new long[size]; + flv = new ArrayList(size); + + map.moveToFrozenArrays( faid, flv ); + + p2size = 0x40000000; + while( p2size > size ) p2size >>= 1; + } + + @Override + public boolean put( long id, V value ) + { + throw new RuntimeException( "cannot put on FrozenLongIntMap" ); + } + + @Override + public void fastPut( long id, V value ) + { + throw new RuntimeException( "cannot put on FrozenLongIntMap" ); + } + + /** + * @return the number of entries in this set + */ + @Override + public int size() + { + return size; + } + + + + /** + * @return true if "id" is contained in this set. + */ + @Override + protected boolean contains( long id, boolean doPut ) + { + if ( size == 0 ) + { + return false; + } + long[] a = faid; + int offset = p2size; + int n = 0; + + while ( offset> 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + value_out = flv.get(n); + return true; + } + return false; + } + + /** + * @return the value for "id", + * Throw an exception if not contained in the map. + */ + @Override + public V get( long id ) + { + if ( size == 0 ) + { + return null; + } + long[] a = faid; + int offset = p2size; + int n = 0; + + while ( offset> 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + return flv.get(n); + } + return null; + } + + public List getValueList() + { + return flv; + } +} diff --git a/brouter-util/src/test/java/btools/util/FrozenLongSet.java b/brouter-util/src/test/java/btools/util/FrozenLongSet.java new file mode 100644 index 0000000..f6ef6b3 --- /dev/null +++ b/brouter-util/src/test/java/btools/util/FrozenLongSet.java @@ -0,0 +1,81 @@ +package btools.util; + +/** + * Frozen instance of Memory efficient Set + * + * This one is readily sorted into a singe array for faster access + * + * @author ab + */ +public class FrozenLongSet extends CompactLongSet +{ + private long[] faid; + private int size = 0; + private int p2size; // next power of 2 of size + + public FrozenLongSet( CompactLongSet set ) + { + size = set.size(); + + faid = new long[size]; + + set.moveToFrozenArray( faid ); + + p2size = 0x40000000; + while( p2size > size ) p2size >>= 1; + } + + @Override + public boolean add( long id ) + { + throw new RuntimeException( "cannot add on FrozenLongSet" ); + } + + @Override + public void fastAdd( long id ) + { + throw new RuntimeException( "cannot add on FrozenLongSet" ); + } + + /** + * @return the number of entries in this set + */ + @Override + public int size() + { + return size; + } + + + + /** + * @return true if "id" is contained in this set. + */ + @Override + public boolean contains( long id ) + { + if ( size == 0 ) + { + return false; + } + long[] a = faid; + int offset = p2size; + int n = 0; + + while ( offset> 0 ) + { + int nn = n + offset; + if ( nn < size && a[nn] <= id ) + { + n = nn; + } + offset >>= 1; + } + if ( a[n] == id ) + { + return true; + } + return false; + } + +} diff --git a/brouter-util/src/test/java/btools/util/LazyArrayOfLists.java b/brouter-util/src/test/java/btools/util/LazyArrayOfLists.java new file mode 100644 index 0000000..850504b --- /dev/null +++ b/brouter-util/src/test/java/btools/util/LazyArrayOfLists.java @@ -0,0 +1,53 @@ +package btools.util; + +import java.util.List; +import java.util.ArrayList; + +/** + * Behaves like an Array of list + * with lazy list-allocation at getList + * + * @author ab + */ +public class LazyArrayOfLists +{ + private ArrayList> lists; + + public LazyArrayOfLists( int size ) + { + lists = new ArrayList>( size ); + for ( int i = 0; i< size; i++ ) + { + lists.add( null ); + } + } + + public List getList( int idx ) + { + ArrayList list = lists.get( idx ); + if ( list == null ) + { + list = new ArrayList(); + lists.set( idx, list ); + } + return list; + } + + public int getSize( int idx ) + { + List list = lists.get( idx ); + return list == null ? 0 : list.size(); + } + + public void trimAll() + { + for ( int idx = 0; idx< lists.size(); idx++ ) + { + ArrayList list = lists.get( idx ); + if ( list != null ) + { + list.trimToSize(); + } + } + } +} diff --git a/brouter-util/src/test/java/btools/util/LongList.java b/brouter-util/src/test/java/btools/util/LongList.java new file mode 100644 index 0000000..2da9914 --- /dev/null +++ b/brouter-util/src/test/java/btools/util/LongList.java @@ -0,0 +1,43 @@ +package btools.util; + +/** + * dynamic list of primitive longs + * + * @author ab + */ +public class LongList +{ + private long[] a; + private int size; + + public LongList( int capacity ) + { + a = capacity < 4 ? new long[4] : new long[capacity]; + } + + public void add( long value ) + { + if ( size == a.length ) + { + long[] aa = new long[2*size]; + System.arraycopy( a, 0, aa, 0, size ); + a = aa; + } + a[size++] = value; + } + + public long get( int idx ) + { + if ( idx >= size ) + { + throw new IndexOutOfBoundsException( "list size=" + size + " idx=" + idx ); + } + return a[idx]; + } + + public int size() + { + return size; + } + +} diff --git a/brouter-util/src/test/java/btools/util/TinyDenseLongMap.java b/brouter-util/src/test/java/btools/util/TinyDenseLongMap.java new file mode 100644 index 0000000..472b3c3 --- /dev/null +++ b/brouter-util/src/test/java/btools/util/TinyDenseLongMap.java @@ -0,0 +1,206 @@ +package btools.util; + +/** + * TinyDenseLongMap implements the DenseLongMap interface + * but actually is made for a medium count of non-dense keys + * + * It's used as a replacement for DenseLongMap where we + * have limited memory and far less keys than maykey + * + * @author ab + */ +public class TinyDenseLongMap extends DenseLongMap +{ + private long[][] al; + private int[] pa; + private int size = 0; + private int _maxKeepExponent = 14; // the maximum exponent to keep the invalid arrays + + protected static final int MAXLISTS = 31; // enough for size Integer.MAX_VALUE + + public TinyDenseLongMap() + { + super(1); + + // pointer array + pa = new int[MAXLISTS]; + + // allocate key lists + al = new long[MAXLISTS][]; + al[0] = new long[1]; // make the first array (the transient buffer) + + // same for the values + vla = new byte[MAXLISTS][]; + vla[0] = new byte[1]; + } + + + private byte[][] vla; // value list array + + private void fillReturnValue(byte[] rv, int idx, int p ) + { + rv[0] = vla[idx][p]; + if ( rv.length == 2 ) + { + vla[idx][p] = rv[1]; + } + } + + @Override + public void put( long id, int value ) + { + byte[] rv = new byte[2]; + rv[1] = (byte)value; + if ( contains( id, rv ) ) + { + return; + } + + vla[0][0] = (byte)value; + _add( id ); + } + + + /** + * Get the byte for the given id + * @param id the key to query + * @return the object + * @exception IllegalArgumentException if id is unknown + */ + @Override + public int getInt( long id ) + { + byte[] rv = new byte[1]; + if ( contains( id, rv ) ) + { + return rv[0]; + } + return -1; + } + + + private boolean _add( long id ) + { + if ( size == Integer.MAX_VALUE ) + { + throw new IllegalArgumentException( "cannot grow beyond size Integer.MAX_VALUE" ); + } + + // put the new entry in the first array + al[0][0] = id; + + // determine the first empty array + int bp = size++; // treat size as bitpattern + int idx = 1; + int n = 1; + + pa[0] = 1; + pa[1] = 1; + + while ( (bp&1) == 1 ) + { + bp >>= 1; + pa[idx++] = n; + n <<= 1; + } + + // create it if not existant + if ( al[idx] == null ) + { + al[idx] = new long[n]; + vla[idx] = new byte[n]; + } + + // now merge the contents of arrays 0...idx-1 into idx + while ( n > 0 ) + { + long maxId = 0; + int maxIdx = -1; + + for ( int i=0; i 0 ) + { + long currentId = al[i][p-1]; + if ( maxIdx < 0 || currentId > maxId ) + { + maxIdx = i; + maxId = currentId; + } + } + } + + // current maximum found, copy to target array + if ( n < al[idx].length && maxId == al[idx][n] ) + { + throw new IllegalArgumentException( "duplicate key found in late check: " + maxId ); + } + --n; + al[idx][n] = maxId; + vla[idx][n] = vla[maxIdx][pa[maxIdx]-1]; + + --pa[maxIdx]; + } + + // de-allocate empty arrays of a certain size (fix at 64kByte) + while ( idx-- > _maxKeepExponent ) + { + al[idx] = null; + vla[idx] = null; + } + + return false; + } + + + private boolean contains( long id, byte[] rv ) + { + // determine the first empty array + int bp = size; // treat size as bitpattern + int idx = 1; + + while ( bp != 0 ) + { + if ( (bp&1) == 1 ) + { + // array at idx is valid, check + if ( contains( idx, id, rv ) ) + { + return true; + } + } + idx++; + bp >>= 1; + } + return false; + } + + + // does sorted array "a" contain "id" ? + private boolean contains( int idx, long id, byte[] rv ) + { + long[] a = al[idx]; + int offset = a.length; + int n = 0; + + while ( (offset >>= 1) > 0 ) + { + int nn = n + offset; + if ( a[nn] <= id ) + { + n = nn; + } + } + if ( a[n] == id ) + { + if ( rv != null ) + { + fillReturnValue( rv, idx, n ); + } + return true; + } + return false; + } + +} diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..6ca1e23 --- /dev/null +++ b/pom.xml @@ -0,0 +1,153 @@ + + + 4.0.0 + org.btools + brouter + 0.98 + pom + http://brensche.de/brouter/ + brouter + The brouter project provides bike routing for OpenStreetMap. + + + brouter-util + brouter-expressions + brouter-mapaccess + brouter-core + brouter-map-creator + brouter-server + brouter-routing-app + + + + scm:svn:http://www.routeconverter.de/subversion/BRouter/ + scm:svn:http://www.routeconverter.de/subversion/BRouter/ + http://www.routeconverter.de/subversion/BRouter/ + HEAD + + + + + arndt.brenschede + Arndt Brenschede + Arndt.Brenschede@web.de + + + + + UTF-8 + UTF-8 + 1.6 + + + + + + + com.jayway.maven.plugins.android.generation2 + android-maven-plugin + 3.6.0 + true + + gen + gen + true + + 10 + + + + + + org.apache.maven.plugins + maven-assembly-plugin + 2.4 + + + jar-with-dependencies + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.1 + + ${targetJdk} + ${targetJdk} + -Xlint:unchecked + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 2.9 + + false + brensche.de/brouter]]> + true + protected + false + + + + + org.apache.maven.plugins + maven-resources-plugin + 2.6 + + + + org.apache.maven.plugins + maven-source-plugin + 2.2.1 + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadoc + package + + jar + + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + package + + jar-no-fork + + + + + + + + + + + junit + junit + 4.11 + test + + + + +