Subversion Repositories display

Rev

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

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