Subversion Repositories display

Rev

Rev 539 | 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.Section;
import uk.me.parabola.imgfmt.app.net.NODHeader;
import uk.me.parabola.util.EnhancedProperties;

import test.display.check.Log;

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.4;

        private final int nodesStart;
    private final int nodesLen;
    private final int align;
        private final int ptrShift;
        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 final int[] classBoundaries;
        private Map<Integer, Nod2Record> roads = new HashMap<>();

        private int indexIdSize;

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

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

        header.readHeader(r);
                if (header.getHeaderLength() >= 127)
                        RouteCenter.setHasExtraZeros(false);
        nodesStart = header.getNodeStart();
        nodesLen = header.getNodeSize();
        align = header.getAlign();
                ptrShift = header.getMult1();
            tableARecordLen = header.getTableARecordLen();
                int flags = header.getFlags();
                classBoundaries = header.getClassBoundaries();

                // 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) & 0x7;
                distanceMult = (1 << mult);
                indexIdSize = header.getIndexIdSize();
        }

        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.get1u();
            long end = calcTablePosition(start, low);

                RouteCenter table = new RouteCenter(end, tableARecordLen, ptrShift, nodesStart);
            table.readTable(reader, 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());
                        while (((reader.position() - nodesStart) & ((1 << ptrShift) - 1)) != 0)
                                reader.get();
            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);
                int nodeClass = 0;

                // Find out what class section we are in.  If the file does not have class sections
                // then this will always be zero.
                if (classBoundaries[0] != 0) {
                        for (int c = 4; c >= 0; c--) {
                                if (classBoundaries[c] <= start) {
                                        nodeClass = c + 1;
                                        break;
                                }
                        }
                }

        while (reader.position() < end - 5) {
            RouteNode node = readNode(table);
                if (node == null)
                        return;

                        node.setNodeClass(nodeClass);
            table.addNode(node);
                        allNodes.put(node.getOffset(), 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.
         * @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) {
                while (((reader.position() - nodesStart) & ((1 << ptrShift) - 1)) != 0)
                        reader.get();

                int offset = (int) reader.position() - nodesStart;

                int low = reader.get1u();

        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
                return null;
            }
        }

                RouteNode node = new RouteNode(center, offset);

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

                int flags = reader.get1u();
            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;
                        int ptrSize = center.getCPointerSize();
                        int topbit = 0x80 << (8 * (ptrSize - 1));
                        while (!done) {
                                int off = reader.getNu(ptrSize);
                                done = (off & topbit) != 0;
                                int val = off & (topbit-1);
                                RouteCenter.Restriction restr = center.getRestriction(val);
                                node.addRestriction(restr);
                        }
                }

                while (((reader.position() - nodesStart) & ((1 << ptrShift) - 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.get1u();

            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.get1u();

            // 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.get1u();
                }

                // 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.get1u();
                int nodeoff = ((intro1 << 8 | intro2) & 0x3fff) << ptrShift;

                // Construct a pointer to another node, signed 16 bit quantity
                if ((nodeoff & (0x2000 << ptrShift)) != 0)
                    nodeoff |= 0xffffc000 << ptrShift;

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

                        if (first || newnet)
                                lastNet = reader.get1u();

                        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.get1u();
                if ((len1 & 0x80) == 0x80) {
                    if ((len1 & 0x40) == 0x40) {
                        //d.item().addText("22 bit length + curve");
                        int len2 = reader.get2u();
                        len = (len1 & 0x3f) | (len2 << 6); // 6+16 bits
                        curve = true;
                    } else {
                        //d.item().addText("14 bit length");
                        int len2 = reader.get1u();
                        len = (len1 & 0x3f) | (len2 << 6); // 6+8 bits
                        curve = false;
                    }
                } else {
                    //d.item().addText("15 bit length + curve");
                    int len2 = reader.get1u();
                    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.get1u(); // 2+8 bits
            }

                // There is a new arc direction when:
                // 1. it's the first one in a node
                // 2. when the new net 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.get1u();
                                arc.setCurveA(curvea);
                                if ((curvea & 0xe0) == 0)
                                        arc.setCurveB(reader.get1u());
            }

            node.addArc(arc);

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

        public List<RouteNode> readBoundaryNodes() {
                List<RouteNode> list = new ArrayList<>();

                NODHeader header = (NODHeader) getHeader();
                Section section = header.getBoundarySection();

                reader.position(section.getPosition());
                while (reader.position() < section.getEndPos()) {
                        int lng = reader.get3u();
                        if ((lng & 0x800000) != 0)
                                lng += 0xff000000;
                        int lat = reader.get3u();
                        if ((lat & 0x800000) != 0)
                                lat += 0xff000000;
                        Coord coord = new Coord(lat, lng);
                        int off = reader.get3u() << ptrShift;
                        RouteNode node = getNode(off);
                        if (node == null) {
                                Log.error("Node:%x: on boundary, does not exist", off);
                                continue;
                        }
                        if (!coord.equals(node.getCoord()))
                                Log.error("Boundary node coords not equal to its node boundary=%s node=%s", coord, node.getCoord());
                        list.add(node);
                }
                return list;
        }

        public List<RouteNode> readHighClassBoundaryNodes() {
                List<RouteNode> list = new ArrayList<>();

                NODHeader header = (NODHeader) getHeader();
                Section section = header.getHighClassBoundary();

                reader.position(section.getPosition());
                while (reader.position() < section.getEndPos()) {
                        reader.get3u();
                        reader.get3u();
                        int off = reader.get3u() << ptrShift;
                        RouteNode node = getNode(off);
                        if (node == null) {
                                Log.error("Node:%x: on boundary, does not exist", off);
                                continue;
                        }
                        list.add(node);
                }
                return list;
        }

        private Coord readPosition(RouteCenter center, boolean bigoff) {
                short longoff;
                short latoff;
                if (bigoff) {
                        longoff = (short) reader.get2u();
                        latoff = (short) reader.get2u();
                } else {
                        int latlon = reader.get3s();

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

        /**
         * 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.get3u() << ptrShift;
                //System.out.printf("nod2=%x, nod1=%x size %d\n", offset, offsetNod1, allNodes.size());
                info.setNode(getNode(offsetNod1));

                int bitLen = reader.get2u();
                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.get1u();
                                reader.get(len >> 1);
                        }
                        if((extraFormat & 0x02) != 0) {
                                int len = reader.get1u();
                                reader.get(len >> 1);
                        }
                        if ((extraFormat & 0xc0) == 0) {
                                if ((extraFormat & 0x04) != 0) {
                                        reader.get();
                                }
                                if ((extraFormat & 0x08) != 0) {
                                        reader.get2u();
                                }
                        } else {
                                int len = reader.get1u();
                                reader.get(len >> 1);
                        }
                }

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

        public boolean hasClassSections() {
                return classBoundaries[0] != 0;
        }
       
        /**
         * @return the indexIdSize
         */

        public int getIndexIdSize() {
                return indexIdSize;
        }

}