Subversion Repositories display

Rev

Rev 540 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * 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;
                }
        }
}