Subversion Repositories display

Rev

Rev 289 | 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 uk.me.parabola.imgfmt.Utils;

/**
 * Standalone program to convert a routing network from NOD to OSM format
 * for visualising in, say, JOSM.
 */

public class NodConvert extends CommonDisplay {
        // whether this is a new direction compared to the previous link
        // not set for first link
        // thus, we have a short link if NEWDIR is not set and it's not the first link
        private static final int NEWDIR = 0x80;
        // this appears to signify the orientation of the link within the road
        private static final int SIGN = 0x40;
        private static final int CURVE = 0x20;
        private static final int LEN = 0x18;
        private static final int EXTRA = CURVE|LEN;

        // restrictions, first byte
        private static final int TOLL = 0x80;
        private static final int CLASS = 0x70;
        private static final int ONEWAY = 0x08;
        private static final int SPEED = 0x07;
        // second byte
        private static final int NOBIKE = 0x20;
        private static final int NOFOOT = 0x10;
        // missing restrictions: emergency, delivery, bus, car, taxi, truck
//      private static final int NOOTHER = 0xcf;

        private int nodesStart;
        private int nodesLen;

        private int align;
        private int aMask;

        private Table curTable;

        private final String booltag = "<tag k='%s' v='%b' />\n";
        private final String inttag = "<tag k='%s' v='%d' />\n";
        private final String bytetag = "<tag k='%s' v='0x%02x' />\n";
        private final String strtag = "<tag k='%s' v='%s' />\n";

        protected void print() {
                readCommonHeader();

                printHeader();

                System.out.printf("<?xml version='1.0' encoding='UTF-8'?>\n");
                System.out.printf("<osm version='0.5' generator='NodConvert'>\n");
                printNod1();
                System.out.printf("</osm>\n");
        }

        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 printNod1() {
                reader.position(nodesStart);
                while (reader.position() < nodesStart + nodesLen) {
                        long start = reader.position();
                        int low = reader.get() & 0xff;

                        long end = calcTablePosition(start, low);

                        reader.position(reader.position() - 1); // step back to low byte

                        curTable = readTable(end);

                        printNode(end);

                        reader.position(curTable.next);
                }
        }

        private void printNode(long end) {
                long groupStart = reader.position();
                while (reader.position() < end) {
                        long nodeOff = reader.position();
                        int low = reader.get();
                        if (low == 0) return; // XXX
                        calcTablePosition(nodeOff, low);
                        int flags = reader.get();

                        System.out.printf("<node id='%d' ", nodeOff+1-nodesStart);

                        boolean restr = (flags & 0x10) == 0x10;         // restrictions present at this node
                        boolean bigoff = (flags & 0x20) == 0x20;                // 2 bytes each for lat/lon offsets
                        boolean boundary = (flags & 0x08) == 0x08;      // this is a boundary node

                        positionOffsets(curTable, bigoff);

                        System.out.printf("<tag k='restrictions' v='%b' />\n", restr);
                        System.out.printf("<tag k='boundary' v='%b' />\n", boundary);
                        System.out.printf("<tag k='offset' v='0x%08x' />\n", nodeOff-nodesStart);
                        System.out.printf(inttag, "center", curTable.id);
                        System.out.printf("</node>\n");

                        pointerPart(nodeOff, groupStart, end);
                }
        }

        private int tableId = 1;

        class Table {

                final int id;
                final int nodeid;

                int lat, lon;

                long next; // start of next RouteCenter

                // just the class for now
                final ArrayList<Integer> tableA = new ArrayList<Integer>();
                // destination offsets for now
                final ArrayList<Integer> tableB = new ArrayList<Integer>();

                Table() {
                        id = tableId++;
                        nodeid = -id;
                }
        }


        private Table readTable(long offset) {
                System.err.println("reading table at " + offset);
                long orig = reader.position();

                Table table = new Table();

                reader.position(offset);


                // Get the header
                int restrbytes = reader.get() & 0xff;

                int l = latLongField();
                table.lon = l;

                l = latLongField();
                table.lat = l;

                System.out.printf("<node id='%d' lon='%f' lat='%f'>\n",
                        table.nodeid,
                        Utils.toDegrees(table.lon),
                        Utils.toDegrees(table.lat));
                System.out.printf(strtag, "routecenter", table.id);
                System.out.printf("</node>\n");



                int n = reader.get() & 0xff;
                int m = reader.get() & 0xff;

                System.err.println("table sizes: " + n + " " + m);

                // Now do 'Table A' (segments)
                for (int i = 0; i < n; i++) {
                        reader.get3();
                        int paramA = reader.get();
                        table.tableA.add((paramA & CLASS) >> 4);
                        reader.get();
                }

                // 'Table B' (inter-section pointers)
                for (int i = 0; i < m; i++) {
                        System.err.println("adding table b entry " + i);
                        table.tableB.add(reader.get3());
                }

                // 'Table C' (restrictions)
                int size=0;
                // I've seen restrbytes up to 2
                if (restrbytes == 0) {
                        reader.get();
                } else if (restrbytes == 1) {
                        size = reader.get() & 0xff;
                } else if (restrbytes == 2) {
                        size = reader.getChar() & 0xffff;
                } else if (restrbytes == 3) {
                        size = reader.getChar() & 0xffffff;
                } else {
                }
                if (size % 11 == 0) {
                        // assume these are fixed length records of size 11
                        for (; size > 0; size -= 11) {
                                // turn restriction at second node from first node (via first segment)
                                // to third node (via second segment)
                                reader.get3();
                                for (int i = 0; i < 3; i++) {
                                        reader.getChar();
                                }
                                reader.get();
                                reader.get();
                        }
                } else {
                }

                table.next = reader.position();

                // Restore position
                reader.position(orig);

                return table;
        }

        private void positionOffsets(Table currentTable, boolean bigoff) {
                short longoff;
                short latoff;
                if (bigoff) {
                        longoff = (short) reader.getChar();
                        latoff = (short) reader.getChar();
                } else {
                        int latlon = reader.get3();

                        latoff = (short) (latlon >> 12);
                        if ((latoff & 0x800) != 0)
                                latoff |= 0xf000;
                        longoff = (short) (latlon & 0xfff);
                        if ((longoff & 0x800) != 0)
                                longoff |= 0xf000;
                }
                System.out.printf("lon='%f' lat='%f'>\n",
                        Utils.toDegrees(longoff + currentTable.lon),
                        Utils.toDegrees(latoff + currentTable.lat));
        }

        private void pointerPart(long offset, long min, long max) {
                // looks like there are 2b before the low1 pointer and 4 after.
                boolean end = false;
                boolean first = true;

                do {
                        System.out.printf("<way id='%d'>\n", reader.position());
                        System.out.printf("<nd ref='%d' />\n", offset+1-nodesStart);

                        // Start with alt6 byte
                        // bit 0x20 seems to determine whether there's an extra byte at the end
                        int alt6 = reader.get() & 0xff;
                        System.out.printf(bytetag, "alt6", alt6);

                        // this is not the class of the segment, but the max of classes of the dest node
                        boolean newdir = (alt6 & NEWDIR) == 0x80;
                        System.out.printf(booltag, "newdir", newdir);
                        int classmask = 0x07;
                        int destclass = alt6 & classmask;
                        System.out.printf(inttag, "destclass", destclass);
                        System.out.printf(booltag, "sign", (alt6 & SIGN) != 0);
                        if (first) {
                                assert !newdir;
                                newdir = true;
                                first = false;
                        }

                        // Continue with two byte values.  The first one has the top
                        // bit set if this is the last pointer in the node record.
                        int intro1 = reader.get() & 0xff;

                        // Note that this is the last if it is.
                        if ((intro1 & 0x80) == 0x80) {
                                end = true;
                                System.out.printf(booltag, "last", true);
                        }

                        // The second highest bit, means inter-section pointer
                        boolean bit2 = (intro1 & 0x40) == 0x40;
                        System.out.printf(booltag, "external", bit2);
                        if (bit2) {
                                int idx = intro1 & 0x3f;
                                if (idx == 0x3f) {
                                        idx = reader.get() & 0xff;
                                }
                                System.out.printf(inttag, "indexB", idx);
                                System.out.printf("<nd ref='%d' />\n",
                                                curTable.tableB.get(idx) +1);
                        } else {
                                // in-section relative node pointer
                                int intro2 = reader.get() & 0xff;
                                short nodeoff = (short) ((intro1 << 8 | intro2) & 0x3fff);

                                // Construct a pointer to another node, signed 16 bit quantity
                                if ((nodeoff & 0x2000) != 0)
                                        nodeoff |= 0xc000;
                                long otherNode = offset + nodeoff;
                                System.out.printf("<nd ref='%d' />\n", otherNode+1-nodesStart);
                        }

                        if (newdir) {
                                int indexA = reader.get() & 0xff;
                                System.out.printf(inttag, "indexA", indexA);
                                System.out.printf(inttag, "roadclass",
                                                curTable.tableA.get(indexA));
                        }

                        System.out.printf(strtag, "type", newdir ? "arc" : "link");

                        int len;
                        boolean curve;
                        if ((alt6 & EXTRA) == EXTRA) {
                                int len1 = reader.get() & 0xff;
                                if ((len1 & 0x80) == 0x80) {
                                        if ((len1 & 0x40) == 0x40) {
                                                int len2 = reader.getChar();
                                                len = (len1 & 0x3f) | (len2 << 6); // 6+16 bits
                                                curve = true;
                                        } else {
                                                int len2 = reader.get() & 0xff;
                                                len = (len1 & 0x3f) | (len2 << 6); // 6+8 bits
                                                curve = false;
                                        }
                                } else {
                                        int len2 = reader.get() & 0xff;
                                        len = (len1 & 0x7f) | (len2 << 7); // 7+8 bits
                                        curve = true;
                                }
                        } else {
                                curve = (alt6 & CURVE) == CURVE;
                                len = (alt6 & 0x18) << 5;
                                len |= reader.get() & 0xff; // 2+8 bits
                        }
                        System.out.printf(inttag, "length", len);

                        if (newdir) {
                                int dir = reader.get() & 0xff;
                                System.out.printf(inttag, "direction", (dir * 360) >> 8);
                        }
       
                        if (curve) {
                                int curvea = reader.get() & 0xff;
                                int curveb;
                                if (curvea < 0x20) {
                                        curveb = reader.get() & 0xff;
                                        System.out.printf(inttag, "curvebytes", 2);
                                } else {
                                        System.out.printf(inttag, "curvebytes", 1);
                                        curveb = (curvea & 0x1f) << 3;
                                        curvea &= 0x70;
                                }
                                System.out.printf(inttag, "curvea", curvea);
                                System.out.printf(inttag, "curveb", curveb);
                        }
                        System.out.printf("</way>\n");
                } while (!end);

        }

        private int latLongField() {
                int l = reader.get3();
                if ((l & 0x800000) != 0)
                        l |= 0xff000000;
                return l;
        }

        private void printHeader() {
                Displayer d = new Displayer(reader);
                d.setTitle("NOD header");

                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);

                d.charValue("???");
                d.charValue("???");
                //d.charValue("???");
                align = d.byteValue("node align");
                aMask = (1<<align) - 1;
                d.byteValue("???");
                d.charValue("???");

                int roadDataStart = d.intValue("NOD 2 (road data) at offset %#08x");
                int roadDataLen = d.intValue("NOD 2 length %d");
                d.item().addText("End of section %08x, len %#08x", roadDataStart +
                                roadDataLen, roadDataLen);

                d.intValue("???");

                int boundriesStart = d.intValue("NOD 3 (boundary nodes) at offset %#08x");
                int boundriesLen = d.intValue("NOD 3 length %d");
                byte boundriesRecsize = d.byteValue("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.print(outStream);
        }

        public static void main(String[] args) {
                if (args.length < 1) {
                        System.err.println("Usage: noddisplay <filename>");
                        System.exit(1);
                }

                String name = args[0];

                NodConvert nd = new NodConvert();
                nd.setOutStream(System.err);

                nd.display(name, "NOD");
        }
}