Subversion Repositories display

Rev

Rev 370 | 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.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.ImgFileReader;

import static test.util.AccessBits.*;
import static test.util.RestrictionBits.CLASS;
import static test.util.RestrictionBits.SPEED;

/**
 * The tables in the NOD1 section.
 *
 * @author Steve Ratcliffe
 */

public class RouteCenter {
        private Coord coord;
        private final long offset;
    private long next;   // The next node starts at this offset.

        private final List<TableA> tableA = new ArrayList<>();
        private final int tableARecordLen;
        private final List<TableB> tableB = new ArrayList<>();
    private final TableC tableC = new TableC();

    private final Map<Integer, RouteNode> nodes = new LinkedHashMap<>();
    private int index;

        public static void setHasExtraZeros(Boolean hasExtraZeros) {
                RouteCenter.hasExtraZeros = hasExtraZeros;
        }

        private static Boolean hasExtraZeros;

        public RouteCenter(long start, int tableARecordLen) {
            this.tableARecordLen = tableARecordLen;
        offset = start;
    }

    public void readTable(ImgFileReader r, int nodesStart, long start) {
        r.position(nodesStart + start);
        readTables(r);
    }

    public long getOffset() {
        return offset;
    }

    public long getNext() {
        return next;
    }

    public int getCPointerSize() {
        return tableC.getPointerSize();
    }

    public Coord getCoord() {
        return coord;
    }
    public int getLat() {
        return coord.getLatitude();
    }
    public int getLon() {
        return coord.getLongitude();
    }

    /**
     * Read in all the node table information for a route center.
     *
     * Each route center has a location, a table of pointers to NET, a table of pointers to
     * NOD1 (for links to other route centers) and a table of route restrictions.
     *
     * @param r The img file reader, which should be positioned at the start of the tables.
     *          At the end it will be just after, ready to read the next node.
     */

    private void readTables(ImgFileReader r) {
        int restrformat = r.get() & 0xff;

        int lon = latLongField(r);
        int lat = latLongField(r);
        coord = new Coord(lat, lon);

        int nTabA = r.get() & 0xff;
        int nTabB = r.get() & 0xff;

        // Now do 'Table A' (segments)
        for (int i = 0; i < nTabA; i++) {
            TableA a = readTableARecord(r);
            tableA.add(a);
        }

        // 'Table B' (inter-section pointers)
        for (int i = 0; i < nTabB; i++) {
            TableB b = new TableB();
            b.setNodOffset(r.get3());
            tableB.add(b);
        }

        // 'Table C' (restrictions)
        int size = 0;
        if ((restrformat & 1) != 0) {
            size = r.get() & 0xff;
        } else if ((restrformat & 2) != 0) {
            size = r.getChar() & 0xffff;
        }

        tableC.setRaw(r.get(size));

        if ((restrformat & 4) != 0) tableC.unk1 = r.get();

        if ((restrformat & 8) != 0) tableC.unpaved = r.get();

        if ((restrformat & 0x10) != 0) tableC.ferry = r.get();

            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 = r.position();
                    byte b = r.get();
                    r.position(pos);

                    // Is there a node at pos? If so then there are not extra bytes.

                    // 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) {
                                    System.out.println("setting extra zero option");
                                    hasExtraZeros = true;
                            } else {
                                    hasExtraZeros = false;
                            }
                    }
                    if (hasExtraZeros) {
                            r.get();
                    }
            }

        next = r.position();
    }

    private TableA readTableARecord(ImgFileReader r) {
        TableA a = new TableA();

        // Get the NET pointer.
        int off = r.get3();

        // top 2 bits of net pointer are access bits
        int access = off & 0xc0_00_00;

        off &= 0x3fffff;
        a.setNetOffset(off);

        int paramA = r.get() & 0xff;
        int paramB = 0;
            if (tableARecordLen >= 5)
                    paramB = r.get() & 0xff;

            a.setClass((paramA & CLASS) >> 4);
        a.setSpeed((paramA & SPEED));
        a.setAccess(access | paramB);
        return a;
    }

    private int latLongField(ImgFileReader r) {
        int l = r.get3();
        if ((l & 0x800000) != 0)
            l |= 0xff000000;

        return l;
    }

    public void addNode(RouteNode node) {
        nodes.put(node.getOffset(), node);
    }

    public void setIndex(int index) {
        this.index = index;
    }

    public int getIndex() {
        return index;
    }

    public Map<Integer, RouteNode> nodes() {
        return nodes;
    }

    public int linkId(int idx) {
        if (idx < 0 || idx >= tableB.size())
            return -1;

        TableB b = tableB.get(idx);
        return b.getNodOffset();
    }

    public TableA getNet(int idx) {
        if (idx < 0 || idx >= tableA.size())
            return null;
        TableA a = tableA.get(idx);
        return a;
    }

    public void resort() {
        List<RouteNode> nlist = new ArrayList<>(nodes.values());
        Collections.sort(nlist, new Comparator<RouteNode>() {
            public int compare(RouteNode o1, RouteNode o2) {
                Integer i1 = o1.getOffset();
                Integer i2 = o2.getOffset();
                return i1.compareTo(i2);
            }
        });
                nodes.clear();

                for (RouteNode node : nlist)
                        nodes.put(node.getOffset(), node);
    }

    /**
     * A table A record contains a link to NET1 and access restriction information.
     */

    class TableA {
        private int netOffset;
        private int roadClass;
        private int speed;
        private int access;

        public void setNetOffset(int netOffset) {
            this.netOffset = netOffset;
        }

        public int getNetOffset() {
            return netOffset;
        }

        public void setClass(int aClass) {
            this.roadClass = aClass;
        }

        public int getRoadClass() {
            return roadClass;
        }

        public void setRoadClass(int roadClass) {
            this.roadClass = roadClass;
        }

        public void setSpeed(int speed) {
            this.speed = speed;
        }

        public int getSpeed() {
            return speed;
        }

        public void setAccess(int access) {
            this.access = access;
        }

        public int getAccess() {
            return access;
        }

        public String noAccessString() {
            StringBuilder sb = new StringBuilder();

            if ((access & NOEMERGENCY) != 0) sb.append("emergency,");
            if ((access & NODELIVERY) != 0) sb.append("delivery,");
            if ((access & NOCAR) != 0) sb.append("car,");
            if ((access & NOBUS) != 0) sb.append("bus,");
            if ((access & NOTAXI) != 0) sb.append("taxi,");
            if ((access & NOCARPOOL) != 0) sb.append("carpool,");
            if ((access & NOFOOT) != 0) sb.append("foot,");
            if ((access & NOBIKE) != 0) sb.append("bike,");
            if ((access & NOTRUCK) != 0) sb.append("truck,");

            // Trim final comma, if there is anything.
            if (sb.length() > 1)
                sb.setLength(sb.length() - 1);

            return sb.toString();
        }
    }

    /**
     * Table B consists of pointers into NOD1.  Used for jumps between different route centres.
     */

    class TableB {
        private int nodOffset;

        public void setNodOffset(int nodOffset) {
            this.nodOffset = nodOffset;
        }

        public int getNodOffset() {
            return nodOffset;
        }
    }

    class TableC {
        // The raw value of table C (restrictions)
        private byte[] raw;
        private int size;
        public byte unk1;
        public byte unpaved;
        public byte ferry;
        public byte unk2;


        public void setRaw(byte[] raw) {
            this.raw = raw;
                size = raw.length;
        }
        public int getPointerSize() {
                if (size > 127)
                return 2;
            else
                return 1;
        }

    }
}