2016-03-04 15:11:05 -05:00
var fs = require ( 'fs' ) ;
var path = require ( 'path' ) ;
var util = require ( 'util' ) ;
var exec = require ( 'child_process' ) . exec ;
var d3 = require ( 'd3-queue' ) ;
var OSM = require ( './build_osm' ) ;
var classes = require ( './data_classes' ) ;
module . exports = function ( ) {
this . setGridSize = ( meters ) => {
// the constant is calculated (with BigDecimal as: 1.0/(DEG_TO_RAD*EARTH_RADIUS_IN_METERS
// see ApproximateDistance() in ExtractorStructs.h
// it's only accurate when measuring along the equator, or going exactly north-south
this . zoom = parseFloat ( meters ) * 0.8990679362704610899694577444566908445396483347536032203503 E - 5 ;
} ;
this . setOrigin = ( origin ) => {
this . origin = origin ;
} ;
this . buildWaysFromTable = ( table , callback ) => {
// add one unconnected way for each row
var buildRow = ( row , ri , cb ) => {
// comments ported directly from ruby suite:
// NOTE: currently osrm crashes when processing an isolated oneway with just 2 nodes, so we use 4 edges
// this is related to the fact that a oneway dead-end street doesn't make a lot of sense
// if we stack ways on different x coordinates, routability tests get messed up, because osrm might pick a neighboring way if the one test can't be used.
// instead we place all lines as a string on the same y coordinate. this prevents using neighboring ways.
// add some nodes
var makeFakeNode = ( namePrefix , offset ) => {
return new OSM . Node ( this . makeOSMId ( ) , this . OSM _USER , this . OSM _TIMESTAMP ,
this . OSM _UID , this . origin [ 0 ] + ( offset + this . WAY _SPACING * ri ) * this . zoom ,
this . origin [ 1 ] , { name : util . format ( '%s%d' , namePrefix , ri ) } ) ;
} ;
var nodes = [ 'a' , 'b' , 'c' , 'd' , 'e' ] . map ( ( l , i ) => makeFakeNode ( l , i ) ) ;
nodes . forEach ( node => { this . OSMDB . addNode ( node ) ; } ) ;
// ...with a way between them
var way = new OSM . Way ( this . makeOSMId ( ) , this . OSM _USER , this . OSM _TIMESTAMP , this . OSM _UID ) ;
nodes . forEach ( node => { way . addNode ( node ) ; } ) ;
// remove tags that describe expected test result, reject empty tags
var tags = { } ;
for ( var rkey in row ) {
if ( ! rkey . match ( /^forw\b/ ) &&
! rkey . match ( /^backw\b/ ) &&
! rkey . match ( /^bothw\b/ ) &&
row [ rkey ] . length )
tags [ rkey ] = row [ rkey ] ;
}
var wayTags = { highway : 'primary' } ,
nodeTags = { } ;
for ( var key in tags ) {
var nodeMatch = key . match ( /node\/(.*)/ ) ;
if ( nodeMatch ) {
if ( tags [ key ] === '(nil)' ) {
delete nodeTags [ key ] ;
} else {
nodeTags [ nodeMatch [ 1 ] ] = tags [ key ] ;
}
} else {
if ( tags [ key ] === '(nil)' ) {
delete wayTags [ key ] ;
} else {
wayTags [ key ] = tags [ key ] ;
}
}
}
wayTags . name = util . format ( 'w%d' , ri ) ;
way . setTags ( wayTags ) ;
this . OSMDB . addWay ( way ) ;
for ( var k in nodeTags ) {
nodes [ 2 ] . addTag ( k , nodeTags [ k ] ) ;
}
cb ( ) ;
} ;
var q = d3 . queue ( ) ;
table . hashes ( ) . forEach ( ( row , ri ) => {
q . defer ( buildRow , row , ri ) ;
} ) ;
q . awaitAll ( callback ) ;
} ;
2016-03-27 23:55:12 -04:00
this . ensureDecimal = ( i ) => {
2016-03-04 15:11:05 -05:00
if ( parseInt ( i ) === i ) return i . toFixed ( 1 ) ;
else return i ;
} ;
this . tableCoordToLonLat = ( ci , ri ) => {
2016-03-27 23:55:12 -04:00
return [ this . origin [ 0 ] + ci * this . zoom , this . origin [ 1 ] - ri * this . zoom ] . map ( this . ensureDecimal ) ;
2016-03-04 15:11:05 -05:00
} ;
this . addOSMNode = ( name , lon , lat , id ) => {
id = id || this . makeOSMId ( ) ;
var node = new OSM . Node ( id , this . OSM _USER , this . OSM _TIMESTAMP , this . OSM _UID , lon , lat , { name : name } ) ;
this . OSMDB . addNode ( node ) ;
this . nameNodeHash [ name ] = node ;
} ;
this . addLocation = ( name , lon , lat ) => {
this . locationHash [ name ] = new classes . Location ( lon , lat ) ;
} ;
this . findNodeByName = ( s ) => {
if ( s . length !== 1 ) throw new Error ( util . format ( '*** invalid node name "%s", must be single characters' , s ) ) ;
if ( ! s . match ( /[a-z0-9]/ ) ) throw new Error ( util . format ( '*** invalid node name "%s", must be alphanumeric' , s ) ) ;
var fromNode ;
if ( s . match ( /[a-z]/ ) ) {
fromNode = this . nameNodeHash [ s . toString ( ) ] ;
} else {
fromNode = this . locationHash [ s . toString ( ) ] ;
}
return fromNode ;
} ;
this . findWayByName = ( s ) => {
return this . nameWayHash [ s . toString ( ) ] || this . nameWayHash [ s . toString ( ) . split ( '' ) . reverse ( ) . join ( '' ) ] ;
} ;
this . resetData = ( ) => {
this . resetOSM ( ) ;
} ;
this . makeOSMId = ( ) => {
this . osmID = this . osmID + 1 ;
return this . osmID ;
} ;
this . resetOSM = ( ) => {
this . OSMDB . clear ( ) ;
this . osmData . reset ( ) ;
this . nameNodeHash = { } ;
this . locationHash = { } ;
this . nameWayHash = { } ;
this . osmID = 0 ;
} ;
this . writeOSM = ( callback ) => {
fs . exists ( this . DATA _FOLDER , ( exists ) => {
var mkDirFn = exists ? ( cb ) => { cb ( ) ; } : fs . mkdir . bind ( fs . mkdir , this . DATA _FOLDER ) ;
mkDirFn ( ( err ) => {
if ( err ) return callback ( err ) ;
var osmPath = path . resolve ( this . DATA _FOLDER , util . format ( '%s.osm' , this . osmData . osmFile ) ) ;
fs . exists ( osmPath , ( exists ) => {
if ( ! exists ) fs . writeFile ( osmPath , this . osmData . str , callback ) ;
else callback ( ) ;
} ) ;
} ) ;
} ) ;
} ;
this . isExtracted = ( callback ) => {
fs . exists ( util . format ( '%s.osrm' , this . osmData . extractedFile ) , ( core ) => {
if ( ! core ) return callback ( false ) ;
fs . exists ( util . format ( '%s.osrm.names' , this . osmData . extractedFile ) , ( names ) => {
if ( ! names ) return callback ( false ) ;
fs . exists ( util . format ( '%s.osrm.restrictions' , this . osmData . extractedFile ) , ( restrictions ) => {
return callback ( restrictions ) ;
} ) ;
} ) ;
} ) ;
} ;
this . isContracted = ( callback ) => {
fs . exists ( util . format ( '%s.osrm.hsgr' , this . osmData . contractedFile ) , callback ) ;
} ;
this . writeTimestamp = ( callback ) => {
fs . writeFile ( util . format ( '%s.osrm.timestamp' , this . osmData . contractedFile ) , this . OSM _TIMESTAMP , callback ) ;
} ;
this . writeInputData = ( callback ) => {
this . writeOSM ( ( err ) => {
if ( err ) return callback ( err ) ;
this . writeTimestamp ( callback ) ;
} ) ;
} ;
this . extractData = ( callback ) => {
this . logPreprocessInfo ( ) ;
this . log ( util . format ( '== Extracting %s.osm...' , this . osmData . osmFile ) , 'preprocess' ) ;
var cmd = util . format ( '%s%s/osrm-extract %s.osm %s --profile %s/%s.lua >>%s 2>&1' ,
this . LOAD _LIBRARIES , this . BIN _PATH , this . osmData . osmFile , this . extractArgs || '' , this . PROFILES _PATH , this . profile , this . PREPROCESS _LOG _FILE ) ;
this . log ( cmd ) ;
process . chdir ( this . TEST _FOLDER ) ;
exec ( cmd , ( err ) => {
if ( err ) {
this . log ( util . format ( '*** Exited with code %d' , err . code ) , 'preprocess' ) ;
2016-03-30 20:53:36 -04:00
process . chdir ( '../' ) ;
2016-03-04 15:11:05 -05:00
return callback ( this . ExtractError ( err . code , util . format ( 'osrm-extract exited with code %d' , err . code ) ) ) ;
}
var q = d3 . queue ( ) ;
var rename = ( file , cb ) => {
this . log ( util . format ( 'Renaming %s.%s to %s.%s' , this . osmData . osmFile , file , this . osmData . extractedFile , file ) , 'preprocess' ) ;
fs . rename ( [ this . osmData . osmFile , file ] . join ( '.' ) , [ this . osmData . extractedFile , file ] . join ( '.' ) , ( err ) => {
if ( err ) return cb ( this . FileError ( null , 'failed to rename data file after extracting' ) ) ;
cb ( ) ;
} ) ;
} ;
var renameIfExists = ( file , cb ) => {
fs . stat ( [ this . osmData . osmFile , file ] . join ( '.' ) , ( doesNotExistErr , exists ) => {
if ( exists ) rename ( file , cb ) ;
else cb ( ) ;
} ) ;
} ;
2016-03-25 13:50:46 -04:00
[ 'osrm' , 'osrm.names' , 'osrm.restrictions' , 'osrm.ebg' , 'osrm.enw' , 'osrm.edges' , 'osrm.fileIndex' , 'osrm.geometry' , 'osrm.nodes' , 'osrm.ramIndex' , 'osrm.properties' ] . forEach ( file => {
2016-03-04 15:11:05 -05:00
q . defer ( rename , file ) ;
} ) ;
[ 'osrm.edge_segment_lookup' , 'osrm.edge_penalties' ] . forEach ( file => {
q . defer ( renameIfExists , file ) ;
} ) ;
q . awaitAll ( ( err ) => {
this . log ( 'Finished extracting ' + this . osmData . extractedFile , 'preprocess' ) ;
process . chdir ( '../' ) ;
callback ( err ) ;
} ) ;
} ) ;
} ;
this . contractData = ( callback ) => {
this . logPreprocessInfo ( ) ;
this . log ( util . format ( '== Contracting %s.osm...' , this . osmData . extractedFile ) , 'preprocess' ) ;
var cmd = util . format ( '%s%s/osrm-contract %s %s.osrm >>%s 2>&1' ,
this . LOAD _LIBRARIES , this . BIN _PATH , this . contractArgs || '' , this . osmData . extractedFile , this . PREPROCESS _LOG _FILE ) ;
this . log ( cmd ) ;
process . chdir ( this . TEST _FOLDER ) ;
exec ( cmd , ( err ) => {
if ( err ) {
this . log ( util . format ( '*** Exited with code %d' , err . code ) , 'preprocess' ) ;
2016-03-30 20:53:36 -04:00
process . chdir ( '../' ) ;
2016-03-04 15:11:05 -05:00
return callback ( this . ContractError ( err . code , util . format ( 'osrm-contract exited with code %d' , err . code ) ) ) ;
}
var rename = ( file , cb ) => {
this . log ( util . format ( 'Renaming %s.%s to %s.%s' , this . osmData . extractedFile , file , this . osmData . contractedFile , file ) , 'preprocess' ) ;
fs . rename ( [ this . osmData . extractedFile , file ] . join ( '.' ) , [ this . osmData . contractedFile , file ] . join ( '.' ) , ( err ) => {
if ( err ) return cb ( this . FileError ( null , 'failed to rename data file after contracting.' ) ) ;
cb ( ) ;
} ) ;
} ;
var copy = ( file , cb ) => {
this . log ( util . format ( 'Copying %s.%s to %s.%s' , this . osmData . extractedFile , file , this . osmData . contractedFile , file ) , 'preprocess' ) ;
fs . createReadStream ( [ this . osmData . extractedFile , file ] . join ( '.' ) )
. pipe ( fs . createWriteStream ( [ this . osmData . contractedFile , file ] . join ( '.' ) )
. on ( 'finish' , cb )
)
. on ( 'error' , ( ) => {
return cb ( this . FileError ( null , 'failed to copy data after contracting.' ) ) ;
} ) ;
} ;
var q = d3 . queue ( ) ;
2016-03-25 13:50:46 -04:00
[ 'osrm.hsgr' , 'osrm.fileIndex' , 'osrm.geometry' , 'osrm.nodes' , 'osrm.ramIndex' , 'osrm.core' , 'osrm.edges' , 'osrm.datasource_indexes' , 'osrm.datasource_names' , 'osrm.level' ] . forEach ( ( file ) => {
2016-03-04 15:11:05 -05:00
q . defer ( rename , file ) ;
} ) ;
2016-03-25 13:50:46 -04:00
[ 'osrm.names' , 'osrm.restrictions' , 'osrm.properties' , 'osrm' ] . forEach ( ( file ) => {
2016-03-04 15:11:05 -05:00
q . defer ( copy , file ) ;
} ) ;
q . awaitAll ( ( err ) => {
this . log ( 'Finished contracting ' + this . osmData . contractedFile , 'preprocess' ) ;
process . chdir ( '../' ) ;
callback ( err ) ;
} ) ;
} ) ;
} ;
var noop = ( cb ) => cb ( ) ;
this . reprocess = ( callback ) => {
this . writeAndExtract ( ( e ) => {
if ( e ) return callback ( e ) ;
this . isContracted ( ( isContracted ) => {
2016-04-29 03:48:13 -04:00
var contractFn = ( isContracted && ! this . forceContract ) ? noop : this . contractData ;
2016-03-04 15:11:05 -05:00
if ( isContracted ) this . log ( 'Already contracted ' + this . osmData . contractedFile , 'preprocess' ) ;
contractFn ( ( e ) => {
2016-04-29 03:48:13 -04:00
this . forceContract = false ;
2016-03-04 15:11:05 -05:00
if ( e ) return callback ( e ) ;
this . logPreprocessDone ( ) ;
callback ( ) ;
} ) ;
} ) ;
} ) ;
} ;
this . writeAndExtract = ( callback ) => {
this . osmData . populate ( ( ) => {
this . writeInputData ( ( e ) => {
if ( e ) return callback ( e ) ;
this . isExtracted ( ( isExtracted ) => {
2016-04-29 03:48:13 -04:00
var extractFn = ( isExtracted && ! this . forceExtract ) ? noop : this . extractData ;
2016-03-04 15:11:05 -05:00
if ( isExtracted ) this . log ( 'Already extracted ' + this . osmData . extractedFile , 'preprocess' ) ;
extractFn ( ( e ) => {
2016-04-29 03:48:13 -04:00
this . forceExtract = false ;
2016-03-04 15:11:05 -05:00
callback ( e ) ;
} ) ;
} ) ;
} ) ;
} ) ;
} ;
this . reprocessAndLoadData = ( callback ) => {
this . reprocess ( ( ) => {
this . OSRMLoader . load ( util . format ( '%s.osrm' , this . osmData . contractedFile ) , callback ) ;
} ) ;
} ;
this . processRowsAndDiff = ( table , fn , callback ) => {
var q = d3 . queue ( 1 ) ;
2016-03-28 18:39:57 -04:00
table . hashes ( ) . forEach ( ( row , i ) => { q . defer ( fn , row , i ) ; } ) ;
2016-03-04 15:11:05 -05:00
q . awaitAll ( ( err , actual ) => {
if ( err ) return callback ( err ) ;
this . diffTables ( table , actual , { } , callback ) ;
} ) ;
} ;
} ;