Subversion Repositories display

Rev

Rev 328 | 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.check;

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.Utils;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.Label;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;

import test.elements.Line;
import test.files.NodFile;
import test.files.RouteArc;
import test.files.RouteCenter;
import test.files.RouteNode;

import static test.files.RouteArc.directionFromDegrees;

/**
 * Check a NOD file for consistency and print out various things that might be useful in debugging.
 *
 * @author Steve Ratcliffe
 */

public class NodCheck extends CommonCheck {
    private NodFile nodFile;

    private final Map<Integer, List<Line>> roadMap = new HashMap<>();

    protected void print() {

                openLbl();

                openTre();

                openNet();
                openRgn();
                rgn.setNetFile(net);

        initRoads();

        reader.position(0);
        nodFile = new NodFile(reader);
        List<RouteCenter> routeCenters = nodFile.readNodeCenters();

        nodFile.fixMissingNodes();
        check(routeCenters);
        }

    private void initRoads() {
        Subdivision[] subdivisions = tre.subdivForLevel(0);

                for (Subdivision sub : subdivisions) {
                        List<Line> lines = rgn.linesForSubdiv(sub);

                        for (Line line : lines) {
                int net1Offset = line.getNet1Offset();

                // Take this to be a road if it has a NET1 Offset
                if (net1Offset > 0) {
                    List<Line> larray = roadMap.get(net1Offset);
                    if (larray == null) {
                        larray = new ArrayList<>();
                        roadMap.put(net1Offset, larray);
                    }
                    larray.add(line);
                }
            }
                }
    }

    private void check(List<RouteCenter> routeCenters) {
        for (RouteCenter center : routeCenters) {
            info("Route center %d: table at %#x, loc(%s)", center.getIndex(), center.getOffset(),
                    fmtCoord(center.getCoord()));

            checkCenter(center);
        }
    }

    private void checkCenter(RouteCenter center) {
        findMoreNodes(center);
        findMoreNodes(center);

        RouteNode last = null;
        int count = 0;
        for (RouteNode node : center.nodes().values()) {
            if (node.isBad()) {
                error(" Node:%x: BAD", count, node.getOffset());
                count++;
                continue;
            }

                checkNode(node, last);

                checkArcs(node);

            last = node;
            count++;
        }
    }

        private void checkNode(RouteNode node, RouteNode previous) {

                // Check for gap or overlap with previous
                if (previous != null && node.getOffset() > previous.getNextStart()) {
                        error("Node:%x: gap %d from last (%x)", node.getOffset(),
                                        node.getOffset() - previous.getNextStart(),
                                        previous.getNextStart());
                } else if (previous != null && node.getOffset() < previous.getNextStart()) {
                        error("Node:%x: overlap with last (%x)", node.getOffset(), previous.getNextStart());
                }

                // Display a summary
                info(" %x:Node%s: (%s) flags:%s, arcs=%d", node.getOffset(), node.isProblem() ? "!" : "",
                                fmtCoord(node.getCoord()), node.flagString(), node.getArcs().size());
        }

        /**
     * Go through the nodes we have already and add the linked node if we don't have it.
     *
     * @param center All the nodes we know about.
     */

    private void findMoreNodes(RouteCenter center) {
        List<Integer> needed = new ArrayList<>();

        for (RouteNode node : center.nodes().values()) {
            if (node.isBad())
                continue;

            for (RouteArc arc : node.getArcs()) {
                int off = arc.getLink();
                if (nodFile.getNode(off) == null)
                    needed.add(off);
            }
        }

        for (Integer i : needed) {
            if (nodFile.getNode(i) == null)
                nodFile.fixNode(i);
        }
        nodFile.resort();
    }

    private boolean matchCoord(Coord co, Coord nodePos) {
        if (Math.abs(co.getLatitude()-nodePos.getLatitude()) <= 1
                && Math.abs(co.getLongitude() - nodePos.getLongitude()) <= 1)
            return true;
        return false;
    }

    private void checkArcs(RouteNode node) {

            int maxDestclass = 0;
        int count = 0;
        for (RouteArc arc : node.getArcs()) {
            if (arc.isBad()) {
                info("   Arc %d[%x]: BAD", count, arc.getOffset());
                count++;
                continue;
            }

                if (arc.getDestclass() > maxDestclass)
                        maxDestclass = arc.getDestclass();

                findLinesForArc(node, arc);

                // Display info about the arc
                String roadInfo = "NONE";
            Integer netOffset = arc.getNetOffset();
            if (netOffset != null) {
                int labelOffset = net.getLabelOffset(netOffset);
                Label label = lbl.fetchLabel(labelOffset);

                roadInfo = String.format("%d:%x:%s:%d/%d", arc.getLocalNet(), arc.getNetOffset(),
                        Label.stripGarminCodes(label.getText()),
                        arc.getRoadClass(), arc.getSpeed());
                                if (arc.getLine() == null)
                                        roadInfo += "|BAD";
                        }

            info("   Arc %d[%x]: %s, [%s] ->%x%s, %dm %s%s",
                    count, arc.getOffset(), arc.alt6String(), roadInfo,
                    arc.getLink(), arc.isLongLink() ? "(LL)" : "",
                    arc.getDistance(), arc.hasDirection()? String.format("%.2fdeg", arc.getDirection()): "ND",
                    arc.isLast()? " LAST": "");

                // Check the target node, make sure it exists and is valid
            int link = arc.getLink();
            RouteNode other = nodFile.getNode(link);
            if (other == null) {
                error("Node:%x/%d: node target not read %x", node.getOffset(), arc.getIndex(), link);
            } else if (other.isBad()) {
                error("Node:%x/%d: node target bad %x", node.getOffset(), arc.getIndex(), link);
            } else {
                // Compare actual distance and direction with this other node from this arc
                compareActual(arc, node, other);

                    checkReverse(arc, other);
            }

            count++;
        }

            if (maxDestclass != node.getDestclass()) {
                    error("node:%x: class on node %d, max destclass of arcs %d", node.getOffset(),
                                    node.getDestclass(), maxDestclass);
            }
    }

        /**
         * Check that the reverse arc.
         * @param arc The forward arc, from the node under test.
         * @param other The target node that this arc leads towards.
         */

        private void checkReverse(RouteArc arc, RouteNode other) {
                // Find the arc from the other node that points back to us.
                // There is always a reverse arc unless the arc is really a link to a
                // higher road class.
                boolean found = false;
                for (RouteArc rarc : other.getArcs()) {
                    if (arc.isBad())
                        continue;
                    int rlink = rarc.getLink();

                    RouteNode rother = nodFile.getNode(rlink);
                    if (rother != null && arc.getNode() == rother) {
                        // This is the arc that links back to this node - the 'reverse arc'
                        trace("      revarc %d: %s", rarc.getIndex(), rarc.alt6String());

                        compareToReverseArc(arc, rarc);
                            found = true;
                        break;
                    }
                }

                if (!found && arc.getNetOffset() != null) {
                        error("node:%x/%d: expected to find reverse arc",
                                        arc.getNode().getOffset(), arc.getIndex());
                }
        }

        /**
         * Find the road lines that belong to this arc.
         *
         * To be proper we should go via NET and get the subdiv/line pairs for the lowest level.
         *
         * Here we just find lines from RGN that have a matching NET offset.
         *
         * @param node The node that the arc belongs to.
         * @param arc The arc.
         */

        private void findLinesForArc(RouteNode node, RouteArc arc) {
                Coord nodePos = node.getCoord();

                Integer netOffset = arc.getNetOffset();
                if (netOffset == null)
                        return;

                List<Line> lines = roadMap.get(netOffset);
                if (lines != null) {
                        if (!arc.isSign())
                                Collections.reverse(lines);

                        out:
                        for (Line line : lines) {
                                List<Coord> coords = line.getCoords();
                                int count = 0;
                                for (Coord co : coords) {
                                        if (co.equals(nodePos) || matchCoord(co, nodePos)) {
                                                if (arc.isSign() && count == coords.size() - 1)
                                                        continue ;
                                                if (!arc.isSign() && count == 0)
                                                        continue;
                                                //System.out.printf("found in line pos=%d, end=%d, sign=%s\n", count, coords.size()-1, arc.isSign());
                                                arc.setLine(line);
                                                arc.setLinePos(count);
                                                break out;
                                        }
                                        count++;
                                }
                        }
                }
        }

    private void compareToReverseArc(RouteArc arc, RouteArc rarc) {
        // It is believed that the signs should be different
        if (!(arc.isSign() ^ rarc.isSign()))
            error("Node:%x/%d: reverse arc has same sign", arc.getNode().getOffset(), arc.getIndex());

        // Distance should be similar. Perhaps not if there are two ways of getting between nodes - need to look into this.
        int d1 = arc.getDistance();
        int d2 = rarc.getDistance();
        if (d1 != d2)
            error("Node:%x/%d: distance along reverse arc different %d/%d", arc.getNode().getOffset(), arc.getIndex(), d1, d2);
    }

        private void compareActual(RouteArc arc, RouteNode node, RouteNode other) {
                Coord c1 = node.getCoord();
                Coord c2 = other.getCoord();
                compareActualDirection(arc, c1, c2);
                compareActualDistance(arc, c1, c2);
        }

    private void compareActualDirection(RouteArc arc, Coord c1, Coord c2) {
        double actualStraighLine = c1.bearingTo(c2);
        Double direction = arc.getDirection();
        Double actualInitial = arc.getActualInitialBearing();

        trace("      direction %f (%x), actual: initial=%f(%02x), direct=%f(%02x)",
                direction, arc.getRawDirection(), actualInitial, actualInitial!=null ? directionFromDegrees( actualInitial) : 0,
                                actualStraighLine, directionFromDegrees(actualStraighLine));

            if (actualInitial == null)
                    actualInitial = actualStraighLine;
            int intInitial = directionFromDegrees(actualInitial);

            if (compareAngle(arc.getRawDirection(), intInitial))
            error("Node:%x/%d: direction %f(%02x), actual %f(%02x)", arc.getNode().getOffset(), arc.getIndex(),
                    direction, arc.getRawDirection(),
                            actualInitial, intInitial);
    }

        private boolean compareAngle(int a1, int a2) {
                int allowed = 3;  // Allowed difference before we complain
                if ((a1 & 0x0f) == 0)
                        allowed = 16;
//              System.out.printf("comp %d %d %d / %d\n", a1, a2, allowed, Math.abs(a1-a2));
                return Math.abs(a1 - a2) > allowed;
        }

        /**
     * Compares the straight line distance to the value in the arc.
     *
     * The arc value is often larger, so may try calculating road length, not straight line.
     *
     * @param arc The starting arc.
     * @param c1 The starting coord.
     * @param c2 The end arc coord.
     */

    private void compareActualDistance(RouteArc arc, Coord c1, Coord c2) {
        int d1 = arc.getDistance();
        double straightLineDistance = c1.distance(c2);

        int min = arc.getDistanceLow();
        int max = arc.getDistanceHigh();
        trace("      distance %d, actual %f %s", d1, straightLineDistance, (max < straightLineDistance)? "?short":"");

            // If the straight line distance is greater than our distance that could be a problem,
            // since the path distance should always be greater
        if (straightLineDistance > max)
            error("Node:%x/%d: distance %d, actual %f", arc.getNode().getOffset(), arc.getIndex(),
                    d1, straightLineDistance);

            if (d1 > straightLineDistance * 2)
                    error("Node:%x/%d: distance %d, actual %f", arc.getNode().getOffset(), arc.getIndex(),
                                    d1, straightLineDistance);
    }

    private String fmtCoord(Coord co) {
        return String.format("%.3f,%.3f",
                Utils.toDegrees(co.getLatitude()), Utils.toDegrees(co.getLongitude()));
    }

    public static void main(String[] args) {
                runMain(NodCheck.class, "NOD", args);
        }
}