/*
* Copyright (C) 2007,2014 Steve Ratcliffe
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
*
* Author: Steve Ratcliffe
* Create date: Dec 16, 2007
*/
package test.display;
import java.util.BitSet;
import java.util.HashMap;
import java.util.Map;
import uk.me.parabola.imgfmt.Utils;
import static test.util.AccessBits.*;
import static test.util.ArcFlags.*;
import static test.util.NodeFlags.*;
import static test.util.RestrictionBits.*;
/**
* Standalone program to display the NOD file as it is worked out. Will start
* out with what is in imgdecode by John Mechalas.
*
* Credits:
* Alexander Atanasov and his libgarmin (http://libgarmin.sourceforge.net/) project.
* Robert Vollmert
*/
public class NodDisplay
extends CommonDisplay
{
private int nodesStart
;
private int nodesLen
;
private int roadDataStart
;
private int roadDataLen
;
private int boundriesStart
;
private int boundriesLen
;
private int boundriesRecsize
;
private final Map<Integer, Offset
> nod1recs =
new HashMap<>();
private NetDisplay net
;
private byte align
;
private int aMask
;
private int tableARecordLen
;
// Initialise this here if the automatic detection does not work.
private Boolean hasExtraZeros
;
private byte mult
;
private Section nod4
;
private Section nod5
;
private Section nod6
;
private int indexIdSize
;
private int distanceMult
;
protected void print
() {
readCommonHeader
();
printHeader
();
printRoadData
();
printNodes
();
printBoundryNodes
();
printHighClassBoundary
();
}
private long calcTablePosition
(long start,
int low
) {
long pos = start - nodesStart
;
pos += aMask +
1;
pos += low
* ((long) 1 << align
);
pos
&= ~aMask
;
return nodesStart + pos
;
}
private void printNodes
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("NOD 1 (nodes)");
d.
setSectStart(nodesStart
);
reader.
position(nodesStart
);
int center =
0;
while (reader.
position() < nodesStart + nodesLen
) {
while (mult
!=
0 && ((reader.
position() - nodesStart
) & ((1 << mult
) -
1)) !=
0)
d.
byteValue("pad");
long start = reader.
position();
int low =
(d.
byteValue("low byte %x, pointer to")) & 0xff
;
long end = calcTablePosition
(start, low
);
d.
item().
addText("center %d, tables at %x", center++, end
);
// determine size of Table C offsets
reader.
position(end
);
// read Table C format
int fmt = reader.
get();
int tabCOffsetSize =
0;
if((fmt
& 2) !=
0) {
// Table C size is 16 bits, so offset must be too
tabCOffsetSize =
2;
}
else if((fmt
& 1) !=
0) {
// this is tricky because the size could be more than
// 128 in which case the offset needs to be 16 bits so
// we need to actually read the size to find out
reader.
position(end +
7);
int tabASize = reader.
get1u();
int tabBSize = reader.
get1u();
reader.
position(end +
9 + tabASize
* 5 + tabBSize
* 3);
if((reader.
get1u()) >= 0x80
)
tabCOffsetSize =
2;
else
tabCOffsetSize =
1;
}
reader.
position(start
); // step back to low byte
if(tabCOffsetSize ==
1)
d.
item().
addText("Table C offsets are 8 bits");
else if(tabCOffsetSize ==
2)
d.
item().
addText("Table C offsets are 16 bits");
TableHeader tableHeader = readTableHeader
((int) end
);
printNodesForCenter
(d, tabCOffsetSize, tableHeader
);
reader.
position(end
);
d.
print(outStream
);
printTables
(d, tableHeader
);
}
d.
print(outStream
);
}
private void printNodesForCenter
(Displayer d,
int tabCOffsetSize, TableHeader tableHeader
) {
long groupStart = reader.
position();
int end =
(int) tableHeader.
getPosition();
while (reader.
position() < end
) {
d.
gap();
d.
item().
addText("New node");
long nodeOff = reader.
position();
DisplayItem item = d.
byteItem();
int low = item.
getValue();
item.
addText("low %x", low
& 0xff
);
if (calcTablePosition
(nodeOff, low
) != end
&& low
> 0) {
d.
item().
addText("error lost sync calc-end=%x, should be=%x",
calcTablePosition
(nodeOff, low
), end
);
low = d.
byteValue("retry low %x") & 0xff
;
if (calcTablePosition
(nodeOff +
1, low
) != end
) {
d.
rawValue((int) (end - reader.
position()),
"remaining bytes");
break;
}
nodeOff +=
1;
}
// byte2 looks like flags, only a small number of values
int flags = d.
byteValue("Flags %x");
if (low ==
0 && flags ==
0 ||
(reader.
position() >=
(end -
3))) {
item.
addText("End of nodes");
d.
rawValue((int) (end - reader.
position()),
"perhaps more nodes");
continue;
}
boolean arcs =
(flags
& NODE_ARCS
) == 0x40
; // has arcs
boolean bigoff =
(flags
& NODE_LARGE_OFFSET
) == 0x20
; // 2 bytes each for lat/lon offsets
boolean restr =
(flags
& NODE_RESTRICTIONS
) == 0x10
; // restrictions present at this node
boolean boundary =
(flags
& NODE_BOUNDARY
) == 0x08
; // this is a boundary node
int destclass = flags
& NODE_DESTCLASS_MASK
; // Highest road class this node leads to
boolean unkFlags =
(flags
& NODE_UNKNOWN_MASK
) !=
0; // has unknown flags
StringBuilder sb =
new StringBuilder(": ");
if (restr
) sb.
append("restrictions ");
if (bigoff
) sb.
append("large-offsets ");
if (boundary
) sb.
append("boundary-node ");
if (arcs
) sb.
append("arcs ");
if (!arcs
) sb.
append("no-arcs ");
sb.
append("destclass=");
sb.
append(destclass
);
sb.
append(" ");
if (unkFlags
) sb.
append("UNKNOWN ");
d.
item().
addText(sb.
toString());
// 1. even spread
// 2. biased toward 00 f0 0f ff etc
// 3. even
// 4. uneven, peaks at 0x20, 40, 60
// 5. uneven again
// 6. fairly even but big peak at 3e,3f
// 7. fairly even
// 8. number decreases with value
positionOffsets
(d, tableHeader, bigoff
);
if(arcs
)
pointerPart
(d, tableHeader, nodeOff, groupStart, end
);
if(restr
) {
boolean done =
false;
while(!done
) {
if(tabCOffsetSize ==
1) {
item = d.
byteItem();
int off = item.
getValue();
item.
addText("Restriction offset 0x%x", off
& 0x7f
);
done =
(off
& 0x80
) == 0x80
;
}
else if(tabCOffsetSize ==
2) {
item = d.
charItem();
int off = item.
getValue();
item.
addText("Restriction offset 0x%x", off
& 0x7fff
);
done =
(off
& 0x8000
) == 0x8000
;
}
else {
done =
true;
}
}
}
while (mult
!=
0 && ((reader.
position() - nodesStart
) & ((1 << mult
) -
1)) !=
0)
d.
byteValue("pad");
}
}
private TableHeader readTableHeader
(int offset
) {
return new TableHeader
(reader, offset, tableARecordLen
);
}
private void positionOffsets
(Displayer d, TableHeader currentTableHeader,
boolean bigoff
) {
DisplayItem item
;
if (bigoff
) {
item = d.
item();
int longoff = reader.
get2s();
item.
setBytes2(longoff
);
item.
addText("longitude %.5f %+d", Utils.
toDegrees(longoff + currentTableHeader.
getLon()), longoff
);
item = d.
item();
int latoff = reader.
get2s();
item.
setBytes2(latoff
);
item.
addText("latitude %.5f %+d", Utils.
toDegrees(latoff + currentTableHeader.
getLat()), latoff
);
} else {
item = d.
item();
int latlon = reader.
get3u();
int latoff =
(latlon
<< 8) >> 20; // sign extend middle bits
int longoff =
(latlon
<< 20) >> 20; // sign extend bottom 12 bits
item.
setBytes3(latlon
);
item.
addText("longitude %.5f %+d", Utils.
toDegrees(longoff + currentTableHeader.
getLon()), longoff
);
item.
addText("latitude %.5f %+d", Utils.
toDegrees(latoff + currentTableHeader.
getLat()), latoff
);
}
}
/**
* Convert from the internal representation to meters.
* @param raw The internal length value. Represents 16 feet.
* @return Length of the arc in meters.
*/
private static final float UNIT_TO_METER = 2.4f
;
public int rawToMeters
(int raw
) {
return Math.
round((raw + 0.5f
) * (distanceMult
* UNIT_TO_METER
));
}
private void pointerPart
(Displayer d, TableHeader tableHeader,
long offset,
long min,
long max
) {
// looks like there are 2b before the low1 pointer and 4 after.
boolean end =
false;
boolean first =
true;
boolean lastSign =
false;
int lastRoad =
0;
do {
// Start with alt6 byte
// bit 0x20 seems to determine whether there's an extra byte at the end
int alt6 = d.
byteValue("alt6 byte %02x");
// this is not the class of the segment, but the max of classes of the dest node
int classmask = 0x07
;
boolean newRoad =
(alt6
& ARC_HASNET
) == 0x80
;
int destclass = alt6
& classmask
;
boolean sign =
(alt6
& ARC_SIGN
) !=
0;
boolean curve =
(alt6
& ARC_CURVE
) !=
0;
d.
item().
addText("net=%d sign=%d curve=%x len=%02x destclass=%d",
newRoad
?1:
0, sign
?1:
0, curve
?1:
0, alt6
& ARC_LEN, destclass
);
// Continue with two byte values. The first one has the top
// bit set if this is the last pointer in the node record.
DisplayItem item = d.
item();
int intro1 = item.
setBytes1(reader.
get1u());
// Note that this is the last if it is.
if ((intro1
& 0x80
) == 0x80
) {
end =
true;
item.
addText("last one");
}
// The second highest bit, means inter-section pointer
boolean longlink =
(intro1
& 0x40
) == 0x40
;
if (longlink
) {
int idx = intro1
& 0x3f
;
if (idx == 0x3f
) {
idx = item.
setBytes1(reader.
get1u());
}
int linkoff = tableHeader.
getLink(idx
);
if (linkoff
>=
0)
item.
addText("inter-section link, index %d -> %x", idx, linkoff
<<mult
);
else
item.
addText("error: inter-section link, index %d -> BAD INDEX", idx
);
} else {
// in-section relative node pointer
int intro2 = item.
setBytes1(reader.
get1u());
short nodeoff =
(short) ((intro1
<< 8 | intro2
) & 0x3fff
);
// Construct a pointer to another node, signed 16 bit quantity
if ((nodeoff
& 0x2000
) !=
0)
nodeoff |= 0xc000
;
nodeoff
<<= mult
;
item.
addText("pointer to another node %x", nodeoff
);
long otherNode = offset + nodeoff - nodesStart
;
item.
addText(" -> node %x", otherNode
);
checkValidNode
(item, max, otherNode
);
if (min - nodesStart
> otherNode || otherNode
>= max - nodesStart
) {
item.
addText("error INVALID node: pointer out of range!");
}
}
if (first || newRoad
) {
item = d.
byteItem();
int ri = item.
getValue();
int road = tableHeader.
getRoad(ri
);
lastRoad = road
;
item.
addText("pointer to local net index %d -> road %x", ri, road
);
} else {
d.
item().
addText(" (same road %x)", lastRoad
);
}
int len
;
if ((alt6
& ARC_EXTRA
) == ARC_EXTRA
) {
item = d.
byteItem();
int len1 = item.
getValue() & 0xff
;
if ((len1
& 0x80
) == 0x80
) {
if ((len1
& 0x40
) == 0x40
) {
item.
addText("22 bit length + curve");
int len2 = item.
setBytes2(reader.
get2u());
len =
(len1
& 0x3f
) |
(len2
<< 6); // 6+16 bits
curve =
true;
} else {
int len2 = item.
setBytes1(reader.
get1u());
item.
addText("14 bit length %02x %02x", len1
& 0x3f, len2
);
len =
(len1
& 0x3f
) |
(len2
<< 6); // 6+8 bits
curve =
false;
}
} else {
item.
addText("15 bit length + curve");
int len2 = item.
setBytes1(reader.
get1u());
len =
(len1
& 0x7f
) |
(len2
<< 7); // 7+8 bits
curve =
true;
}
}
else {
curve =
(alt6
& ARC_CURVE
) == ARC_CURVE
;
if(curve
)
d.
item().
addText("10 bit length + curve");
else
d.
item().
addText("10 bit length");
len =
(alt6
& ARC_LEN
) << 5;
item = d.
item();
len |= item.
setBytes1(reader.
get1u()); // 2+8 bits
}
item.
addText("length %d (%dm)", len, rawToMeters
(len
));
if (first
) {
haveDir =
false;
largeDir =
!newRoad
;
}
if (first || newRoad ||
(sign
!= lastSign
)) {
fetchDirection
(d
);
} else {
item.
addText("reused direction info");
}
if (curve
) {
item = d.
item();
int curvea = item.
setBytes1(reader.
get1u());
if ((curvea
& 0xe0
) ==
0) {
int curveb = item.
setBytes1(reader.
get1u());
item.
addText("curve[0] %02x curve[1] %02x (two bytes)", curvea, curveb
);
} else {
int angle =
360 * (byte)(((curvea
& 0x1f
) << 3) |
((curvea
& 0xe0
) >> 5)) /
256;
item.
addText("curve[0] %02x (%d deg?)", curvea, angle
);
}
}
d.
line();
first =
false;
lastSign = sign
;
} while (!end
);
}
private byte savedDir
;
private boolean haveDir
;
private boolean largeDir
;
/**
* Fetch the direction. See the {@link test.files.DirectionReader} class for more details.
* It could be used here but we want a byte by byte breakdown printed out.
*/
private int fetchDirection
(Displayer d
) {
if (largeDir
) {
DisplayItem item = d.
byteItem();
int dir = item.
getValue();
item.
addText("large direction %d (%.0fdeg)", dir,
(dir
* 360.0) /
256);
return dir
;
}
if (haveDir
) {
haveDir =
false;
int dir =
(savedDir
& 0xf0
);
d.
item().
addText("saved direction %x (%.0fdeg)", dir,
(dir
* 360.0) /
256);
return dir
;
} else {
DisplayItem item = d.
byteItem();
savedDir =
(byte) item.
getValue();
haveDir =
true;
int dir = savedDir
& 0xf
;
dir
<<=
4;
item.
addText("read direction %x (%.0fdeg)", dir,
(dir
* 360.0) /
256);
return dir
;
}
}
private void checkValidNode
(DisplayItem item,
long tablePosition,
long nodeStart
) {
// Work out if this is actually a node to help in debugging. False positives
// are possible, but not false negatives.
if (nodeStart
< 0) {
item.
addText("error INVALID node, start=%x", nodeStart
);
return;
}
long savpos = reader.
position();
reader.
position(nodeStart + nodesStart
);
int low = reader.
get1u();
long tabpos = calcTablePosition
(nodeStart + nodesStart, low
);
if (tabpos
!= tablePosition
) {
item.
addText("error INVALID node, low=%x, tabpos=%x, max=%x", low, tabpos, tablePosition
);
}
// restore position
reader.
position(savpos
);
}
private void printTables
(Displayer d, TableHeader tableHeader
) {
d.
setTitle("Tables");
int restrformat = d.
byteValue("Table C format");
int l = latLongField
(d,
"longitude");
tableHeader.
setLong(l
);
l = latLongField
(d,
"latitude");
tableHeader.
setLat(l
);
int n = d.
byteValue("%d records in Table A") & 0xff
;
int m = d.
byteValue("%d records in Table B") & 0xff
;
d.
print(outStream
);
// Now do 'Table A' (segments)
d.
setTitle("Table A (segments)");
for (int i =
0; i
< n
; i++
) {
d.
item().
addText("net pointer index %d %02x", i, i
);
DisplayItem item = d.
item();
int off = item.
setBytes3(reader.
get3u());
// top 2 bits of net pointer are access bits
int access =
(off
& 0xc00000
) >> 8;
off
&= 0x3fffff
;
item.
addText("pointer to net %06x (%s)", off, getNameFromNetOff
(off
));
item = d.
item();
int paramA = item.
setBytes1(reader.
get1u());
int paramB =
0;
if (tableARecordLen
>=
5)
paramB = item.
setBytes1(reader.
get1u());
String par =
"class %d, speed %d";
if ((paramA
& TOLL
) == TOLL
)
par +=
", toll";
if ((paramA
& ONEWAY
) == ONEWAY
)
par +=
", oneway";
if ((paramB
& NO_THROUGH_ROUTING
) == NO_THROUGH_ROUTING
)
par +=
", no through routing";
item.
addText(par,
(paramA
& CLASS
) >> 4, paramA
& SPEED
);
access |= paramB
& 0xff
;
par =
"";
par +=
(access
& NOEMERGENCY
) ==
0 ? "emergency, " :
"no emergency, ";
par +=
(access
& NODELIVERY
) ==
0 ? "delivery, " :
"no delivery, ";
par +=
(access
& NOCAR
) ==
0 ? "car, " :
"no car, ";
par +=
(access
& NOBUS
) ==
0 ? "bus, " :
"no bus, ";
par +=
(access
& NOTAXI
) ==
0 ? "taxi, " :
"no taxi, ";
par +=
(access
& NOCARPOOL
) ==
0 ? "carpool, " :
"no carpool, ";
par +=
(access
& NOFOOT
) ==
0 ? "foot, " :
"no foot, ";
par +=
(access
& NOBIKE
) ==
0 ? "bike, " :
"no bike, ";
par +=
(access
& NOTRUCK
) ==
0 ? "truck, " :
"no truck, ";
item.
addText("access: %s", par
);
}
d.
print(outStream
);
// 'Table B' (inter-section pointers)
d.
setTitle("Table B (inter-section pointers)");
for (int i =
0; i
< m
; i++
) {
d.
item().
addText("node pointer index %d 0x%02x", i, i
);
DisplayItem item = d.
int3Item();
item.
addText("offset into Nod1 %x", item.
getValue() << mult
);
}
d.
print(outStream
);
printRestrictions
(tableHeader, restrformat
);
d.
setTitle("");
if ((restrformat
& 0x4
) !=
0) {
d.
byteValue("%d roundabouts");
}
if ((restrformat
& 0x8
) !=
0) {
d.
byteValue("%d unpaved roads");
}
if((restrformat
& 0x10
) !=
0) {
d.
byteValue("%d ferry routes");
}
if((restrformat
& 0x03
) ==
0) {
// Sometimes there is an extra zero here. Eg mkgmap currently adds one.
// If there is no zero then we know that that isn't the case for this file.
// If it is however, the it could be the start of a node with a very short section.
// So we can't really tell, but if we found a node at this position from NOD 2,
// then we know that there definitely is not.
long pos = reader.
position();
byte b = reader.
get();
reader.
position(pos
);
// Is there a node at pos? If so then there are not extra bytes.
Offset offset = nod1recs.
get((int) pos - nodesStart
);
// Since the file either has this or not, then remember this the first time. It will
// then always be right or always wrong. Initialise hasExtraZeros at the top if
// looking at a file where this heuristic does not work.
if (hasExtraZeros ==
null) {
if (b ==
0 && offset ==
null) {
hasExtraZeros =
true;
} else {
hasExtraZeros =
false;
}
}
if (hasExtraZeros
) {
d.
byteValue("Skip zero value %d");
}
}
d.
print(outStream
);
}
private void printRestrictions
(TableHeader tableHeader,
int restrformat
) {
// 'Table C' (restrictions)
Displayer d =
new Displayer
(reader
);
d.
setTitle("Table C (restrictions)");
d.
setSectStart(nodesStart
);
int size=
0;
if ((restrformat
& 1) !=
0) {
size = d.
byteValue("table c size %d") & 0xff
;
} else if ((restrformat
& 2) !=
0) {
size = d.
charValue("table c size %d") & 0xffff
;
}
int cSize = size
> 0x7f
? 2:
1;
int end =
(int) (reader.
position() + size -
(restrformat
& 0x3
));
d.
setSectStart(reader.
position());
while (reader.
position() < end
){
d.
gap();
// read three byte header
int restrType = d.
byteValue("restriction type %x") & 0xff
;
if ((restrType
& 0xf
) ==
5){
DisplayItem item = d.
byteItem();
int flags = item.
getValue() & 0xff
;
int numItems =
(flags
>> 5) & 0xff
;
String par =
"";
boolean extra =
(flags
& RESTR_EXTRA
) !=
0;
par +=
"extra" +
(extra
? "+," :
"-, ");
par +=
"pedestrian" +
((flags
& PEDESTRIAN
) !=
0 ? "+," :
"-, ");
par +=
"emergency" +
((flags
& EMERGENCY
) !=
0 ? "+," :
"-, ");
par +=
((flags
& MORE_EXCEPTIONS
) !=
0 ? "more," :
"no more, ");
item.
addText("%d arcs, %s", numItems, par
);
//d.item().addText(par);
d.
byteValue("??? %d");
if ((flags
& MORE_EXCEPTIONS
) !=
0){
//0x01: car; 0x02: bus; 0x04: taxi; 0x10: delivery; 0x20: bicycle; 0x40: truck
item = d.
byteItem();
int vehicleExceptions = item.
getValue();
StringBuilder sb =
new StringBuilder();
if ((vehicleExceptions
& 0x01
) !=
0)
sb.
append("car,");
if ((vehicleExceptions
& 0x02
) !=
0)
sb.
append("bus,");
if ((vehicleExceptions
& 0x04
) !=
0)
sb.
append("taxi,");
if ((vehicleExceptions
& 0x10
) !=
0)
sb.
append("delivery,");
if ((vehicleExceptions
& 0x20
) !=
0)
sb.
append("bicycle,");
if ((vehicleExceptions
& 0x40
) !=
0)
sb.
append("truck,");
item.
addText("vehicle exceptions %x %s", vehicleExceptions, sb.
toString());
}
// The list of nodes that are involved
for (int i =
0; i
< numItems+
1; i++
) {
// top bit always set
String arcType
;
if (i ==
0)
arcType =
"from";
else if (i == numItems
)
arcType =
"to";
else
arcType =
"via";
item = d.
charItem();
int off = item.
getValue();
if ((off
& 0x8000
) ==
0) {
int tabB = off
& 0x00ff
;
item.
addText(arcType +
" node %x (table B %d)", tableHeader.
getLink(tabB
)<<mult, tabB
);
if (tableHeader.
getLink(tabB
) == -
1)
item.
addText(" error BAD node");
} else {
int noff =
((int) tableHeader.
getPosition() - nodesStart
) -
(off
& 0x7fff
);
item.
addText(arcType +
" node %x", noff, off, tableHeader.
getPosition());
}
}
// The list of roads that are involved.
for (int i =
0; i
< numItems
; i++
) {
item = d.
byteItem();
int ri = item.
getValue() & 0xff
;
String roadType
;
if (i ==
0)
roadType =
"from";
else if (i+
1 == numItems
)
roadType =
"to";
else
roadType =
"via";
item.
addText("%s road %x (table A %d)", roadType,
tableHeader.
getRoad(ri
), ri
);
}
if (extra
) {
item = d.
byteItem();
item.
addText("restextra %d cs=%d", item.
getValue(), cSize
);
int b = d.
byteValue("extrafirst %x") & 0xff
;
if (b == 0x88
) {
d.
byteValue("??");
} else if (b == 0x8c
) {
d.
charValue("??");
} else if (b == 0x8d
) {
//d.intValue(3, "??");
d.
byteValue("??");
byte f1 = d.
byteValue("?? f");
if ((f1
& 0x80
) ==
0) {
d.
byteValue("??");
} else {
d.
charValue("??");
}
} else if ( b == 0x08 || b == 0x0c
) {
d.
rawValue(5,
"??");
//} else if ( b == 0x0c) {
// int val = d.intValue("??");
// //d.charValue("??");
// while ((val & 0x100000) == 0) {
// d.charValue("??");
// val = d.intValue("??");
// }
}
}
} else {
d.
item().
addText("unk restr type %x", restrType
);
}
}
d.
rawValue((int) (end - reader.
position()) +
(restrformat
& 3));
d.
print(outStream
);
}
private int latLongField
(Displayer d,
String label
) {
DisplayItem item = d.
int3Item();
int l = item.
getValue();
if ((l
& 0x800000
) !=
0)
l |= 0xff000000
;
item.
addText("%s %.5f [%x]", label, Utils.
toDegrees(l
), l
);
return l
;
}
private String getNameFromNetOff
(int off
) {
if (net
!=
null)
return net.
getNameFromNetOff(off
);
else
return "";
}
private void printRoadData
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("NOD 2 (road data)");
d.
setSectStart(roadDataStart
);
d.
print(outStream
);
reader.
position(roadDataStart
);
int end = roadDataStart + roadDataLen
;
int nrecords =
0;
while (reader.
position() < end
) {
nrecords++
;
d =
new Displayer
(reader
);
d.
setSectStart(roadDataStart
);
int recstart =
(int) reader.
position();
// agree with speed & class in RouteParam (plus one bit)
DisplayItem item = d.
item();
int flags = item.
setBytes1(reader.
get1u());
item.
addText("Road classification speed=%d, class=%d",
(flags
& 0xf
) >> 1,
(flags
& 0x70
) >> 4);
if((flags
& 1) ==
0)
item.
addText("bit 0 is zero");
else {
item = d.
int3Item();
int sectoff = item.
getValue();
item.
addText("offset into NOD 1 %06x", sectoff
<< mult
);
String origin =
String.
format("nod 2 %06x", recstart
);
Offset offset = nod1recs.
get(sectoff
);
if (offset
!=
null) {
offset.
appendOrigin(origin
);
} else {
Offset offval =
new Offset
(sectoff, origin
);
nod1recs.
put(sectoff, offval
);
}
// Ok this is the number of bits in the following.
int nbits = d.
charValue("Bit stream len %d");
// The number of set bits appears to be the number of nodes in the road.
// Usually th lowest nbits appear to be set, and I've seen the lowest
// missing when an end of the road is not a node. --Rob
// A number of bits follows. Work out how many bytes are needed to
// hold that number of bits.
int nstream =
(nbits+
7)/
8;
byte[] bs = d.
rawValue(nstream,
"Bit stream");
String bsStr = bitStreamAsString
(bs, nbits
);
d.
item().
addText("BIT STREAM %s", bsStr
);
}
if((flags
& 0x80
) !=
0) {
int extraFormat = d.
byteValue("extra data format");
if(extraFormat
>= 0x01
&& extraFormat
<= 0x0b
) {
if((extraFormat
& 0x01
) !=
0) {
int len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data 1");
}
if((extraFormat
& 0x02
) !=
0) {
int len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data 2");
}
if((extraFormat
& 0x04
) !=
0) {
d.
byteValue("extra data 4");
}
if((extraFormat
& 0x08
) !=
0) {
d.
charValue("extra data 8");
}
}
else if(extraFormat == 0x0c
) {
int len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data c");
}
else if(extraFormat == 0x0d
) {
int len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data da");
len = d.
byteValue("len");
d.
rawValue(len
>> 1,
"extra data db");
}
else if(extraFormat == 0x0e
) {
int len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data ea");
len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data eb");
}
else if(extraFormat == 0x0f
) {
int len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data fa");
len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data fb");
len = d.
byteValue("len") & 0xff
;
d.
rawValue(len
>> 1,
"extra data fc");
}
else
d.
item().
addText("Unknown format");
}
d.
gap();
d.
print(outStream
);
}
d.
item().
addText("Number of records %d", nrecords
);
d.
print(outStream
);
}
private String bitStreamAsString
(byte[] bs,
int nbits
) {
BitSet bset =
BitSet.
valueOf(bs
);
StringBuilder sb =
new StringBuilder();
for (int i =
0; i
< nbits
; i++
)
sb.
append(bset.
get(i
) ? "1" :
"0");
return sb.
toString();
}
/**
* This is a set of fixed length records, so is a good one to start with.
*/
private void printBoundryNodes
() {
Displayer d =
new Displayer
(reader
);
d.
setSectStart(nodesStart
);
d.
setTitle("NOD 3 (boundary nodes)");
reader.
position(boundriesStart
);
for (int pos = boundriesStart
; pos
< boundriesStart + boundriesLen
; pos += boundriesRecsize
) {
printBoundaryNode
(d
);
d.
gap();
}
d.
print(outStream
);
}
private void printHeader
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("NOD header");
long start = reader.
position();
nodesStart = d.
intValue("NOD 1 (nodes) at offset %#08x");
nodesLen = d.
intValue("NOD 1 length %d");
d.
item().
addText("End of section %08x, len %#08x", nodesStart + nodesLen, nodesLen
);
int flags = d.
charValue("flags"); // usually 0x0025 or 0x0027
if ((flags
& 0x02
) !=
0)
d.
item().
addText("have restrictions");
int multShift =
(flags
>> 5) & 0x7
;
distanceMult =
(1 << multShift
);
d.
charValue("???");
//d.charValue("???");
align = d.
byteValue("node align %x");
mult = d.
byteValue("ptr multiplier %d");
aMask =
(1<<align
) -
1;
tableARecordLen = d.
charValue("Table A record len %d");
roadDataStart = d.
intValue("NOD 2 (road data) at offset %#08x");
roadDataLen = d.
intValue("NOD 2 length %d");
d.
item().
addText("End of section %08x, len %#08x", roadDataStart + roadDataLen, roadDataLen
);
d.
intValue("???");
boundriesStart = d.
intValue("NOD 3 (boundary nodes) at offset %#08x");
boundriesLen = d.
intValue("NOD 3 length %d");
boundriesRecsize = d.
charValue("NOD 3 record size %d");
d.
item().
addText("End of section %08x, len %#08x", boundriesStart + boundriesLen, boundriesLen
)
.
addText("Number of records %d", boundriesLen / boundriesRecsize
);
d.
intValue("??");
if (getHeaderLen
() > 0x3f
) {
//d.intValue("??");
//d.intValue("??");
nod4 = readSection
(d,
"NOD4",
4,
false,
false);
// Each route center has an associated class. These pointers are to where the route
// centers for each particular class start.
int dst1 = d.
intValue("start of dst1 %x");
DisplayItem item = d.
intItem();
int dst2 = dst1 + item.
getValue();
item.
addText("start of dst2 %x (dst1 size %x)", dst2, item.
getValue());
item = d.
intItem();
int dst3 = dst2 + item.
getValue();
item.
addText("start of dst3 %x (dst2 size %x)", dst3, item.
getValue());
item = d.
intItem();
int dst4 = dst3 + item.
getValue();
item.
addText("start of dst4 %x (dst3 size %x)", dst4, item.
getValue());
item = d.
intItem();
item.
addText("dst4 size", item.
getValue());
}
if (getHeaderLen
() >= 0x7f
) {
reader.
position(reader.
getGMPOffset() + 0x67
);
nod5 = readSection
(d,
"NOD5",
5,
false,
false);
d.
charValue("???");
nod6 = readSection
(d,
"NOD6",
6,
true,
true);
indexIdSize = Utils.
numberToPointerSize(nod6.
getNumberOfRecords());
}
d.
rawValue((int) (getHeaderLen
() -
(reader.
position() - start
) ));
d.
print(outStream
);
}
private void printHighClassBoundary
() {
if (nod4 ==
null)
return;
Displayer d =
new Displayer
(reader
);
d.
setSectStart(nod4.
getStart());
d.
setTitle("NOD 4 (Boundary nodes with destclass > 0)");
reader.
position(nod4.
getStart());
while (reader.
position() < nod4.
getEnd()) {
printBoundaryNode
(d
);
d.
gap();
}
d.
print(outStream
);
}
private void printBoundaryNode
(Displayer d
){
latLongField
(d,
"longitude");
latLongField
(d,
"latitude");
DisplayItem item = d.
int3Item();
item.
addText("offset into NOD 1 %06x", item.
getValue() << mult
);
}
public static void main
(String[] args
) {
if (args.
length < 1) {
System.
err.
println("Usage: noddisplay [--tab-zero=0] <filename>");
System.
exit(1);
}
String name = args
[args.
length-
1];
NodDisplay nd =
new NodDisplay
();
if ("--tab-zero=0".
equals(args
[0]) )
nd.
hasExtraZeros=
false;
if ("--tab-zero=1".
equals(args
[0]) )
nd.
hasExtraZeros=
true;
nd.
display(name,
"NOD");
}
public void setNet
(NetDisplay net
) {
this.
net = net
;
}
public int getIndexIdSize
() {
return indexIdSize
;
}
}