/*
* 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.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.net.NODHeader;
import uk.me.parabola.util.EnhancedProperties;
import static test.util.ArcFlags.ARC_CURVE;
import static test.util.ArcFlags.ARC_EXTRA;
import static test.util.ArcFlags.ARC_SIGN;
/**
* Read in the NOD file for display and testing purposes.
*
* @author Steve Ratcliffe
*/
public class NodFile
extends ImgReader
{
private final int nodesStart
;
private final int nodesLen
;
private final int align
;
private final ImgFileReader reader
;
private final Map<Integer, RouteNode
> allNodes =
new HashMap<>();
private final Map<Integer, RouteCenter
> tables =
new HashMap<>();
public NodFile
(ImgFileReader r
) {
reader = r
;
setReader
(r
);
NODHeader header =
new NODHeader
();
setHeader
(header
);
header.
readHeader(r
);
nodesStart = header.
getNodeStart();
nodesLen = header.
getNodeSize();
align = header.
getAlign();
}
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.
get() & 0xff
;
long end = calcTablePosition
(start, low
);
RouteCenter table = RouteCenter.
readTable(reader, nodesStart, 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());
centers.
add(table
);
count++
;
}
return centers
;
}
public List<RoadData
> readRoads
() {
NODHeader header =
(NODHeader
) getHeader
();
reader.
position(header.
getRoadSection().
getPosition());
List<RoadData
> roads =
new ArrayList<>();
long end = header.
getRoadSection().
getEndPos();
while (reader.
position() < end
) {
RoadData data =
new RoadData
();
reader.
get();
int nodOffset = reader.
getu3();
data.
setNodOffset(nodOffset
);
int bitlen = reader.
getChar();
int len =
(bitlen +
7) /
8;
reader.
get(len
);
roads.
add(data
);
}
return roads
;
}
/**
* 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
);
while (reader.
position() < end
) {
RouteNode node = readNode
(table,
0);
if (node ==
null || node.
isBad())
return;
table.
addNode(node
);
allNodes.
put(node.
getOffset(), node
);
}
}
public RouteNode fixNode
(int start
) {
RouteNode node = readNode
(null, start
);
if (node ==
null)
return null;
RouteCenter center = node.
getCenter();
center.
addNode(node
);
allNodes.
put(node.
getOffset(), node
);
return node
;
}
/**
* Go through all the nodes that are reachable from NOD 2 section and attempt to read them.
*
* All nodes may not be reachable from Nod 2.
*/
public void fixMissingNodes
() {
List<RoadData
> roads = readRoads
();
List<Integer> offsets =
new ArrayList<>();
for (RoadData data : roads
) {
offsets.
add(data.
getNodOffset());
}
Collections.
sort(offsets
);
for (Integer offset : offsets
) {
RouteNode node = getNode
(offset
);
if (node ==
null) {
fixNode
(offset
);
}
}
}
/**
* 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.
* @param start The start position of the node relative to the section data start.
* @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,
int start
) {
int offset
;
if (start ==
0)
offset =
(int) reader.
position() - nodesStart
;
else {
offset = start
;
reader.
position(start + nodesStart
);
}
int low = reader.
get() & 0xff
;
if (low ==
0)
return null;
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
//System.out.printf("Cache miss on table %x\n", tabstart);
return null;
}
}
RouteNode node =
new RouteNode
(center, offset
);
if (tabstart
!= center.
getOffset()) {
node.
setBad(true);
return node
;
}
int flags = reader.
get() & 0xff
;
node.
setFlags(flags
);
Coord coord = readPosition
(center, node.
hasLargeCoordOffsets());
node.
setCoord(coord
);
if (node.
hasArcs()) {
readArcs
(center, node
);
}
if (node.
hasRestrictions()) {
boolean done =
false;
while (!done
) {
if (center.
getCPointerSize() ==
1) {
int off = reader.
get();
done =
(off
& 0x80
) == 0x80
;
} else if (center.
getCPointerSize() ==
2) {
int off = reader.
getChar();
done =
(off
& 0x8000
) == 0x8000
;
} else {
done =
true;
}
}
}
node.
setNextStart(reader.
position()-nodesStart
);
return node
;
}
private void readArcs
(RouteCenter center, RouteNode node
) {
boolean end =
false;
boolean first =
true;
DirectionReader dirFetcher =
new DirectionReader
(reader
);
int count =
0;
do {
RouteArc arc =
new RouteArc
(node
);
arc.
setIndex(count
);
arc.
setOffset(reader.
position()-nodesStart
);
// Start with alt6 byte
int alt6 = reader.
get() & 0xff
;
arc.
setAlt6(alt6
);
// this is not the class of the segment, but the max of classes of the dest node
boolean newdir = arc.
hasNet();
// 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.
get() & 0xff
;
// 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.
get() & 0xff
;
}
// 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.
get() & 0xff
;
short nodeoff =
(short) ((intro1
<< 8 | intro2
) & 0x3fff
);
// Construct a pointer to another node, signed 16 bit quantity
if ((nodeoff
& 0x2000
) !=
0)
nodeoff |= 0xc000
;
int otherNode = node.
getOffset() + nodeoff
;
if (otherNode
> 0)
arc.
setLink(otherNode
);
}
if (first || newdir
) {
//d.byteValue("pointer to local net index %d");
int netidx = reader.
get() & 0xff
;
arc.
setLocalNet(netidx
);
RouteCenter.
TableA net = center.
getNet(netidx
);
if (net
!=
null) {
arc.
setNetOffset(net.
getNetOffset());
arc.
setRoadClass(net.
getRoadClass());
arc.
setSpeed(net.
getSpeed());
} else {
arc.
setBad(true);
node.
setProblem(true);
}
}
int len
;
boolean curve
;
if ((alt6
& ARC_EXTRA
) == ARC_EXTRA
) {
int len1 = reader.
get() & 0xff
;
if ((len1
& 0x80
) == 0x80
) {
if ((len1
& 0x40
) == 0x40
) {
//d.item().addText("22 bit length + curve");
int len2 = reader.
getChar() & 0xff
;
len =
(len1
& 0x3f
) |
(len2
<< 6); // 6+16 bits
curve =
true;
} else {
//d.item().addText("14 bit length");
int len2 = reader.
get() & 0xff
;
len =
(len1
& 0x3f
) |
(len2
<< 6); // 6+8 bits
curve =
false;
}
} else {
//d.item().addText("15 bit length + curve");
int len2 = reader.
get() & 0xff
;
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.
get() & 0xff
; // 2+8 bits
}
// length should be in units of 16 feet
//item.addText("length %d (%dm)", len, (int)(len * 16 / 3.25));
arc.
setRawDistance(len
);
arc.
setRawDirection(dirFetcher.
fetchDirection(first, newdir,
(alt6
& ARC_SIGN
) !=
0));
if (curve
) {
int curvea = reader.
get() & 0xff
;
if ((curvea
& 0xe0
) ==
0) {
int curveb = reader.
get() & 0xff
;
//item.addText("curve[0] %02x curve[1] %02x (two bytes)", curvea, curveb);
} else {
int angle =
360 * (byte)(((curvea
& 0x1f
) << 3) |
((curvea
& 0xe0
) >> 5)) /
256;
//item.addText("curve[0] %02x (%d deg?)", curvea, angle);
}
}
node.
addArc(arc
);
first =
false;
count++
;
} while (!end
);
}
private Coord readPosition
(RouteCenter center,
boolean bigoff
) {
short longoff
;
short latoff
;
if (bigoff
) {
longoff =
(short) reader.
getChar();
latoff =
(short) reader.
getChar();
} else {
int latlon = reader.
get3();
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
);
}
public void resort
() {
for (RouteCenter table : tables.
values()) {
table.
resort();
}
}
}