package btools.mapcreator; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.File; import java.io.FileInputStream; import java.io.RandomAccessFile; import java.util.List; import java.util.TreeMap; import btools.codec.DataBuffers; import btools.codec.MicroCache; import btools.codec.MicroCache2; import btools.codec.StatCoderContext; import btools.expressions.BExpressionContextWay; import btools.expressions.BExpressionMetaData; import btools.util.ByteArrayUnifier; import btools.util.CompactLongMap; import btools.util.CompactLongSet; import btools.util.Crc32; import btools.util.FrozenLongMap; import btools.util.FrozenLongSet; import btools.util.LazyArrayOfLists; /** * WayLinker finally puts the pieces together to create the rd5 files. For each * 5*5 tile, the corresponding nodefile and wayfile is read, plus the (global) * bordernodes file, and an rd5 is written * * @author ab */ public class WayLinker extends MapCreatorBase implements Runnable { private File nodeTilesIn; private File wayTilesIn; private File trafficTilesIn; private File dataTilesOut; private File borderFileIn; private File restrictionsFileIn; private String dataTilesSuffix; private boolean readingBorder; private CompactLongMap nodesMap; private OsmTrafficMap trafficMap; private List nodesList; private CompactLongSet borderSet; private short lookupVersion; private short lookupMinorVersion; private long creationTimeStamp; private BExpressionContextWay expctxWay; private ByteArrayUnifier abUnifier; private int minLon; private int minLat; private int microCacheEncoding = 2; private int divisor = 32; private int cellsize = 1000000 / divisor; private boolean skipEncodingCheck; private boolean isSlave; private ThreadController tc; public static final class ThreadController { long maxFileSize = 0L; long currentSlaveSize; long currentMasterSize = 2000000000L; synchronized boolean setCurrentMasterSize( long size ) { try { if ( size <= currentSlaveSize ) { maxFileSize = Long.MAX_VALUE; return false; } currentMasterSize = size; if ( maxFileSize == 0L ) { maxFileSize = size; } return true; } finally { notify(); } } synchronized boolean setCurrentSlaveSize( long size ) throws Exception { if ( size >= currentMasterSize ) { return false; } while ( size + currentMasterSize + 50000000L > maxFileSize ) { System.out.println( "****** slave thread waiting for permission to process file of size " + size + " currentMaster=" + currentMasterSize + " maxFileSize=" + maxFileSize ); wait( 10000 ); } currentSlaveSize = size; return true; } } 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 region of an OSM map for routing" ); if ( args.length != 8 ) { 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] ), new File( args[6] ), args[7] ); System.out.println( "dumping bad TRs" ); RestrictionData.dumpBadTRs(); } public void process( File nodeTilesIn, File wayTilesIn, File borderFileIn, File restrictionsFileIn, File lookupFile, File profileFile, File dataTilesOut, String dataTilesSuffix ) throws Exception { WayLinker master = new WayLinker(); WayLinker slave = new WayLinker(); slave.isSlave = true; master.isSlave = false; ThreadController tc = new ThreadController(); slave.tc = tc; master.tc = tc; master._process( nodeTilesIn, wayTilesIn, borderFileIn, restrictionsFileIn, lookupFile, profileFile, dataTilesOut, dataTilesSuffix ); slave._process( nodeTilesIn, wayTilesIn, borderFileIn, restrictionsFileIn, lookupFile, profileFile, dataTilesOut, dataTilesSuffix ); Thread m = new Thread( master ); Thread s = new Thread( slave ); m.start(); s.start(); m.join(); s.join(); } private void _process( File nodeTilesIn, File wayTilesIn, File borderFileIn, File restrictionsFileIn, File lookupFile, File profileFile, File dataTilesOut, String dataTilesSuffix ) throws Exception { this.nodeTilesIn = nodeTilesIn; this.wayTilesIn = wayTilesIn; this.trafficTilesIn = new File( "../traffic" ); this.dataTilesOut = dataTilesOut; this.borderFileIn = borderFileIn; this.restrictionsFileIn = restrictionsFileIn; this.dataTilesSuffix = dataTilesSuffix; BExpressionMetaData meta = new BExpressionMetaData(); // read lookup + profile for lookup-version + access-filter expctxWay = new BExpressionContextWay( meta ); meta.readMetaData( lookupFile ); lookupVersion = meta.lookupVersion; lookupMinorVersion = meta.lookupMinorVersion; expctxWay.parseFile( profileFile, "global" ); creationTimeStamp = System.currentTimeMillis(); abUnifier = new ByteArrayUnifier( 16384, false ); skipEncodingCheck = Boolean.getBoolean( "skipEncodingCheck" ); } @Override public void run() { try { // then process all segments new WayIterator( this, true, !isSlave ).processDir( wayTilesIn, ".wt5" ); } catch( Exception e ) { System.out.println( "******* thread (slave=" + isSlave + ") got Exception: " + e ); throw new RuntimeException( e ); } finally { if (!isSlave) { tc.setCurrentMasterSize( 0L ); } } } @Override public boolean wayFileStart( File wayfile ) throws Exception { // master/slave logic: // total memory size should stay below a maximum // and no file should be processed twice long filesize = wayfile.length(); System.out.println( "**** wayFileStart() for isSlave=" + isSlave + " size=" + filesize ); if ( isSlave ) { if ( !tc.setCurrentSlaveSize( filesize ) ) { return false; } } else { if ( !tc.setCurrentMasterSize( filesize ) ) { return false; } } File trafficFile = fileFromTemplate( wayfile, trafficTilesIn, "trf" ); // 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, true ).processFile( nodeFile ); // freeze the nodes-map FrozenLongMap nodesMapFrozen = new FrozenLongMap( nodesMap ); nodesMap = nodesMapFrozen; File restrictionFile = fileFromTemplate( wayfile, new File( nodeTilesIn.getParentFile(), "restrictions55" ), "rt5" ); // read restrictions for nodes in nodesMap if ( restrictionFile.exists() ) { DataInputStream di = new DataInputStream( new BufferedInputStream ( new FileInputStream( restrictionFile ) ) ); int ntr = 0; try { for(;;) { RestrictionData res = new RestrictionData( di ); OsmNodeP n = nodesMap.get( res.viaNid ); if ( n != null ) { if ( ! ( n instanceof OsmNodePT ) ) { n = new OsmNodePT( n ); nodesMap.put( res.viaNid, n ); } OsmNodePT nt = (OsmNodePT) n; res.viaLon = nt.ilon; res.viaLat = nt.ilat; res.next = nt.firstRestriction; nt.firstRestriction = res; ntr++; } } } catch( EOFException eof ) { di.close(); } System.out.println( "read " + ntr + " turn-restrictions" ); } nodesList = nodesMapFrozen.getValueList(); } // read a traffic-file, if any if ( trafficFile.exists() ) { trafficMap = new OsmTrafficMap( expctxWay ); trafficMap.loadAll( trafficFile, minLon, minLat, minLon + 5000000, minLat + 5000000, false ); } return true; } @Override public void nextNode( NodeData data ) throws Exception { OsmNodeP n = data.description == null ? new OsmNodeP() : new OsmNodePT( data.description ); n.ilon = data.ilon; n.ilat = data.ilat; n.selev = data.selev; if ( readingBorder || ( !borderSet.contains( data.nid ) ) ) { nodesMap.fastPut( data.nid, n ); } if ( readingBorder ) { n.bits |= OsmNodeP.BORDER_BIT; 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 ); } // check if one of the nodes has a turn-restriction with // the current way as from or to member. // It seems to be required, that each member of a turn-restriction // starts or ends at it's via node. However, we allow // ways not ending at the via node, and in this case we take // the leg according to the mapped direction private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w ) { checkRestriction( n1, n2, w, true ); checkRestriction( n2, n1, w, false ); } private void checkRestriction( OsmNodeP n1, OsmNodeP n2, WayData w, boolean checkFrom ) { RestrictionData r = n2.getFirstRestriction(); while ( r != null ) { if ( r.fromWid == w.wid ) { if ( r.fromLon == 0 || checkFrom ) { r.fromLon = n1.ilon; r.fromLat = n1.ilat; n1.bits |= OsmNodeP.DP_SURVIVOR_BIT; if ( !isEndNode( n2, w ) ) { r.badWayMatch = true; } } } if ( r.toWid == w.wid ) { if ( r.toLon == 0 || !checkFrom ) { r.toLon = n1.ilon; r.toLat = n1.ilat; n1.bits |= OsmNodeP.DP_SURVIVOR_BIT; if ( !isEndNode( n2, w ) ) { r.badWayMatch = true; } } } r = r.next; } } private boolean isEndNode( OsmNodeP n, WayData w ) { return n == nodesMap.get( w.nodes.get( 0 ) ) || n == nodesMap.get( w.nodes.get( w.nodes.size() - 1 ) ); } @Override public void nextWay( WayData way ) throws Exception { byte[] description = abUnifier.unify( way.description ); // filter according to profile expctxWay.evaluate( false, description ); boolean ok = expctxWay.getCostfactor() < 10000.; expctxWay.evaluate( true, description ); ok |= expctxWay.getCostfactor() < 10000.; if ( !ok ) return; byte wayBits = 0; expctxWay.decode( description ); if ( !expctxWay.getBooleanLookupValue( "bridge" ) ) wayBits |= OsmNodeP.NO_BRIDGE_BIT; if ( !expctxWay.getBooleanLookupValue( "tunnel" ) ) wayBits |= OsmNodeP.NO_TUNNEL_BIT; OsmNodeP n1 = null; OsmNodeP n2 = null; for ( int i = 0; i < way.nodes.size(); i++ ) { long nid = way.nodes.get( i ); n1 = n2; n2 = nodesMap.get( nid ); if ( n1 != null && n2 != null && n1 != n2 ) { checkRestriction( n1, n2, way ); OsmLinkP link = n2.createLink( n1 ); link.descriptionBitmap = description; if ( n1.ilon / cellsize != n2.ilon / cellsize || n1.ilat / cellsize != n2.ilat / cellsize ) { n2.incWayCount(); // force first node after cell-change to be a // network node } } if ( n2 != null ) { n2.bits |= wayBits; n2.incWayCount(); } } } @Override public void wayFileEnd( File wayfile ) throws Exception { int ncaches = divisor * divisor; int indexsize = ncaches * 4; nodesMap = null; borderSet = null; byte[] abBuf1 = new byte[10 * 1024 * 1024]; byte[] abBuf2 = new byte[10 * 1024 * 1024]; int maxLon = minLon + 5000000; int maxLat = minLat + 5000000; // cleanup duplicate targets for ( OsmNodeP n : nodesList ) { if ( n == null || n.getFirstLink() == null || n.isTransferNode() ) continue; n.checkDuplicateTargets(); } // write segment data to individual files { int nLonSegs = ( maxLon - minLon ) / 1000000; int nLatSegs = ( maxLat - minLat ) / 1000000; // sort the nodes into segments LazyArrayOfLists seglists = new LazyArrayOfLists( nLonSegs * nLatSegs ); for ( OsmNodeP n : nodesList ) { if ( n == null || n.getFirstLink() == 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 ); long[] fileIndex = new long[25]; int[] fileHeaderCrcs = new int[25]; // write 5*5 index dummy for ( int i55 = 0; i55 < 25; i55++ ) { os.writeLong( 0 ); } long filepos = 200L; // sort further in 1/divisor-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( ncaches ); byte[][] subByteArrays = new byte[ncaches][]; for ( int ni = 0; ni < nlist.size(); ni++ ) { OsmNodeP n = nlist.get( ni ); int subLonIdx = ( n.ilon - minLon ) / cellsize - divisor * lonIdx; int subLatIdx = ( n.ilat - minLat ) / cellsize - divisor * latIdx; int si = subLatIdx * divisor + subLonIdx; subs.getList( si ).add( n ); } subs.trimAll(); int[] posIdx = new int[ncaches]; int pos = indexsize; for ( int si = 0; si < ncaches; si++ ) { List subList = subs.getList( si ); int size = subList.size(); if ( size > 0 ) { OsmNodeP n0 = subList.get( 0 ); int lonIdxDiv = n0.ilon / cellsize; int latIdxDiv = n0.ilat / cellsize; MicroCache mc = new MicroCache2( size, abBuf2, lonIdxDiv, latIdxDiv, divisor ); // sort via treemap TreeMap sortedList = new TreeMap(); for ( OsmNodeP n : subList ) { long longId = n.getIdFromPos(); int shrinkid = mc.shrinkId( longId ); if ( mc.expandId( shrinkid ) != longId ) { throw new IllegalArgumentException( "inconstistent shrinking: " + longId ); } sortedList.put( Integer.valueOf( shrinkid ), n ); } for ( OsmNodeP n : sortedList.values() ) { n.writeNodeData( mc, trafficMap ); } if ( mc.getSize() > 0 ) { byte[] subBytes; for ( ;; ) { int len = mc.encodeMicroCache( abBuf1 ); subBytes = new byte[len]; System.arraycopy( abBuf1, 0, subBytes, 0, len ); if ( skipEncodingCheck ) { break; } // cross-check the encoding: re-instantiate the cache MicroCache mc2 = new MicroCache2( new StatCoderContext( subBytes ), new DataBuffers( null ), lonIdxDiv, latIdxDiv, divisor, null, null ); // ..and check if still the same String diffMessage = mc.compareWith( mc2 ); if ( diffMessage != null ) { if ( MicroCache.debug ) throw new RuntimeException( "encoding crosscheck failed: " + diffMessage ); else MicroCache.debug = true; } else break; } pos += subBytes.length + 4; // reserve 4 bytes for crc subByteArrays[si] = subBytes; } } posIdx[si] = pos; } byte[] abSubIndex = compileSubFileIndex( posIdx ); fileHeaderCrcs[tileIndex] = Crc32.crc( abSubIndex, 0, abSubIndex.length ); os.write( abSubIndex, 0, abSubIndex.length ); for ( int si = 0; si < ncaches; si++ ) { byte[] ab = subByteArrays[si]; if ( ab != null ) { os.write( ab ); os.writeInt( Crc32.crc( ab, 0, ab.length ) ^ microCacheEncoding ); } } filepos += pos; } fileIndex[tileIndex] = filepos; } } byte[] abFileIndex = compileFileIndex( fileIndex, lookupVersion, lookupMinorVersion ); // write extra data: timestamp + index-checksums os.writeLong( creationTimeStamp ); os.writeInt( Crc32.crc( abFileIndex, 0, abFileIndex.length ) ^ microCacheEncoding ); for ( int i55 = 0; i55 < 25; i55++ ) { os.writeInt( fileHeaderCrcs[i55] ); } os.close(); // re-open random-access to write file-index RandomAccessFile ra = new RandomAccessFile( outfile, "rw" ); ra.write( abFileIndex, 0, abFileIndex.length ); ra.close(); } if ( trafficMap != null ) { trafficMap.finish(); trafficMap = null; } System.out.println( "**** codec stats: *******\n" + StatCoderContext.getBitReport() ); } private byte[] compileFileIndex( long[] fileIndex, short lookupVersion, short lookupMinorVersion ) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream( bos ); for ( int i55 = 0; i55 < 25; i55++ ) { long versionPrefix = i55 == 1 ? lookupMinorVersion : lookupVersion; versionPrefix <<= 48; dos.writeLong( fileIndex[i55] | versionPrefix ); } dos.close(); return bos.toByteArray(); } private byte[] compileSubFileIndex( int[] posIdx ) throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream( bos ); for ( int si = 0; si < posIdx.length; si++ ) { dos.writeInt( posIdx[si] ); } dos.close(); return bos.toByteArray(); } }