/*
* Copyright (C) 2007 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.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import uk.me.parabola.imgfmt.app.BitReader;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.lbl.Zip;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.imgfmt.app.trergn.Zoom;
import uk.me.parabola.log.Logger;
import test.elements.Line;
import test.elements.Point;
import test.files.ByteImgFileReader;
/**
* Standalone program to display the NET file as it is worked out. Will start
* out with what is in imgdecode by John Mechalas.
*
* Can produce a massive file, so may take some time to write.
*
* @author Steve Ratcliffe
*/
public class NetDisplay
extends CommonDisplay
{
private static final Logger log =
Logger.
getLogger(CommonDisplay.
class);
private static final int U_FLAG_1 = 0x04
;
private static final int U_FLAG_2 = 0x08
;
private static final int U_FLAG_3 = 0x10
;
private int roadDefStart
;
private int roadDefSize
;
// This is 'Unknown 2' in imgdecode. It appears to be a list of offsets
// to the records in road defs. But it is in a different order, why? - Because
// it puts them in alphabetical order, perhaps for ease of searching.
//
// .. and it is not complete, there are a subset. -- but all ones with names
// are there.
//
// Also some of them have a 4 in the top nibble. These always point to
// a record with two labels.
private int roadIndexStart
;
private int roadIndexSize
;
private int roadIndexRecsize
;
// Road data flags.
private static final int RD_HAS_NOD_INFO = 0x40
;
private static final int RD_HAS_STREET_ADDRESS_INFO = 0x10
;
private static final int RD_DIRECTION_INDICATOR = 0x02
;
private final Map<Long,
String> netnames =
new HashMap<>();
// Index of net1 offset to text string, in order as in net3
private final Map<Integer,
String> sortedRoads =
new LinkedHashMap<>();
private final SortedSet<Integer> roadOffsets =
new TreeSet<>();
private int mult1
;
protected void print
() {
readCommonHeader
();
openLbl
();
findRoads
();
printHeader
();
printRoadIndex
();
printRoadDefs
();
printSortedRoads
();
}
/**
* We have to find all the roads positions before we start.
*/
private void findRoads
() {
openTre
();
openRgn
();
openNet
();
Zoom
[] levels = tre.
getMapLevels();
Subdivision
[] subdivisions = tre.
subdivForLevel(levels
[levels.
length-
1].
getLevel());
for (Subdivision div : subdivisions
) {
List<Line> lines = rgn.
linesForSubdiv(div
);
for (Line line : lines
) {
if (line.
hasNet())
roadOffsets.
add(line.
getNetOffset());
}
}
}
private void printSortedRoads
() {
outStream.
println("----------------- Roads as sorted in NET 3 ------------------------");
for (Map.Entry<Integer,
String> ent : sortedRoads.
entrySet()) {
outStream.
printf(" 0x%06x: %s\n", ent.
getKey(), ent.
getValue());
}
}
private void printRoadDefs
() {
//assert !sortedRoads.isEmpty();
Displayer d =
new Displayer
(reader
);
d.
setTitle("NET 1 (Road definitions)");
d.
print(outStream
);
ArrayList<Integer> list =
new ArrayList<>();
for (int off : sortedRoads.
keySet())
list.
add(off
& ~0xc00000
);
Collections.
sort(list
);
reader.
position(roadDefStart
);
int end = roadDefStart + roadDefSize
;
Iterator<Integer> iterator = roadOffsets.
iterator();
if (!iterator.
hasNext())
return;
// First will be at zero
int nextPos = iterator.
next();
assert nextPos ==
0;
int roadNum =
0;
while (reader.
position() < end
) {
d =
new Displayer
(reader
);
d.
setSectStart(roadDefStart
);
long netoff = reader.
position() - roadDefStart
;
while (mult1
!=
0 && (reader.
position() & (2*mult1 -
1)) !=
0)
d.
byteValue("pad");
d.
gap();
d.
item().
addText("Road number %d, at offset %06x (next=x%x)", roadNum++, netoff, nextPos
);
d.
print(outStream
);
int labval
;
DisplayItem item
;
int labNum =
0;
do {
item = d.
item();
labval = reader.
get3u();
item.
setBytes3(labval
);
int laboff = labval
& 0x3fffff
;
String labelstr = fetchLabel
(laboff
);
item.
addText("Label offset: %x (%s)", laboff, labelstr
);
netnames.
put(netoff, labelstr
);
// Set the name as found into our sorted roads map.
if (sortedRoads.
containsKey((int) netoff +
(labNum
<< 22)))
sortedRoads.
put((int) netoff +
(labNum
<< 22), labelstr
);
labNum++
;
} while ((labval
& 0x800000
) ==
0);
item = d.
byteItem();
int flags = item.
getValue() & 0xff
;
StringBuilder flagDesc =
new StringBuilder();
if ((flags
& RD_HAS_NOD_INFO
) !=
0)
flagDesc.
append("nod,");
if ((flags
& RD_HAS_STREET_ADDRESS_INFO
) !=
0)
flagDesc.
append("addr,");
if ((flags
& RD_DIRECTION_INDICATOR
) !=
0)
flagDesc.
append("oneway,");
item.
addText("Flags %02x %s", flags, flagDesc
);
item = d.
item();
int rlen = reader.
get3u();
item.
setBytes3(rlen
);
// this is possibly different on different maps, working with this
// for now.
item.
addText("Road len %d meters",
Math.
round(2*2.4*rlen
));
printLevelDivs
(d
);
// Various sections that can be present depending on flags above.
if ((flags
& RD_HAS_STREET_ADDRESS_INFO
) == RD_HAS_STREET_ADDRESS_INFO
) {
DisplayItem bitem = d.
byteItem();
DisplayItem afitem = d.
item();
int addrFlags = afitem.
setBytes1(reader.
get1u());
int nblocks =
(bitem.
getValue() & 0xff
) +
((addrFlags
& 0x3
) << 8);
bitem.
addText("Number of blocks %d", nblocks
);
afitem.
addText("flags z%x c%x n%x (hi-part of blocks %x)",
(addrFlags
>>2)&0x3,
(addrFlags
>>4)&0x3,
(addrFlags
>>6)&0x3, addrFlags
&0x3
);
printAddrInfo
(d, addrFlags
>> 2,
"zip", zipSize
);
printAddrInfo
(d, addrFlags
>> 4,
"city", citySize
);
printAddrInfo
(d, addrFlags
>> 6,
"numbers",
(nblocks
<< 8) + flags
);
}
if ((flags
& RD_HAS_NOD_INFO
) == RD_HAS_NOD_INFO
) {
int nfollow = d.
byteValue("flags for bytes following %x");
// OK now it is clear that this is a pointer into NOD-2
// nfollow {
// nod_pointer_size: 2
// unk: 3
// counter: 2
int nodSize = nfollow
& 0x3
;
d.
intValue(nodSize +
1,
"nod 2 pointer %x");
int rest = nfollow
& 0xfc
;
if (rest
> 0)
d.
item().
addText("rest %x", rest
);
int counter =
(rest
>> 5) & 3;
if (counter
> 0) {
d.
item().
addText("counter %d", counter
);
while (counter--
> 0) {
int uflag = d.
byteValue("uflag %x") & 0xff
;
d.
item().
addText("flag1=%b flag2=%b flag3=%b lo=%d hi=%d",
(uflag
&U_FLAG_1
)!=
0,
(uflag
&U_FLAG_2
)!=
0,
(uflag
&U_FLAG_3
) !=
0, uflag
&0x3,
(uflag
>>>4));
// Variable length (?) bit command.
// 0 read number of bytes as given in lo bits, append to uflag and shift down
// 10 shift down uflag, add one, read this many bytes
// 11x shift down uflag, read 1, combine. read that many bytes.
int lo = uflag
& 0x3
;
if ((uflag
& U_FLAG_1
) ==
0) {
int idx = d.
intValue((uflag
& 0x3
)+
1,
"partial value");
idx +=
(uflag
>>>3);
} else {
int len
;
if ((uflag
& U_FLAG_2
) == U_FLAG_2
) {
len = d.
byteValue("length (hi)") << 3;
len +=
(uflag
>>> 5);
} else {
len =
1 +
(uflag
>>> 4);
}
d.
item().
addText("length %d", len
);
if (len
> 0) {
d.
rawValue(len,
"data");
//printAddrInfo(d, 0, "city", len);
} else {
d.
item().
addText("TODO: will not work");
}
}
}
} else if ((rest
& 0x80
) == 0x80
) {
d.
rawValue(8,
"80 data"); // XXX
}
}
// Step to the next road.
if (!iterator.
hasNext())
break;
nextPos = iterator.
next() << mult1
;
int len =
(int) (nextPos -
(reader.
position() - roadDefStart
));
if (len
< 0) {
d.
item().
addText("skip back %d", -len
);
reader.
position(reader.
position() + len
);
} else
d.
rawValue(len,
"catch up to next start");
d.
print(outStream
);
}
d.
print(outStream
);
}
private void printRoadIndex
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("NET 3 (Road index)");
if (roadIndexRecsize
> 3)
assert false :
"not implemented, unknown record size " + roadIndexSize
;
reader.
position(roadIndexStart
);
for (int pos =
0; pos
< roadIndexSize
; pos += roadIndexRecsize
) {
int off = d.
int3Value("Offset %06x");
// This establishes the order
sortedRoads.
put(off,
null);
}
d.
print(outStream
);
}
private void printLevelDivs
(Displayer d
) {
DisplayItem item
;
// one has the 0x80 bit set. They are counts of things to follow.
int[] counts =
new int[8];
int ncounts =
0;
boolean last =
false;
while (!last
) {
item = d.
item();
int b = reader.
get1u();
item.
setBytes1(b
);
if ((b
& 0x80
) == 0x80
) {
last =
true;
}
counts
[ncounts++
] = b
& 0x7f
;
item.
addText("count %d", counts
[ncounts-
1]);
if (ncounts
> 6) {
log.
error("Suspicious large ncounts " + ncounts
);
d.
print(outStream
);
outStream.
flush();
}
}
// Not in imgdecode, but is in JM's document.
for (int i =
0; i
< ncounts
; i++
) {
int n = counts
[i
];
while (n--
> 0) {
item = d.
item();
int val = item.
setBytes1(reader.
get1u());
item.
addText("Level %d: line %d", i, val
);
d.
charValue("in subdiv %d");
}
}
}
private void printAddrInfo
(Displayer d,
int addrFlags,
String type,
int size
) {
switch (addrFlags
& 0x3
) {
case 0:
// number field;
int nfollow = d.
byteValue("%d following") & 0xff
;
d.
print(outStream
);
switch (type
) {
case "numbers":
NumberDisplayer nd =
new NumberDisplayer
();
try {
// size is flags here
nd.
printNumbers(d, nfollow, size
);
} catch (NumberException e
) {
d.
item().
addText("Except: %s", e.
getMessage());
nd.
remainingBits();
d.
print(outStream
);
return;
} catch (ArrayIndexOutOfBoundsException e
) {
d.
print(outStream
);
throw e
;
}
break;
case "city":
CityZipDisplayer cd =
new CityZipDisplayer
(d, nfollow, size,
true);
cd.
print("CityInfo:");
break;
case "zip":
CityZipDisplayer zd =
new CityZipDisplayer
(d, nfollow, size,
false);
zd.
print("ZipInfo:");
break;
default:
d.
rawValue(nfollow, type
);
break;
}
break;
case 1:
// number field;
nfollow = d.
charValue("%d following");
if (type.
equals("numbers")) {
try {
// size is flags here
(new NumberDisplayer
()).
printNumbers(d, nfollow, size
);
} catch (NumberException e
) {
d.
print(outStream
);
return;
}
} else {
d.
rawValue(nfollow, type
);
}
break;
case 2:
// zip index
// can be two bytes
if (size ==
2)
d.
charValue(type +
" index %d");
else
d.
byteValue(type +
" index %d");
break;
default:
// No field, do nothing
break;
}
}
static class NumberException
extends RuntimeException {
public NumberException
(String msg
) {
super(msg
);
}
}
static class NumberDisplayer
{
private static final int STYLE_NONE =
0;
private static final int STYLE_EVEN =
1;
private static final int STYLE_ODD =
2;
private static final int STYLE_BOTH =
3;
private BitReader br
;
private int len
;
private DisplayItem item
;
// For reading the start differences and end difference numbers.
private VarBitReader startReader
;
private VarBitReader endReader
;
private VarBitReader savedStartReader
;
private VarBitReader savedEndReader
;
private boolean doRestoreBitWidths
;
// base numbers
private int leftBase
;
private int rightBase
;
// numbering styles
private int leftStyle = STYLE_EVEN
;
private int rightStyle = STYLE_ODD
;
// start numbers
private int leftStart
;
private int rightStart
;
// end numbers
private int leftEnd
;
private int rightEnd
;
// saved end numbers
private int leftLastEndDiff
;
private int rightLastEndDiff
;
// Numbers are a range between nodes. Keep count of them here
private int nodeCounter
;
private int numberCounter
; // like nodeCounter but does not count skips
// was the last thing read a number range? helps in detecting
// end of stream.
private int lastCommand = -
1;
/**
* Main routine to print the house numbers.
*/
private void printNumbers
(Displayer d,
int len,
int flags
) throws NumberException
{
this.
len = len
* 8;
item = d.
rawItem(len
& 0xff
);
byte[] bytes = item.
getBytes();
br =
new BitReader
(bytes
);
int nblocks =
(flags
>> 8);
item.
addText("numbers nblocks=%d, flgs=0x%x", nblocks, flags
& 0xff
);
if ((flags
& 0x20
) ==
0) {
leftStyle = STYLE_ODD
;
rightStyle = STYLE_EVEN
;
} else {
leftStyle = STYLE_EVEN
;
rightStyle = STYLE_ODD
;
}
item.
addText("def left=%s right=%s", toStyleString
(leftStyle
), toStyleString
(rightStyle
));
getBitWidths
();
getInitialBase
();
while (nodeCounter
< nblocks+
1) {
try {
runCommand
();
} catch (NumberException |
ArrayIndexOutOfBoundsException e
) {
item.
addText("Expected %d blocks, saw %d/%d before Exception", nblocks, nodeCounter, numberCounter
);
return;
}
}
item.
addText("saw %d nodes, %d numbers", nodeCounter, numberCounter
);
remainingBits
();
}
/**
* Get the bit widths for the start and end differences.
* Based on code for reading the RGN streams, but the signed bit is the
* opposite value.
* x is for start value differences. y is for end value differences.
*/
private void getBitWidths
() {
startReader =
new VarBitReader
(br,
5);
endReader =
new VarBitReader
(br,
2);
item.
addText("start read %s", startReader.
toString());
item.
addText("end read %s", endReader.
toString());
}
/**
* Decode the next command in the stream and run it.
*/
private void runCommand
() throws NumberException
{
int cmd = readCommand
(); // fetch 1, 3 skip, 2 reload, 0 style
switch (cmd
) {
case 0:
changeStyles
();
break;
case 1:
fetchNumbers
();
break;
case 2:
useBits
();
break;
case 6:
skipNodes
();
break;
default:
fail
("unimplemented command: " + cmd
);
}
}
/**
* Temporarily use a different bit width for the following number fetch.
*/
private void useBits
() {
if (!doRestoreBitWidths
) {
savedStartReader = startReader
;
savedEndReader = endReader
;
}
doRestoreBitWidths =
true;
if (br.
get1()) {
endReader =
new VarBitReader
(br,
2);
item.
addText("cmd: bits end %s", endReader.
toString());
} else {
startReader =
new VarBitReader
(br,
5);
item.
addText("cmd: bits start %s", startReader.
toString());
}
}
/**
* Skip nodes. For parts of a road that has no numbers.
*/
private void skipNodes
() {
boolean f = br.
get1();
int skip
;
if (f
)
skip =
1 + br.
get(10);
else
skip =
1 + br.
get(5);
nodeCounter += skip
;
item.
addText("cmd: skip %d", skip
);
}
/**
* Read the next command from the stream. Commands are variable length in the bit
* stream.
* 0 - numbering style (none, odd, even, both)
* 1 - fetch numbers
* 2 - change bit widths
* 6 - skip nodes
* @return The command number
*/
private int readCommand
() {
int cmd =
0;
if (br.
get1()) {
cmd |= 0x1
;
} else {
if (br.
get1()) {
cmd |= 0x2
;
if (br.
get1()) {
cmd |= 0x4
;
}
}
}
// probably end of stream
if (cmd ==
0 && lastCommand ==
0 && br.
getBitPosition() > this.
len -
8)
throw new NumberException
("probably end of stream cmd="+cmd
);
lastCommand = cmd
;
return cmd
;
}
/**
* Read the house numbers for a stretch of road.
*
* The start and end positions of the the left hand side of the road is first, followed
* by the right hand side of the road.
*
* The differences to the last point are stored. It is also possible to
*/
private void fetchNumbers
() {
item.
addText("cmd: fetch numbers");
// If one side has no numbers, then there is only one set of numbers to calculate, but
// changes to base are applied to both sides.
boolean doSingleSide =
(leftStyle ==
0 || rightStyle ==
0);
if (leftStyle ==
0)
leftBase = rightBase
;
// Check for command to copy the base number
boolean doSameBase =
false;
if (!doSingleSide
) {
doSameBase = br.
get1();
if (doSameBase
)
copyBase
();
}
//int abc = br.get(3);
boolean doRightOverride =
false;
if (!doSingleSide
)
doRightOverride =
!br.
get1();
boolean doReadStart =
!br.
get1();
boolean doReadEnd =
!br.
get1();
int startDiff =
0, endDiff = leftLastEndDiff
;
if (doReadStart
) {
startDiff = startReader.
read();
item.
addText(" read start diff %d", startDiff
);
}
if (doReadEnd
) {
endDiff = endReader.
read();
item.
addText(" read end diff %d", endDiff
);
}
item.
addText("values: L base=%d, start diff=%d, end diff=%d", leftBase, startDiff, endDiff
);
leftStart = leftBase + startDiff
;
leftEnd = leftStart + endDiff
;
leftBase = leftEnd
;
leftLastEndDiff = endDiff
;
if (doSingleSide
) {
printSingleSide
();
restoreReaders
();
return;
}
// *** Now for the right hand side numbers ***
// Note that endDiff falls through to this part.
// Start diff also does, at least when doSameBase is true
if (!doSameBase
)
startDiff =
0;
// If we didn't read an endDiff value for the left side or right is different then
// default to the saved value.
if (doRightOverride ||
!doReadEnd
) {
endDiff = rightLastEndDiff
;
item.
addText(" default endDiff(R) %d", endDiff
);
}
doReadStart =
false;
doReadEnd =
false;
if (!doSameBase
)
doReadStart =
!br.
get1();
if (doRightOverride
)
doReadEnd =
!br.
get1();
if (doReadStart
) {
startDiff = startReader.
read();
item.
addText(" read start diff %d (R)", startDiff
);
}
if (doReadEnd
) {
endDiff = endReader.
read();
item.
addText(" read end diff %d (R)", endDiff
);
}
item.
addText("values: R base=%d, start diff=%d, end diff=%d", rightBase, startDiff, endDiff
);
rightStart = rightBase + startDiff
;
rightEnd = rightStart + endDiff
;
rightBase = rightEnd
;
rightLastEndDiff = endDiff
;
adjustValues
();
item.
addText("Numbers %d,%s,%d,%d,%s,%d,%d", nodeCounter, toStyleString
(leftStyle
), leftStart, leftEnd,
toStyleString
(rightStyle
), rightStart, rightEnd
);
nodeCounter++
;
numberCounter++
;
restoreReaders
();
}
/**
* After a temporary bit width change.
*/
private void restoreReaders
() {
if (doRestoreBitWidths
) {
startReader = savedStartReader
;
endReader = savedEndReader
;
doRestoreBitWidths =
false;
}
}
/**
* If the road has numbers on just one side, then there is a shortened reading routine.
* The left variables are mostly used during reading regardless of which side of the
* road has numbers. Make everything work here.
*/
private void printSingleSide
() {
rightBase = leftBase
;
rightStart = leftStart
;
rightEnd = leftEnd
;
rightLastEndDiff = leftLastEndDiff
;
adjustValues
();
if (leftStyle == STYLE_NONE
)
item.
addText("Numbers %d,N,-1,-1,%s,%d,%d", nodeCounter, toStyleString
(rightStyle
), rightStart, rightEnd
);
else
item.
addText("Numbers %d,%s,%d,%d,N,-1,-1", nodeCounter, toStyleString
(leftStyle
), leftStart, leftEnd
);
nodeCounter++
;
numberCounter++
;
}
/**
* When it is known if the numbers are odd or even, then a shorter bitstream is made
* by taking advantage of that fact. This leaves the start and end points needing
* adjustment to made them odd or even as appropriate.
*/
private void adjustValues
() {
int ldirection =
1; // direction start is adjusted in; end in the opposite direction.
if (leftStart
< leftEnd
)
leftEnd--
;
else if (leftStart
> leftEnd
) {
leftEnd++
;
ldirection = -
1;
}
int rdirection =
1; // direction start is adjusted in; end in the opposite direction.
if (rightStart
< rightEnd
)
rightEnd--
;
else if (rightStart
> rightEnd
) {
rightEnd++
;
rdirection = -
1;
}
if (leftStyle == STYLE_EVEN
) {
if ((leftStart
& 1) ==
1) leftStart += ldirection
;
if ((leftEnd
& 1) ==
1) leftEnd -= ldirection
;
} else if (leftStyle == STYLE_ODD
) {
if ((leftStart
& 1) ==
0) leftStart+=ldirection
;
if ((leftEnd
& 1) ==
0) leftEnd-=ldirection
;
}
if (rightStyle == STYLE_EVEN
) {
if ((rightStart
& 1) ==
1) rightStart+=rdirection
;
if ((rightEnd
& 1) ==
1) rightEnd-=rdirection
;
} else if (rightStyle == STYLE_ODD
) {
if ((rightStart
& 1) ==
0) rightStart+=rdirection
;
if ((rightEnd
& 1) ==
0) rightEnd-=rdirection
;
}
}
/**
* Copy one of the bases to the other so they have the same value.
* The source is determined by reading a bit from the input.
*/
private void copyBase
() {
boolean f2 = br.
get1();
if (f2
) {
item.
addText("cmd: base rightBase = leftBase [%d (%d)]", leftBase, rightBase
);
rightBase = leftBase
;
} else {
item.
addText("cmd: base leftBase = rightBase [%d (%d)]", rightBase, leftBase
);
leftBase = rightBase
;
}
}
private String toStyleString
(int style
) {
switch (style
) {
case 0:
return "N";
case 1:
return "E";
case 2:
return "O";
case 3:
return "B";
default:
fail
("bad numbering style");
}
return "X"; // not actually reachable
}
/**
* Change the numbering styles for this section of roads.
*/
private void changeStyles
() {
leftStyle = br.
get(2);
rightStyle = br.
get(2);
item.
addText("cmd: style left=%s, right=%s", toStyleString
(leftStyle
), toStyleString
(rightStyle
));
}
/**
* Get the initial base value. The first number for this section of road (although a diff
* can be applied to it).
*
* @throws NumberException
*/
private void getInitialBase
() {
int extra =
0;
boolean b1 = br.
get1();
if (!b1
)
extra = br.
get(4);
leftBase = br.
get(5 + extra
);
rightBase = leftBase
;
item.
addText("base=%d", leftBase
);
}
/**
* For cases that are not implemented yet.
*/
private void fail
(String s
) throws NumberException
{
item.
addText("ABANDON: %s", s
);
remainingBits
();
throw new NumberException
("ABANDON: " + s
);
}
/**
* Just print out any remaining bits.
*
* Was mostly used during development, before the whole stream was decoded.
*/
private void remainingBits
() {
StringBuilder sb =
new StringBuilder();
while (br.
getBitPosition() < len
) {
sb.
insert(0, br.
get1() ? "1" :
"0");
}
item.
addText(sb.
toString());
}
/**
* Reads integers with specified numbers of bits and optionally with sign bits.
*/
static class VarBitReader
{
private boolean signed
; // read as signed values
private boolean negative
; // all values are read as positive and then negated
private int width
; // the number of bits
private final int off
; // a value to be added to width to get the true number to read.
private BitReader br
;
public VarBitReader
(BitReader br,
int off
) {
this.
br = br
;
this.
off = off
;
negative = br.
get1();
signed = br.
get1();
width = br.
get(4);
}
public int read
() {
int val
;
if (signed
) {
val = br.
sget(width + off +
1);
} else {
val = br.
get(width + off
);
}
if (negative
)
val = -val
;
return val
;
}
public String toString
() {
return String.
format("sign=%b neg=%b width=%d+%d", signed, negative, width, off
);
}
}
}
private void printHeader
() {
Displayer d =
new Displayer
(reader
);
d.
setTitle("NET header");
roadDefStart = d.
intValue("NET 1, Road defs, offset %x");
roadDefSize = d.
intValue("NET 1, Road defs, length %d");
DisplayItem item = d.
item();
item.
addText("end of section at %#x", roadDefStart + roadDefSize
);
item = d.
byteItem();
mult1 = item.
getValue() & 0xff
;
item.
addText("Road def offsets are x%d",
1 << mult1
);
// Unknown sect 1, variable length fields?
int u1Start = d.
intValue("NET 2, Segmented roads, offset %x");
int u1Size = d.
intValue("NET 2, Segmented roads, length %d");
item = d.
item();
item.
addText("end of section at %#x", u1Start + u1Size
);
// This may not be right, just my guess based on the prev
item = d.
byteItem();
int mult2 = item.
getValue() & 0xff
;
item.
addText("Road index offsets are x%d",
1 << mult2
);
// Unknown section with fixed length fields
roadIndexStart = d.
intValue("NET 3, Road index, offset %x");
roadIndexSize = d.
intValue("NET 3, Road index, length %d");
item = d.
item();
item.
addText("end of section at %#x", roadIndexStart + roadIndexSize
);
roadIndexRecsize = d.
charValue("Sorted roads record size %d");
d.
item().
addText("Number of records is %d", roadIndexSize/roadIndexRecsize
);
d.
intValue("???");
d.
byteValue("???");
d.
byteValue("???");
int remain =
(int) (getHeaderLen
() - reader.
position());
d.
rawValue(remain
);
d.
print(outStream
);
}
String getNameFromNetOff
(long off
) {
String s = netnames.
get(off
);
if (s ==
null)
s =
"";
return s
;
}
public static void main
(String[] args
) {
if (args.
length < 1) {
System.
err.
println("Usage: netdisplay <filename>");
System.
exit(1);
}
String name = args
[0];
CommonDisplay nd =
new NetDisplay
();
nd.
display(name,
"NET");
}
public class CityZipDisplayer
{
private final int blockSize
;
private final DisplayItem item
;
private final ImgFileReader reader
;
private final int size
;
private final boolean isCity
;
private final List<City
> cities
;
private final List<Zip
> zips
;
private int node
;
private int left
;
private int right
;
public CityZipDisplayer
(Displayer d,
int nfollow,
int size,
boolean isCity
) {
this.
isCity = isCity
;
item = d.
rawItem(nfollow
& 0xff
);
reader =
new ByteImgFileReader
(item.
getBytes());
this.
size = size
;
blockSize = nfollow
;
cities = lbl.
getCities();
zips = lbl.
getZips();
}
public void print
(String text
) {
item.
addText(text
);
while (reader.
position() < blockSize
) {
int startNode = node
;
int initFlag = getInitFlag
();
if (initFlag ==
0) {
right = left = getCityOrZip
();
} else if ((initFlag
& 0x4
) !=
0) {
if ((initFlag
& 1) ==
0)
right =
0;
if ((initFlag
& 2) ==
0)
left =
0;
} else {
if ((initFlag
& 1) !=
0)
left = getCityOrZip
();
if ((initFlag
& 2) !=
0)
right = getCityOrZip
();
}
item.
addText(" %d-%d: left=%d[%s], right=%d[%s]", startNode, node -
1, left, nameFor
(left
), right, nameFor
(right
));
}
}
private String nameFor
(int n
) {
if (isCity
) {
if (n ==
0)
return "No city";
City city = cities.
get(n-
1);
if (city
!=
null) {
Label label = city.
getLabel();
if (label ==
null) {
Subdivision
[] subdivisions = tre.
subdivForLevel(0);
int subdiv = city.
getSubdivNumber();
int point = city.
getPointIndex();
int number = subdivisions
[0].
getNumber();
Subdivision subdivision = subdivisions
[subdiv - number
];
List<Point> points = rgn.
pointsForSubdiv(subdivision
);
Point p = points.
get(point -
1);
label = p.
getLabel();
city.
setLabel(label
); // Cache it
return label.
getText();
} else
return label.
getText();
}
} else {
if (n ==
0)
return "No zip";
Zip zip = zips.
get(n-
1);
if (zip
!=
null) {
Label label = zip.
getLabel();
if (label
!=
null)
return label.
getText();
}
}
return "Not found";
}
private int getInitFlag
() {
int initFlag = reader.
get1u();
int skip =
(initFlag
& 0x1f
);
initFlag
>>=
5;
if (initFlag ==
7) {
// Need to read another byte
initFlag = reader.
get1u();
skip |=
((initFlag
& 0x1f
) << 5);
initFlag
>>=
5;
}
node += skip +
1;
item.
addText("flag 0x%x", initFlag
);
return initFlag
;
}
private int getCityOrZip
() {
if (reader.
position() > blockSize - size
) {
item.
addText("ERROR: overflow");
return 0;
}
int cnum
;
if (size ==
1)
cnum = reader.
get1u();
else if (size ==
2)
cnum = reader.
get2u();
else {
item.
addText("Bad city/zip size " + size
);
return 0;
}
item.
addText(" [read city/zip %d]", cnum
);
return cnum
;
}
}
}