Subversion Repositories display

Rev

Rev 365 | 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.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;

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

public class NodFile extends ImgReader {
        private static final double UNIT_TO_METER = 2.391;

        private final int nodesStart;
    private final int nodesLen;
    private final int align;
        private final int mult;
        private final int distanceMult;
        private final int tableARecordLen;
    private final ImgFileReader reader;

    private final Map<Integer, RouteNode> allNodes = new HashMap<>();
    private final Map<Integer, RouteCenter> tables = new HashMap<>();
        private Map<Integer, Nod2Record> roads = 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();
                mult = header.getMult1();
            tableARecordLen = header.getTableARecordLen();
                int flags = header.getFlags();

                // This appears to be 3 bits long. If you change this in a map, then the time
                // estimates for a route change, although not the distance estimates. Possible that
                // there is more to it.
                int mult = (flags >> 5) & 0x3;
                distanceMult = (1 << mult);
        }

        public int metersToRaw(double m) {
                double d = m / (distanceMult * UNIT_TO_METER);
                return (int) Math.round(d);
        }

        /**
         * Convert from the internal representation to meters.
         * @param raw The internal length value.  Represents 16 feet.
         * @return Length of the arc in meters.
         */

        public int rawToMeters(int raw) {
            return (int) Math.round(((double) raw+0.5) * (distanceMult * UNIT_TO_METER));
        }

        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 = new RouteCenter(end, tableARecordLen);
            table.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;
    }

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


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

        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;
            if (low == 0 && flags == 0)
                    return null;
        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) {
                    int ptrSize = center.getCPointerSize();
                    if (ptrSize == 1) {
                    int off = reader.get();
                    done = (off & 0x80) == 0x80;
                } else if (ptrSize == 2) {
                    int off = reader.getChar();
                    done = (off & 0x8000) == 0x8000;
                } else {
                    done = true;
                }
            }
        }

            while (((reader.position() - nodesStart) & ((1 << mult) - 1)) != 0)
                    reader.get();

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

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

                DirectionReader dirFetcher = new DirectionReader(reader);

            int length = 0;
                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 newnet = arc.newNet();
                boolean sign = arc.isSign();

            // 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 || newnet)
                                lastNet = reader.get() & 0xff;

                        arc.setLocalNet(lastNet);
                        RouteCenter.TableA net = center.getNet(lastNet);
                        if (net != null) {
                                arc.setTableA(net);
                        } 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
            }

                // There is a new arc direction when:
                // 1. it's the first one in a node
                // 2. when the newnet bit is set
                // 3. when the sign changes
                //
                // This is reason to read a new direction and to start a new length.
                if (first || newnet || sign != lastSign)
                        length = 0;

            // length should be in units of 16 feet. It is cumulative since the last change
                length += len;
            arc.setDistance(length);
                arc.setPartialDistance(len);

                arc.setDirection(dirFetcher.fetchDirection(first, newnet, sign));

                        if (curve) {
                int curvea = reader.get() & 0xff;
                                arc.setCurveA(curvea);
                                if ((curvea & 0xe0) == 0)
                                        arc.setCurveB(reader.get() & 0xff);
            }

            node.addArc(arc);

                lastSign = sign;
            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();
        }
    }

        /**
         * Read the NOD2 information for a road.
         *
         * @param offset The offset into Nod 2.
         */

        public Nod2Record getNod2(int offset) {
                Nod2Record info = roads.get(offset);
                if (info != null)
                        return info;

                info = new Nod2Record(offset);
                roads.put(offset, info);

                int start = ((NODHeader) getHeader()).getRoadSection().getPosition();
                reader.position(start + offset);

                byte flags = reader.get();
                info.setFlags(flags);

                int offsetNod1 = reader.getu3();
                //System.out.printf("nod2=%x, nod1=%x size %d\n", offset, offsetNod1, allNodes.size());
                info.setNode(getNode(offsetNod1));

                int bitLen = reader.getChar();
                int nstream = (bitLen + 7) / 8;
                byte[] bytes = reader.get(nstream);
                info.setBitStream(bytes);
                info.setBitStreamLen(bitLen);

                if((flags & 0x80) != 0) {
                        int extraFormat = reader.get();
                        if((extraFormat & 0x01) != 0) {
                                int len = reader.get() & 0xff;
                                reader.get(len >> 1);
                        }
                        if((extraFormat & 0x02) != 0) {
                                int len = reader.get() & 0xff;
                                reader.get(len >> 1);
                        }
                        if ((extraFormat & 0xc0) == 0) {
                                if ((extraFormat & 0x04) != 0) {
                                        reader.get();
                                }
                                if ((extraFormat & 0x08) != 0) {
                                        reader.getChar();
                                }
                        } else {
                                int len = reader.get() & 0xff;
                                reader.get(len >> 1);
                        }
                }

                info.setNext(reader.position() - start);
                return info;
        }
}