Subversion Repositories display

Rev

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

/*
 * Copyright (C) 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 3 or
 * 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.
 */

package test.files;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.ImgFileReader;
import uk.me.parabola.imgfmt.app.ImgReader;
import uk.me.parabola.imgfmt.app.net.NODHeader;
import uk.me.parabola.util.EnhancedProperties;

import static test.util.ArcFlags.ARC_CURVE;
import static test.util.ArcFlags.ARC_EXTRA;
import static test.util.ArcFlags.ARC_SIGN;

/**
 * Read in the NOD file for display and testing purposes.
 *
 * @author Steve Ratcliffe
 */

public class NodFile extends ImgReader {

    private final int nodesStart;
    private final int nodesLen;
    private final int align;
    private final ImgFileReader reader;

    private final Map<Integer, RouteNode> allNodes = new HashMap<>();
    private final Map<Integer, RouteCenter> tables = new HashMap<>();


    public NodFile(ImgFileReader r) {
        reader = r;
        setReader(r);

        NODHeader header = new NODHeader();
        setHeader(header);

        header.readHeader(r);
        nodesStart = header.getNodeStart();
        nodesLen = header.getNodeSize();
        align = header.getAlign();
    }

    public void config(EnhancedProperties props) {
    }

    public List<RouteCenter> readNodeCenters() {
        List<RouteCenter> centers = new ArrayList<>();

        reader.position(nodesStart);

        int count = 0;
        while (reader.position() < nodesStart + nodesLen) {
            long start = reader.position();
            int low = reader.get() & 0xff;
            long end = calcTablePosition(start, low);

            RouteCenter table = RouteCenter.readTable(reader, nodesStart, end);
            tables.put((int) table.getOffset(), table);

            table.setIndex(count);

            reader.position(start); // step back to beginning of the group

            readNodesForCenter(table, start, nodesStart + end);

            reader.position(table.getNext());
            centers.add(table);
            count++;
        }

        return centers;
    }

    public List<RoadData> readRoads() {
        NODHeader header = (NODHeader) getHeader();
        reader.position(header.getRoadSection().getPosition());

        List<RoadData> roads = new ArrayList<>();

        long end = header.getRoadSection().getEndPos();
        while (reader.position() < end) {
            RoadData data = new RoadData();
            reader.get();
            int nodOffset = reader.getu3();
            data.setNodOffset(nodOffset);

            int bitlen = reader.getChar();
            int len = (bitlen + 7) / 8;
            reader.get(len);

            roads.add(data);
        }

        return roads;
    }

    /**
     * Read all nodes for the given node center.
     * @param table The route center for this group of nodes.
     * @param start The start position of the nodes.
     * @param end The end position of the nodes. This is also the start of the tables.
     */

    private void readNodesForCenter(RouteCenter table, long start, long end) {
        reader.position(start);

        while (reader.position() < end) {
            RouteNode node = readNode(table, 0);
            if (node == null || node.isBad())
                return;

            table.addNode(node);

            allNodes.put(node.getOffset(), node);
        }
    }

    public RouteNode fixNode(int start) {
        RouteNode node = readNode(null, start);
        if (node == null)
            return null;

        RouteCenter center = node.getCenter();
        center.addNode(node);
        allNodes.put(node.getOffset(), node);

        return node;
    }

    /**
     * Go through all the nodes that are reachable from NOD 2 section and attempt to read them.
     *
     * All nodes may not be reachable from Nod 2.
     */

    public void fixMissingNodes() {
        List<RoadData> roads = readRoads();

        List<Integer> offsets = new ArrayList<>();
        for (RoadData data : roads) {
            offsets.add(data.getNodOffset());
        }

        Collections.sort(offsets);

        for (Integer offset : offsets) {
            RouteNode node = getNode(offset);
            if (node == null) {
                fixNode(offset);
            }
        }
    }

    /**
     * Read a single node from NOD1.
     *
     * Can read (or will attempt to) a node from any address.
     *
     * @param center The route center that this node belongs to. If null it will be calculated.
     * @param start The start position of the node relative to the section data start.
     * @return The new node. If it can be determined that there is no proper node at the given position
     * then null is returned.
     */

    private RouteNode readNode(RouteCenter center, int start) {
        int offset;

        if (start == 0)
            offset = (int) reader.position() - nodesStart;
        else {
            offset = start;
            reader.position(start + nodesStart);
        }

        int low = reader.get() & 0xff;
        if (low == 0)
            return null;

        int tabstart = (int) calcTablePosition(offset + nodesStart, low);

        if (center == null) {
            // If we don't have a center then read it in or get from the cache
            center = tables.get(tabstart);

            if (center == null) {
                // This should not happen as long as we have read the whole section first
                //System.out.printf("Cache miss on table %x\n", tabstart);
                return null;
            }
        }

        RouteNode node = new RouteNode(center, offset);

        if (tabstart != center.getOffset()) {
            node.setBad(true);
            return node;
        }

        int flags = reader.get() & 0xff;
        node.setFlags(flags);

        Coord coord = readPosition(center, node.hasLargeCoordOffsets());
        node.setCoord(coord);

        if (node.hasArcs()) {
            readArcs(center, node);
        }

        if (node.hasRestrictions()) {
            boolean done = false;
            while (!done) {
                if (center.getCPointerSize() == 1) {
                    int off = reader.get();
                    done = (off & 0x80) == 0x80;
                } else if (center.getCPointerSize() == 2) {
                    int off = reader.getChar();
                    done = (off & 0x8000) == 0x8000;
                } else {
                    done = true;
                }
            }
        }

        node.setNextStart(reader.position()-nodesStart);
        return node;
    }

    private void readArcs(RouteCenter center, RouteNode node) {
        boolean end = false;
        boolean first = true;

                DirectionReader dirFetcher = new DirectionReader(reader);

                int count = 0;
        do {
            RouteArc arc = new RouteArc(node);
            arc.setIndex(count);
            arc.setOffset(reader.position()-nodesStart);

            // Start with alt6 byte
            int alt6 = reader.get() & 0xff;

            arc.setAlt6(alt6);

            // this is not the class of the segment, but the max of classes of the dest node
            boolean newdir = arc.hasNet();

            // 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) {
                arc.setLast(true);
                end = true;
            }

            // The second highest bit, means inter-section pointer
            boolean longlink = (intro1 & 0x40) == 0x40;
            if (longlink) {
                int idx = intro1 & 0x3f;
                if (idx == 0x3f) {
                    idx = reader.get() & 0xff;
                }

                // A long link that must be looked up from Table B.
                arc.setLongLink(true);
                int link = center.linkId(idx);
                if (link == -1) {
                    arc.setBad(true);
                    node.setProblem(true);
                } else if (link > 0)
                    arc.setLink(link);
            } 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;

                int otherNode = node.getOffset() + nodeoff;
                if (otherNode > 0)
                    arc.setLink(otherNode);
            }

                        if (first || newdir) {
                //d.byteValue("pointer to local net index %d");
                int netidx = reader.get() & 0xff;

                arc.setLocalNet(netidx);
                RouteCenter.TableA net = center.getNet(netidx);
                if (net != null) {
                    arc.setNetOffset(net.getNetOffset());
                    arc.setRoadClass(net.getRoadClass());
                    arc.setSpeed(net.getSpeed());
                } else {
                    arc.setBad(true);
                    node.setProblem(true);
                }
            }

            int len;
            boolean curve;
            if ((alt6 & ARC_EXTRA) == ARC_EXTRA) {
                int len1 = reader.get() & 0xff;
                if ((len1 & 0x80) == 0x80) {
                    if ((len1 & 0x40) == 0x40) {
                        //d.item().addText("22 bit length + curve");
                        int len2 = reader.getChar() & 0xff;
                        len = (len1 & 0x3f) | (len2 << 6); // 6+16 bits
                        curve = true;
                    } else {
                        //d.item().addText("14 bit length");
                        int len2 = reader.get() & 0xff;
                        len = (len1 & 0x3f) | (len2 << 6); // 6+8 bits
                        curve = false;
                    }
                } else {
                    //d.item().addText("15 bit length + curve");
                    int len2 = reader.get() & 0xff;
                    len = (len1 & 0x7f) | (len2 << 7); // 7+8 bits
                    curve = true;
                }
            }
            else {
                curve = (alt6 & ARC_CURVE) == ARC_CURVE;
                //if(curve)
                //      d.item().addText("9 bit length + curve");
                //else
                //      d.item().addText("9 bit length");
                len = (alt6 & 0x18) << 5;
                len |= reader.get() & 0xff; // 2+8 bits
            }
            // length should be in units of 16 feet
            //item.addText("length %d (%dm)", len, (int)(len * 16 / 3.25));
            arc.setRawDistance(len);

                arc.setRawDirection(dirFetcher.fetchDirection(first, newdir, (alt6 & ARC_SIGN) != 0));

                        if (curve) {
                int curvea = reader.get() & 0xff;
                if ((curvea & 0xe0) == 0) {
                    int curveb = reader.get() & 0xff;
                    //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);
                }
            }

            node.addArc(arc);

            first = false;
            count++;
        } while (!end);
    }

        private Coord readPosition(RouteCenter center, 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;
                }

                return new Coord(center.getLat() + latoff, center.getLon() + longoff);
        }

    /**
     *
     * @param start The real position in NOD1, ie from the start of the header.
     * @param low
     * @return The table offset within NOD1. Need to add nodeStart to get a position.
     */

    private long calcTablePosition(long start, int low) {
        int aMask = (1 << align) - 1;
        long pos = start - nodesStart;
        pos += aMask + 1;
        pos += low * (1 << align);
        pos &= ~aMask;
        return pos;
    }

    public RouteNode getNode(Integer offset) {
        return allNodes.get(offset);
    }

    public void resort() {
        for (RouteCenter table : tables.values()) {
            table.resort();
        }
    }
}