/*
* 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.net.NODHeader;
import uk.me.parabola.util.EnhancedProperties;
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.391;
private final int nodesStart
;
private final int nodesLen
;
private final int align
;
private final int mult
;
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 Map<Integer, Nod2Record
> roads =
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();
mult = header.
getMult1();
tableARecordLen = header.
getTableARecordLen();
int flags = header.
getFlags();
// 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
);
}
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.
get() & 0xff
;
long end = calcTablePosition
(start, low
);
RouteCenter table =
new RouteCenter
(end, tableARecordLen
);
table.
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
;
}
/**
* 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)
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
;
}
/**
* 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
;
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
;
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;
while (!done
) {
int ptrSize = center.
getCPointerSize();
if (ptrSize ==
1) {
int off = reader.
get();
done =
(off
& 0x80
) == 0x80
;
} else if (ptrSize ==
2) {
int off = reader.
getChar();
done =
(off
& 0x8000
) == 0x8000
;
} else {
done =
true;
}
}
}
while (((reader.
position() - nodesStart
) & ((1 << mult
) -
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.
get() & 0xff
;
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.
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 || newnet
)
lastNet = reader.
get() & 0xff
;
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.
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
}
// There is a new arc direction when:
// 1. it's the first one in a node
// 2. when the newnet 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.
get() & 0xff
;
arc.
setCurveA(curvea
);
if ((curvea
& 0xe0
) ==
0)
arc.
setCurveB(reader.
get() & 0xff
);
}
node.
addArc(arc
);
lastSign = sign
;
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();
}
}
/**
* 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.
getu3();
//System.out.printf("nod2=%x, nod1=%x size %d\n", offset, offsetNod1, allNodes.size());
info.
setNode(getNode
(offsetNod1
));
int bitLen = reader.
getChar();
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.
get() & 0xff
;
reader.
get(len
>> 1);
}
if((extraFormat
& 0x02
) !=
0) {
int len = reader.
get() & 0xff
;
reader.
get(len
>> 1);
}
if ((extraFormat
& 0xc0
) ==
0) {
if ((extraFormat
& 0x04
) !=
0) {
reader.
get();
}
if ((extraFormat
& 0x08
) !=
0) {
reader.
getChar();
}
} else {
int len = reader.
get() & 0xff
;
reader.
get(len
>> 1);
}
}
info.
setNext(reader.
position() - start
);
return info
;
}
}