/*
* 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.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.net.NODHeader;
import test.display.check.Log;
import test.elements.Line;
import test.files.Nod2Record;
import test.files.NodeLocator;
import test.files.RoadData;
import test.files.RouteArc;
import test.files.RouteCenter;
import test.files.RouteNode;
import test.files.Segment;
import static test.files.RouteArc.directionFromDegrees;
import static test.files.RouteCenter.setHasExtraZeros;
/**
* 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
{
protected void print
() {
openLbl
();
openTre
();
openNet
();
openRgn
();
openNod
();
reader.
position(0);
List<RouteCenter
> routeCenters = nod.
readNodeCenters();
initRoads
();
checkNod2
();
check
(routeCenters
);
if (nod.
hasClassSections())
checkBoundary4
();
System.
out.
printf("Distance %f\n", totMeters / totUnits
);
}
private void checkNod2
() {
int end =
((NODHeader
) nod.
getHeader()).
getRoadSection().
getSize();
int pos =
0;
do {
Nod2Record nod2 = nod.
getNod2(pos
);
byte[] bitStream = nod2.
getBitStream();
RoadData road = nod2.
getRoadData();
if (road ==
null) {
if (nod2.
getNode() ==
null)
error
("could not find road for nod2=%x, nor the node", pos
);
else
error
("could not find road for nod2=%x node=%x", pos, nod2.
getNode().
getOffset());
pos =
(int) nod2.
getNext();
continue;
}
BitSet bset =
BitSet.
valueOf(bitStream
);
int nInStream = nod2.
getBitStreamLen();
info
("Road %x: node=%x, %s", road.
getOffset(), nod2.
getNode().
getOffset(),
bitString
(bset, nInStream
));
matchRoadsNodes
(road, nod2
);
pos =
(int) nod2.
getNext();
} while (pos
< end
&& pos
!=
0);
}
private String bitString
(BitSet bset,
int size
) {
StringBuilder sb =
new StringBuilder();
for (int i =
0; i
< size
; i++
)
sb.
append(bset.
get(i
) ? "1" :
"0");
return sb.
toString();
}
/**
* Match up routing nodes to points on the road.
*
* A road consists of one or more line segments. Each line has a number of points.
* Some of those points have the 'node-flag' set. Some of those flagged points are
* routing nodes and some are only for house number ranges (and perhaps other reasons).
*
* The bitstream in nod 2 tells you which kind of node you have. The bit is set
* to 1 for a routing node and zero otherwise.
*
* Note that the nod 2 bitstream also covers the first and last points of a road. Its best to
* think of the first and last points in a road having the node-flag set implicitly.
*
* @param road The road to examine.
* @param nod2 The NOD 2 record that the road refers to. A road can only point to one
* NOD 2 record.
*/
private void matchRoadsNodes
(RoadData road, Nod2Record nod2
) {
BitSet bset =
BitSet.
valueOf(nod2.
getBitStream());
List<RouteNode
> routeNodes = getRouteNodes
(road, nod2.
getNode());
if (routeNodes.
size() != bset.
cardinality()) {
error
("number of route nodes (%d) not equal to set bits %s", routeNodes.
size(),
bset.
toString());
// At the moment, don't try to set the nodes up
return;
}
List<Segment> segments = road.
getSegments();
int nseg = segments.
size();
int rnod =
0;
int rcount =
0;
int npoint =
0;
double length =
0;
Coord lastCoord =
null;
for (int s =
0; s
< nseg
; s++
) {
Segment seg = segments.
get(s
);
Line line = seg.
getLine();
List<Coord
> coords = line.
getCoords();
int ncoord = coords.
size();
for (int c =
0; c
< ncoord
; c++
) {
Coord co = coords.
get(c
);
if (lastCoord
!=
null)
length += lastCoord.
distance(co
);
lastCoord = co
;
// This counts as a road node if it is the first or last in the road, or it has
// the node-flag set.
boolean last = c == ncoord -
1 && s == nseg -
1;
if (co.
getId() > 0 || npoint ==
0 || last
) {
if (s
> 0 && c ==
0)
continue;
// See if this road node is also a routing node
if (bset.
get(rnod
)) {
RouteNode node = routeNodes.
get(rcount
);
if (!equalCoord
(node.
getCoord(), co
))
error
("coords dont match n=%s, c=%s", node.
getCoord(), co
);
NodeLocator locator =
new NodeLocator
(node
);
locator.
setIndex(rcount
);
locator.
setSegmentNumber(s
);
locator.
setCoordNumber(c
);
locator.
setCoordsInSegment(ncoord
);
locator.
setLength((int) Math.
round(length
));
road.
addNodeLocation(locator
);
trace
(" Node:%x %s", node.
getOffset(), fmtCoord
(co
));
node.
updateDestClass(nod2.
getRoadClass());
node.
addRoad(road
);
rcount++
;
}
rnod++
;
}
npoint++
;
}
}
if (rcount
< routeNodes.
size()) {
for (int i = rcount
; i
< routeNodes.
size(); i++
) {
RouteNode node = routeNodes.
get(i
);
StringBuilder sb =
new StringBuilder();
for (Segment seg : road.
getSegments()) {
List<Coord
> coords = seg.
getLine().
getCoords();
sb.
append(coords
);
}
error
("missing node %x %s %s", node.
getOffset(), node.
getCoord(),
sb.
toString());
}
}
}
/**
* Are coords equal to withing a margin of error.
*
* Often the node coords are one out compared to the corresponding coordinate. Possible
* that we are missing something, as you might expect them to be the same.
*
* @param c1 First coordinate to compare.
* @param c2 Second coordinate to compare.
* @return True if they are equal to within one unit.
*/
public boolean equalCoord
(Coord c1, Coord c2
) {
if (Math.
abs(c1.
getLatitude() - c2.
getLatitude()) < 2
&& Math.
abs(c1.
getLongitude() - c2.
getLongitude()) < 2)
return true;
else
return false;
}
/**
* Get a list of routing nodes for a road. We do this by walking along the forward arcs
* for the road.
*
* We assume that the first arc in the forward direction is always a link to the next
* node in this routine. That is believed to be how it is meant to be.
*
* @param road The road to examine.
* @param first The first (routing) node on the road as taken from nod 2.
* @return A list of all the routing nodes found for the road. They are in order from
* the first.
*/
private List<RouteNode
> getRouteNodes
(RoadData road, RouteNode first
) {
List<RouteNode
> nodes =
new ArrayList<>();
nodes.
add(first
);
for (int i =
0; i
< road.
getNod2().
getBitStreamLen(); i++
) {
for (RouteArc arc : first.
getArcs()) {
if (arc.
isSign() && arc.
getNetOffset() == road.
getOffset()) {
RouteNode next = nod.
getNode(arc.
getLink());
nodes.
add(next
);
first = next
;
break;
}
}
}
return nodes
;
}
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
) {
// Check for repeated entries in Table A. A waste of space, rather than an error as such.
Set<Integer> roadIds =
new HashSet<>();
for (RouteCenter.
TableA aRecord : center.
getTableA()) {
if (!roadIds.
add(aRecord.
getNetOffset()))
error
("Center %x: Repeated table A entry %x", center.
getOffset(), aRecord.
getNetOffset());
}
// Check for repeated entries in Table B. A waste of space, rather than an error as such.
Set<Integer> nodeIds =
new HashSet<>();
for (RouteCenter.
TableB bRecord : center.
getTableB()) {
if (!nodeIds.
add(bRecord.
getNodOffset()))
error
("Center %x: Repeated table B node entry %x", center.
getOffset(), bRecord.
getNodOffset());
}
RouteNode last =
null;
double lastNodeSortPosition =
Double.
NEGATIVE_INFINITY;
for (RouteNode node : center.
nodes().
values()) {
checkNode
(node, last
);
last = node
;
if (node.
isBad())
continue;
double nodeSortPosition = node.
getCoord().
getLonDegrees();
if (nodeSortPosition
< lastNodeSortPosition
) {
error
("Node %x: not sorted on longitude %f, %f", node.
getOffset(),
lastNodeSortPosition, nodeSortPosition
);
}
lastNodeSortPosition = nodeSortPosition
;
if (node.
hasArcs()) {
checkArcs
(node
);
roadDiagrams
(node
);
}
int rc = predictGroupClass
(node
);
if (rc == node.
getNodeClass())
trace
(" Node:%x: node class %d, correct %d %s", node.
getOffset(), node.
getNodeClass(), rc, node.
getClasses());
else
trace
(" Node:%x: node class %d, predicted %d %s", node.
getOffset(), node.
getNodeClass(), rc, node.
getClasses());
if (node.
hasRestrictions())
checkRestrictions
(node
);
}
List<RouteCenter.
Restriction> restrictions = center.
getRestrictions();
info
("Restrictions for center %x", center.
getOffset());
for (RouteCenter.
Restriction restr : restrictions
) {
boolean valid =
true;
List<RouteNode
> nodes =
new ArrayList<>();
for (int noff : restr.
getNodeOffsets()) {
RouteNode node = nod.
getNode(noff
);
if (node ==
null) {
valid =
false;
error
("Center:%x:Restriction:%x: invalid node %x", center.
getOffset(), restr.
getOffset(), noff
);
} else {
trace
(" Restriction:%x: node %x", restr.
getOffset(), noff
);
}
nodes.
add(node
);
}
List<Integer> roadOffsets = restr.
getRoadOffsets();
for (int n =
0; n
< roadOffsets.
size(); n++
) {
int roff = roadOffsets.
get(n
);
RoadData road = net.
getRoad(roff
);
if (road ==
null) {
error
("Restriction invalid road %x", roff
);
valid =
false;
} else {
trace
(" Restriction:%x: road %x", restr.
getOffset(), roff
);
}
// If all nodes were not valid, no point checking further
if (!valid
)
continue;
RouteNode n1 = nodes.
get(n
);
NodeLocator nl1 = road.
locateNode(n1
);
RouteNode n2 = nodes.
get(n +
1);
NodeLocator nl2 = road.
locateNode(n2
);
if (nl1 ==
null)
error
("Restriction: node %x not on road %x", n1.
getOffset(), roff
);
if (nl2 ==
null)
error
("Restriction: node %x not on road %x", n2.
getOffset(), roff
);
}
}
}
private void checkRestrictions
(RouteNode node
) {
List<RouteCenter.
Restriction> restrictions = node.
getRestrictions();
for (RouteCenter.
Restriction restr : restrictions
) {
if (restr ==
null) {
error
("Restriction not found");
continue;
}
//List<Integer> roadOffsets = restr.getRoadOffsets();
//for (int roff : roadOffsets) {
// boolean found = false;
// List<RoadData> roads = node.getRoads();
// for (RoadData r : roads) {
// if (r.getOffset() == roff) {
// found = true;
// break;
// }
// }
// if (!found)
// error("Node:%x:Restrict:%x: not found on road %x", node.getOffset(), restr.getOffset(), roff);
//}
}
}
private static long getNodeSortPosition
(RouteNode node
) {
Coord coord = node.
getCoord();
long longitude =
(long) coord.
getLongitude() + Utils.
toMapUnit(180);
long lat =
(long) coord.
getLatitude() + Utils.
toMapUnit(180);
return (longitude
<< 28) + lat
;
}
private void roadDiagrams
(RouteNode node
) {
RoadData lastRoad =
null;
List<RouteArc
> roadArcs =
new ArrayList<>();
for (RouteArc arc : node.
getArcs()) {
RoadData road = arc.
getRoad();
if (road
!= lastRoad
&& lastRoad
!=
null) {
roadDiagram
(lastRoad, node, roadArcs
);
predictRoadArcs
(lastRoad, node, roadArcs
);
roadArcs.
clear();
}
roadArcs.
add(arc
);
lastRoad = road
;
}
roadDiagram
(lastRoad, node, roadArcs
);
predictRoadArcs
(lastRoad, node, roadArcs
);
}
private int predictGroupClass
(RouteNode node
) {
ClassFound found =
new ClassFound
();
if (node.
getArcs().
isEmpty())
return 0;
for (RoadData road : node.
getRoads()) {
// In general the first and last nodes of a road are placed into a higher group.
// In 99.7% of cases that is all there is to it.
predictGroupFromEndPoints
(node, road, found
);
}
if (!found.
isFound()) {
// If a node is never the start or the end of a road then it is usually in group 0.
// But if it is the crossing point between two high class roads, then we might want
// to bump the class up.
//test("setting to mid road cross");
int c = node.
findFirstCross();
if (c
> 0)
found.
updateRoadClass(c
);
}
return found.
getNodeClass();
}
/**
* First stage is to use the road end points to predict a group class. This tends to
* predict 99.7% of the group classes.
* @param node The road we are trying to predict the group class of.
* @param road The road to examine.
* @param found A structure to save the results to.
*/
private void predictGroupFromEndPoints
(RouteNode node, RoadData road,
ClassFound found
) {
List<NodeLocator
> locations = road.
getNodeLocations();
int roadClass = road.
getRoadClass();
if (locations.
get(0).
getOffset() == node.
getOffset()) {
//test("node:%x: is first in road %x %d", node.getOffset(), road.getOffset(), roadClass);
found.
updateFromEnd(node, roadClass
);
} else if (locations.
get(locations.
size()-
1).
getOffset() == node.
getOffset()) {
//test("node:%x: is last in road %x %d", node.getOffset(), road.getOffset(), roadClass);
found.
updateFromEnd(node, roadClass
);
} else {
//test("node:%x: is nothing special in road %x", node.getOffset(), road.getOffset());
// Ignore for now, unless there are three different class at this node.
if (node.
nClasses() > 2)
found.
updateFromEnd(node, roadClass
);
}
}
/**
* Predict or check the arcs that we expect with those that actually exist.
*
* @param road The road to examine.
* @param node A node in that road; the source node.
* @param roadArcs The actual road arcs. We compare what we predict with this array.
*/
private void predictRoadArcs
(RoadData road, RouteNode node,
List<RouteArc
> roadArcs
) {
if (!node.
hasArcs() ||
!nod.
hasClassSections())
return;
List<NodeLocator
> nodeLocations = road.
getNodeLocations();
// Determine starting position of the source node within the road.
int start = -
1;
for (int i =
0; i
< nodeLocations.
size(); i++
) {
NodeLocator loc = nodeLocations.
get(i
);
if (loc.
getNode() == node
) {
start = i
;
break;
}
}
if (start
< 0) {
error
("Could not find start node within road");
return;
}
int currentClass = node.
getNodeClass();
int finalClass = road.
getNod2().
getRoadClass();
List<RouteArc
> predicted =
new ArrayList<>();
// Start with the backward direction. We assume this is always done first.
int first = start -
1;
for (int i = first
; i
>=
0; i--
) {
RouteNode n = nodeLocations.
get(i
).
getNode();
int nodeClass = n.
getNodeClass();
if (nodeClass
> currentClass || i == first
) {
if (nodeClass
> finalClass
)
nodeClass = finalClass
;
currentClass = nodeClass
;
addToPredicted
(predicted, n, nodeClass
);
if (nodeClass
>= finalClass
)
break;
}
}
// Now the forward direction.
currentClass = node.
getNodeClass();
finalClass = road.
getNod2().
getRoadClass();
first = start +
1;
for (int i = first
; i
< nodeLocations.
size(); i++
) {
RouteNode n = nodeLocations.
get(i
).
getNode();
int nodeClass = n.
getNodeClass();
if (nodeClass
> currentClass || i == first
) {
if (nodeClass
> finalClass
)
nodeClass = finalClass
;
currentClass = nodeClass
;
addToPredicted
(predicted, n, nodeClass
);
if (nodeClass
>= finalClass
)
break;
}
}
// Compare the arc details (node/class) that we predicted with the real thing.
int size =
Math.
min(predicted.
size(), roadArcs.
size());
for (int i =
0; i
< size
; i++
) {
RouteArc parc = predicted.
get(i
);
RouteArc rarc = roadArcs.
get(i
);
if (parc.
getTargetNode().
getOffset() != rarc.
getTargetNode().
getOffset()) {
test
("Predicted node %x, saw %x", parc.
getTargetNode().
getOffset(),
rarc.
getTargetNode().
getOffset());
}
if (parc.
getDestclass() != rarc.
getDestclass()) {
test
("Predicted arc class %d, saw %d. for node %x", parc.
getDestclass(),
rarc.
getDestclass(), parc.
getTargetNode().
getOffset());
}
}
// Check that we had the same number too.
if (predicted.
size() != roadArcs.
size()) {
test
("mismatch between predicted number of arcs and actual %d/%d", predicted.
size(),
roadArcs.
size());
}
}
private void addToPredicted
(List<RouteArc
> predicted, RouteNode node,
int nodeClass
) {
RouteArc arc =
new RouteArc
(null);
arc.
setAlt6(nodeClass
);
arc.
setTargetNode(node
);
predicted.
add(arc
);
}
/**
* Draw a diagram of the road and a given node with the arc destinations marked.
*
* Lists every node in the road. If the node is the source node it is marked with 'O', if
* the node is an arc target it is marked with a '+', otherwise it is marked with a plain line '|'.
*
* The possible destinations at each node are printed, and if this is an actual arc destination
* the actual arc-class is printed too.
*
* @param road A road to display.
* @param node A node in that road; the source node.
* @param roadArcs All the arcs from the given node that use this road.
*/
private void roadDiagram
(RoadData road, RouteNode node,
List<RouteArc
> roadArcs
) {
List<NodeLocator
> nodeLocations = road.
getNodeLocations();
// Don't display if not interesting
if (roadArcs.
size() < 3)
return;
dtrace
(" Road diagram %x class=%d/%d, narcs=%d t=%d %s\n", road.
getOffset(),
road.
getNod2().
getRoadClass(), road.
getNod2().
getRoadClass(), roadArcs.
size(),
road.
getSegments().
get(0).
getLine().
getType(), road.
isOneway()?"oneway":
"");
for (NodeLocator loc : nodeLocations
) {
String mark =
"|";
if (loc.
getOffset() == node.
getOffset())
mark =
"O";
RouteArc thisArc =
null;
for (RouteArc arc : roadArcs
) {
if (arc.
getTargetNode().
getOffset() == loc.
getOffset()) {
mark =
"+";
thisArc = arc
;
break;
}
}
RouteNode current = loc.
getNode();
dtrace
("%6s %6x rc%4d[%d] %s", mark, current.
getOffset(), current.
getCenter().
getIndex(),
current.
getNodeClass(), current.
getClasses());
if (thisArc
!=
null) {
dtrace
(" arc%d dst=%d", thisArc.
getIndex(), thisArc.
getDestclass());
}
dtrace
("\n");
}
}
private void dtrace
(String fmt,
Object...
args) {
if (Log.
isTrace()) {
System.
out.
format(fmt, args
);
}
}
private void checkNode
(RouteNode node, RouteNode previous
) {
// Display a summary
info
(" %x:Node%s: (%s) flags:'%s', arcs=%d destclass=%d nodeclass=%d minclass=%d",
node.
getOffset(), node.
isProblem() ? "!" :
"",
fmtCoord
(node.
getCoord()), node.
flagString(), node.
getArcs().
size(),
node.
getDestclass(), node.
getNodeClass(), node.
getMinDestclass());
//
// 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());
}
}
/**
* A node has zero or more arcs to other nodes.
*
* @param node The starting node.
*/
private void checkArcs
(RouteNode node
) {
int maxRoadclass =
0;
int minRoadclass =
5;
int count =
0;
RouteArc last =
null;
for (RouteArc arc : node.
getArcs()) {
if (arc.
isBad()) {
info
(" Arc %d[%x]: BAD", count, arc.
getOffset());
count++
;
continue;
}
if (arc.
getRoadClass() > maxRoadclass
)
maxRoadclass = arc.
getRoadClass();
if (arc.
getRoadClass() < minRoadclass
)
minRoadclass = arc.
getRoadClass();
//findLinesForArc(node, arc);
// Display info about the arc
Integer netOffset = arc.
getNetOffset();
RoadData rd = net.
getRoad(netOffset
);
assert rd
!=
null :
String.
format("net %x", netOffset
);
arc.
setRoad(rd
);
Label label = rd.
getLabel(0);
String roadInfo =
String.
format("rd=%x:%s:cls=%d/%d", arc.
getNetOffset(),
Label.
stripGarminCodes(label.
getText()),
arc.
getRoadClass(), arc.
getSpeed() );
info
(" Arc %d[%x]: %s, [%s] ->%x%s, %dm %s%s",
count, arc.
getOffset(), arc.
alt6String(), roadInfo,
arc.
getLink(), arc.
isLongLink() ? "(LL)" :
"",
nod.
rawToMeters(arc.
getDistance()), arc.
hasDirection()? String.
format("%.2fdeg", arc.
getInitialDegrees()):
"ND",
arc.
isLast()? " LAST":
"");
// Check that the class/speed from the arc, matches the values from NET 2.
Nod2Record nod2Record = rd.
getNod2();
if (arc.
getRoadClass() != nod2Record.
getRoadClass()) {
error
("road class from arc %d, does not match from net2 %d",
arc.
getRoadClass(), nod2Record.
getRoadClass());
}
if (arc.
getSpeed() != nod2Record.
getSpeed()) {
error
("road speed from arc %d, does not match from net2 %d",
arc.
getSpeed(), nod2Record.
getSpeed());
}
// Check the target node, make sure it exists and is valid
int link = arc.
getLink();
RouteNode other = nod.
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 {
arc.
setTargetNode(other
);
// Compare actual distance and direction with this other node from this arc
compareActual
(arc, node, other
);
checkReverse
(arc, other
);
checkCurve
(arc, other
);
checkRoadClass
(arc, other, last
);
if (last
!=
null)
checkWithLast
(arc, last
);
}
last = arc
;
count++
;
}
if (maxRoadclass
!= node.
getDestclass()) {
error
("node:%x: class on node %d, max destclass of arcs %d", node.
getOffset(),
node.
getDestclass(), maxRoadclass
);
}
}
private void checkWithLast
(RouteArc arc, RouteArc last
) {
if (arc.
getNetOffset().
equals(last.
getNetOffset())) {
// Same road, so we should have not have new net set
checkEqual
(false, arc.
newNet(),
"Node:%x/%d: continuing road new-flag",
arc.
getNode().
getOffset(), arc.
getIndex());
// The target node should not be the same
if (arc.
getLink() == last.
getLink()) {
test
("repeated arc");
}
} else {
checkEqual
(true, arc.
newNet(),
"Node:%x/%d: new road new-flag",
arc.
getNode().
getOffset(), arc.
getIndex());
}
}
/**
* Print out road class information and check it is correct.
* Not sure about the road class understanding, often the arc dest class is zero
* when you would not expect it to be.
*
* Investigating.
*
* @param arc The arc to check.
* @param target The arc target node.
* @param lastArc The previous arc. If this is the first, then it is null.
*/
private void checkRoadClass
(RouteArc arc, RouteNode target, RouteArc lastArc
) {
RouteNode node = arc.
getNode();
trace
(" road class: this=%d arcdst=%d source-node-dst=%d target-node-class=%d target-classes=%s",
arc.
getRoadClass(), arc.
getDestclass(), node.
getDestclass(), target.
getDestclass(), target.
getClasses());
int arcClass = arc.
getDestclass();
// The class on the arc is always less than or equal to that of the road it is on
if (arcClass
> arc.
getRoadClass())
error
("Node:%x/%d: arc class greater than road class", node.
getOffset(), arc.
getIndex());
// Divide into next-node arcs and far-arcs. There is not really any difference so this distinction will
// be removed once we have something better.
if (lastArc ==
null || arc.
newNet() ||
(!arc.
newNet() && arc.
isSign()!=lastArc.
isSign())) {
// Next-node arcs
if (!target.
connectsToClass(arcClass
) && arcClass
> 0)
error
("Node:%x/%d: arc-class does not match any on the target node", node.
getOffset(), arc.
getIndex());
} else {
// Far links
if (arcClass ==
0)
error
("Node:%x/%d: arc class is zero for a far-arc", node.
getOffset(), arc.
getIndex());
if (!target.
connectsToClass(arcClass
))
error
("Node:%x/%d: arc-class does not match any on the target node %s", node.
getOffset(), arc.
getIndex(), node.
getClasses());
}
}
private void checkCurve
(RouteArc arc, RouteNode other
) {
if (!arc.
hasCurve())
return;
Coord c1 = arc.
getNode().
getCoord();
Coord c2 = other.
getCoord();
double actualStraighLine = c1.
bearingTo(c2
);
int cdir = arc.
getCurveDirection();
int actualCDir = RouteArc.
directionFromDegrees(actualStraighLine
);
trace
(" curve A=%x[%x,%x->%x], B=%x direct=%.0fdeg[%x], dstc=%d, len=%dm", arc.
getCurveA(),
(arc.
getCurveA() & 0xe0
)>>5, arc.
getCurveA() & 0x1f,
cdir,
arc.
getCurveB(),
actualStraighLine, actualCDir,
arc.
getDestclass(),
nod.
rawToMeters(arc.
getDistance()));
int diff =
Math.
abs(cdir - actualCDir
);
if (diff
> 128)
diff -=
256;
if (diff
> 8)
error
("Node:%x/%d: curve direct bearing %x, expected %x", arc.
getNode().
getOffset(), arc.
getIndex(),
cdir, actualCDir
);
if (arc.
hasCurve()) {
int direct= nod.
metersToRaw(
arc.
getNode().
getCoord().
distance(other.
getCoord()));
int path = arc.
getDistance();
int calcRatio =
(int) Math.
min(31,
Math.
round(32.0d
* direct/ path
));
int ratio =
(arc.
getCurveA() & 0xe0
) >> 5;
int tolerance
;
if (direct
> 2 && path
>= direct
){
double hi =
Math.
min(31,
Math.
round(32.0d
* (direct+
1) / path
));
double lo =
Math.
min(31,
Math.
floor(32.0d
* (direct-
1) / path
));
tolerance =
(int)(hi - lo
);
} else
tolerance =
32; // ignore ratio errors, rounding errors are too big
if (ratio
> 0) {
// Compressed/limited range of ratios, so adjust.
ratio =
2 * ratio +
17;
tolerance +=
2;
//test("hi%x lo%x calc%x", hi, lo, calcRatio);
} else {
ratio = arc.
getCurveA() & 0x1f
;
if (ratio
> 17){
error
("ratio > 17 found in curveA %d",ratio
);
}
}
trace
(" distance ratio: %d, calc=%d direct=%x,path=%x", ratio, calcRatio, direct, path
);
if (Math.
abs(ratio - calcRatio
) > tolerance
) {
error
("Node:%x/%d: distance ratio %d, expected %d", arc.
getNode().
getOffset(),
arc.
getIndex(),
ratio, calcRatio
);
}
}
}
/**
* Check 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 = nod.
getNode(rlink
);
if (rother
!=
null && arc.
getNode() == rother
&& arc.
getNetOffset().
equals(rarc.
getNetOffset())) {
// This is the arc that links back to this node - the 'reverse arc'
compareToReverseArc
(arc, rarc
);
found =
true;
break;
}
}
if (!found
&& arc.
newNet()) {
error
("node:%x/%d: expected to find reverse arc",
arc.
getNode().
getOffset(), arc.
getIndex());
}
}
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();
int diff =
Math.
abs(d1 - d2
);
if (diff
> 2)
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, node, other
);
}
private void compareActualDirection
(RouteArc arc, Coord c1, Coord c2
) {
double actualStraighLine = c1.
bearingTo(c2
);
Double direction = arc.
getInitialDegrees();
Double actualInitial = arc.
getActualInitialBearing();
if (actualInitial ==
null) {
error
("Node:%x/%d: could not get initial bearing of road", arc.
getNode().
getOffset(), arc.
getIndex());
return;
}
trace
(" direction %.0f (%x), actual: initial=%.0f(%02x), direct=%.0f(%02x)",
direction, arc.
getDirection(), actualInitial, actualInitial
!=
null ? directionFromDegrees
( actualInitial
) :
0,
actualStraighLine, directionFromDegrees
(actualStraighLine
));
if (actualInitial ==
null)
actualInitial = actualStraighLine
;
int intInitial = directionFromDegrees
(actualInitial
);
if (compareAngle
(arc.
getDirection(), intInitial
))
error
("Node:%x/%d: direction %.0f(%02x), actual init=%.0f(%02x), direct=%.0f(%02x)",
arc.
getNode().
getOffset(), arc.
getIndex(),
direction, arc.
getDirection(),
actualInitial, intInitial,
actualStraighLine, directionFromDegrees
(actualStraighLine
));
}
private boolean compareAngle
(int a1,
int a2
) {
int allowed =
8; // Allowed difference before we complain
if ((a1
& 0x0f
) ==
0)
allowed += 0x10
;
int diff =
Math.
abs(a1 - a2
);
if (diff
> 128)
diff -=
256;
//System.out.printf("comp %d %d allowed=%x / diff=%x\n", a1, a2, allowed, diff);
return diff
> allowed
;
}
private double totUnits
;
private double totMeters
;
/**
* Compares the distance along the road with the value stored on the arc.
*
* @param arc The starting arc.
* @param node The starting node.
* @param other The target node that this arc leads towards.
*/
private void compareActualDistance
(RouteArc arc, RouteNode node, RouteNode other
) {
int units = arc.
getDistance();
int meters = nod.
rawToMeters(units
);
RoadData road = arc.
getRoad();
NodeLocator loc1 = road.
locateNode(node
);
NodeLocator loc2 = road.
locateNode(other
);
if (loc2 ==
null) {
error
("Node:%x/%d: Could not locate target node for arc", node.
getOffset(), arc.
getIndex());
return;
}
int pathDistance = loc2.
getLength() - loc1.
getLength();
if (!arc.
isSign())
pathDistance = -pathDistance
;
if (units
> 200) {
totUnits += units
;
totMeters += pathDistance
;
}
int pathUnits = nod.
metersToRaw(pathDistance
);
trace
(" distance %dm[%x/%x], actual %dm[%x] %s", meters, arc.
getDistance(), arc.
getPartialDistance(),
pathDistance, pathUnits,
(units
< pathUnits -
1)? "?short":
"");
// Check distance to within a percent.
if (Math.
abs(units - pathUnits
) > units/
100+
2)
error
("Node:%x/%d: distance %dm[%x], actual %dm[%x]", arc.
getNode().
getOffset(), arc.
getIndex(),
meters, units, pathDistance, pathUnits
);
}
private void checkBoundary4
() {
info
("Check NOD 4, boundary nodes with destclass > 0");
List<RouteNode
> allBoundaryNodes = nod.
readBoundaryNodes();
List<RouteNode
> list = nod.
readHighClassBoundaryNodes();
long lastSort =
0;
for (RouteNode node : list
) {
info
("ClassBoundaryNode:%x: c=%s", node.
getOffset(), node.
getClasses());
if (!node.
isBoundary())
error
("Node:%x: not a boundary node", node.
getOffset());
boolean contained = allBoundaryNodes.
remove(node
);
if (!contained
)
error
("Node:%x: high class boundary not contained in full list", node.
getOffset());
long sort = getNodeSortPosition
(node
);
if (sort
<= lastSort
)
error
("class boundary node:%x: not sorted", node.
getOffset());
lastSort = sort
;
}
lastSort =
0;
for (RouteNode node : allBoundaryNodes
) {
if (node.
getMaxDestclass() !=
0) {
error
("node %x: high class boundary node not included in NOD 4 %s",
node.
getOffset(), node.
getClasses());
long sort = getNodeSortPosition
(node
);
if (sort
<= lastSort
)
error
("normal boundary node:%x: not sorted", node.
getOffset());
lastSort = sort
;
}
}
}
public void extraArgs
(Map<String,
String> args
) {
String val = args.
remove("tab-zero");
if (val
!=
null) {
boolean z =
true;
if (val.
equals("0"))
z =
false;
setHasExtraZeros
(z
);
}
super.
extraArgs(args
);
}
public static void main
(String[] args
) {
runMain
(NodCheck.
class,
"NOD", args
);
}
private class ClassFound
{
private boolean found
;
private int roadClass
;
public void set
() {
found =
true;
}
public boolean isFound
() {
return found
;
}
public int getNodeClass
() {
return roadClass
;
}
public void updateRoadClass
(int roadClass
) {
found =
true;
this.
roadClass =
Math.
max(this.
roadClass, roadClass
);
}
public void updateFromEnd
(RouteNode node,
int roadClass
) {
int higher = node.
higherConnectionsCount(roadClass
);
if (node.
nClasses() > 1 && node.
connectionsAtClass(roadClass
) <=
1 && higher ==
0)
return;
found =
true;
this.
roadClass =
Math.
max(this.
roadClass, roadClass
);
}
}
}