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