/*
* 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.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.Section;
import uk.me.parabola.imgfmt.app.net.NODHeader;
import uk.me.parabola.util.EnhancedProperties;
import test.display.check.Log;
import static test.util.ArcFlags.ARC_CURVE;
import static test.util.ArcFlags.ARC_EXTRA;
/**
* Read in the NOD file for display and testing purposes.
*
* @author Steve Ratcliffe
*/
public class NodFile
extends ImgReader
{
private static final double UNIT_TO_METER =
2.4;
private final int nodesStart
;
private final int nodesLen
;
private final int align
;
private final int ptrShift
;
private final int distanceMult
;
private final int tableARecordLen
;
private final ImgFileReader reader
;
private final Map<Integer, RouteNode
> allNodes =
new HashMap<>();
private final Map<Integer, RouteCenter
> tables =
new HashMap<>();
private final int[] classBoundaries
;
private Map<Integer, Nod2Record
> roads =
new HashMap<>();
private int indexIdSize
;
public NodFile
(ImgFileReader r
) {
reader = r
;
setReader
(r
);
NODHeader header =
new NODHeader
();
setHeader
(header
);
header.
readHeader(r
);
if (header.
getHeaderLength() >=
127)
RouteCenter.
setHasExtraZeros(false);
nodesStart = header.
getNodeStart();
nodesLen = header.
getNodeSize();
align = header.
getAlign();
ptrShift = header.
getMult1();
tableARecordLen = header.
getTableARecordLen();
int flags = header.
getFlags();
classBoundaries = header.
getClassBoundaries();
// This appears to be 3 bits long. If you change this in a map, then the time
// estimates for a route change, although not the distance estimates. Possible that
// there is more to it.
int mult =
(flags
>> 5) & 0x7
;
distanceMult =
(1 << mult
);
indexIdSize = header.
getIndexIdSize();
}
public int metersToRaw
(double m
) {
double d = m /
(distanceMult
* UNIT_TO_METER
);
return (int) Math.
round(d
);
}
/**
* Convert from the internal representation to meters.
* @param raw The internal length value. Represents 16 feet.
* @return Length of the arc in meters.
*/
public int rawToMeters
(int raw
) {
return (int) Math.
round(((double) raw+
0.5) * (distanceMult
* UNIT_TO_METER
));
}
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.
get1u();
long end = calcTablePosition
(start, low
);
RouteCenter table =
new RouteCenter
(end, tableARecordLen, ptrShift, nodesStart
);
table.
readTable(reader, 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());
while (((reader.
position() - nodesStart
) & ((1 << ptrShift
) -
1)) !=
0)
reader.
get();
centers.
add(table
);
count++
;
}
return centers
;
}
/**
* 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
);
int nodeClass =
0;
// Find out what class section we are in. If the file does not have class sections
// then this will always be zero.
if (classBoundaries
[0] !=
0) {
for (int c =
4; c
>=
0; c--
) {
if (classBoundaries
[c
] <= start
) {
nodeClass = c +
1;
break;
}
}
}
while (reader.
position() < end -
5) {
RouteNode node = readNode
(table
);
if (node ==
null)
return;
node.
setNodeClass(nodeClass
);
table.
addNode(node
);
allNodes.
put(node.
getOffset(), node
);
}
}
/**
* 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.
* @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
) {
while (((reader.
position() - nodesStart
) & ((1 << ptrShift
) -
1)) !=
0)
reader.
get();
int offset =
(int) reader.
position() - nodesStart
;
int low = reader.
get1u();
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
return null;
}
}
RouteNode node =
new RouteNode
(center, offset
);
if (tabstart
!= center.
getOffset()) {
node.
setBad(true);
return node
;
}
int flags = reader.
get1u();
if (low ==
0 && flags ==
0)
return null;
node.
setFlags(flags
);
Coord coord = readPosition
(center, node.
hasLargeCoordOffsets());
node.
setCoord(coord
);
if (node.
hasArcs()) {
readArcs
(center, node
);
}
if (node.
hasRestrictions()) {
boolean done =
false;
int ptrSize = center.
getCPointerSize();
int topbit = 0x80
<< (8 * (ptrSize -
1));
while (!done
) {
int off = reader.
getNu(ptrSize
);
done =
(off
& topbit
) !=
0;
int val = off
& (topbit-
1);
RouteCenter.
Restriction restr = center.
getRestriction(val
);
node.
addRestriction(restr
);
}
}
while (((reader.
position() - nodesStart
) & ((1 << ptrShift
) -
1)) !=
0)
reader.
get();
node.
setNextStart(reader.
position()-nodesStart
);
return node
;
}
private void readArcs
(RouteCenter center, RouteNode node
) {
boolean end =
false;
boolean first =
true;
boolean lastSign =
false;
int lastNet =
0;
DirectionReader dirFetcher =
new DirectionReader
(reader
);
int length =
0;
int count =
0;
do {
RouteArc arc =
new RouteArc
(node
);
arc.
setIndex(count
);
arc.
setOffset(reader.
position()-nodesStart
);
// Start with alt6 byte
int alt6 = reader.
get1u();
arc.
setAlt6(alt6
);
// this is not the class of the segment, but the max of classes of the dest node
boolean newnet = arc.
newNet();
boolean sign = arc.
isSign();
// 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.
get1u();
// 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.
get1u();
}
// 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.
get1u();
int nodeoff =
((intro1
<< 8 | intro2
) & 0x3fff
) << ptrShift
;
// Construct a pointer to another node, signed 16 bit quantity
if ((nodeoff
& (0x2000
<< ptrShift
)) !=
0)
nodeoff |= 0xffffc000
<< ptrShift
;
int otherNode = node.
getOffset() + nodeoff
;
if (otherNode
> 0)
arc.
setLink(otherNode
);
}
if (first || newnet
)
lastNet = reader.
get1u();
arc.
setLocalNet(lastNet
);
RouteCenter.
TableA net = center.
getNet(lastNet
);
if (net
!=
null) {
arc.
setTableA(net
);
} else {
arc.
setBad(true);
node.
setProblem(true);
}
int len
;
boolean curve
;
if ((alt6
& ARC_EXTRA
) == ARC_EXTRA
) {
int len1 = reader.
get1u();
if ((len1
& 0x80
) == 0x80
) {
if ((len1
& 0x40
) == 0x40
) {
//d.item().addText("22 bit length + curve");
int len2 = reader.
get2u();
len =
(len1
& 0x3f
) |
(len2
<< 6); // 6+16 bits
curve =
true;
} else {
//d.item().addText("14 bit length");
int len2 = reader.
get1u();
len =
(len1
& 0x3f
) |
(len2
<< 6); // 6+8 bits
curve =
false;
}
} else {
//d.item().addText("15 bit length + curve");
int len2 = reader.
get1u();
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.
get1u(); // 2+8 bits
}
// There is a new arc direction when:
// 1. it's the first one in a node
// 2. when the new net bit is set
// 3. when the sign changes
//
// This is reason to read a new direction and to start a new length.
if (first || newnet || sign
!= lastSign
)
length =
0;
// length should be in units of 16 feet. It is cumulative since the last change
length += len
;
arc.
setDistance(length
);
arc.
setPartialDistance(len
);
arc.
setDirection(dirFetcher.
fetchDirection(first, newnet, sign
));
if (curve
) {
int curvea = reader.
get1u();
arc.
setCurveA(curvea
);
if ((curvea
& 0xe0
) ==
0)
arc.
setCurveB(reader.
get1u());
}
node.
addArc(arc
);
lastSign = sign
;
first =
false;
count++
;
} while (!end
);
}
public List<RouteNode
> readBoundaryNodes
() {
List<RouteNode
> list =
new ArrayList<>();
NODHeader header =
(NODHeader
) getHeader
();
Section section = header.
getBoundarySection();
reader.
position(section.
getPosition());
while (reader.
position() < section.
getEndPos()) {
int lng = reader.
get3u();
if ((lng
& 0x800000
) !=
0)
lng += 0xff000000
;
int lat = reader.
get3u();
if ((lat
& 0x800000
) !=
0)
lat += 0xff000000
;
Coord coord =
new Coord
(lat, lng
);
int off = reader.
get3u() << ptrShift
;
RouteNode node = getNode
(off
);
if (node ==
null) {
Log.
error("Node:%x: on boundary, does not exist", off
);
continue;
}
if (!coord.
equals(node.
getCoord()))
Log.
error("Boundary node coords not equal to its node boundary=%s node=%s", coord, node.
getCoord());
list.
add(node
);
}
return list
;
}
public List<RouteNode
> readHighClassBoundaryNodes
() {
List<RouteNode
> list =
new ArrayList<>();
NODHeader header =
(NODHeader
) getHeader
();
Section section = header.
getHighClassBoundary();
reader.
position(section.
getPosition());
while (reader.
position() < section.
getEndPos()) {
reader.
get3u();
reader.
get3u();
int off = reader.
get3u() << ptrShift
;
RouteNode node = getNode
(off
);
if (node ==
null) {
Log.
error("Node:%x: on boundary, does not exist", off
);
continue;
}
list.
add(node
);
}
return list
;
}
private Coord readPosition
(RouteCenter center,
boolean bigoff
) {
short longoff
;
short latoff
;
if (bigoff
) {
longoff =
(short) reader.
get2u();
latoff =
(short) reader.
get2u();
} else {
int latlon = reader.
get3s();
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
);
}
/**
* Read the NOD2 information for a road.
*
* @param offset The offset into Nod 2.
*/
public Nod2Record getNod2
(int offset
) {
Nod2Record info = roads.
get(offset
);
if (info
!=
null)
return info
;
info =
new Nod2Record
(offset
);
roads.
put(offset, info
);
int start =
((NODHeader
) getHeader
()).
getRoadSection().
getPosition();
reader.
position(start + offset
);
byte flags = reader.
get();
info.
setFlags(flags
);
int offsetNod1 = reader.
get3u() << ptrShift
;
//System.out.printf("nod2=%x, nod1=%x size %d\n", offset, offsetNod1, allNodes.size());
info.
setNode(getNode
(offsetNod1
));
int bitLen = reader.
get2u();
int nstream =
(bitLen +
7) /
8;
byte[] bytes = reader.
get(nstream
);
info.
setBitStream(bytes
);
info.
setBitStreamLen(bitLen
);
if((flags
& 0x80
) !=
0) {
int extraFormat = reader.
get();
if((extraFormat
& 0x01
) !=
0) {
int len = reader.
get1u();
reader.
get(len
>> 1);
}
if((extraFormat
& 0x02
) !=
0) {
int len = reader.
get1u();
reader.
get(len
>> 1);
}
if ((extraFormat
& 0xc0
) ==
0) {
if ((extraFormat
& 0x04
) !=
0) {
reader.
get();
}
if ((extraFormat
& 0x08
) !=
0) {
reader.
get2u();
}
} else {
int len = reader.
get1u();
reader.
get(len
>> 1);
}
}
info.
setNext(reader.
position() - start
);
return info
;
}
public boolean hasClassSections
() {
return classBoundaries
[0] !=
0;
}
/**
* @return the indexIdSize
*/
public int getIndexIdSize
() {
return indexIdSize
;
}
}