/*
* Copyright (C) 2008 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.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.BitReader;
import uk.me.parabola.imgfmt.app.Coord;
import uk.me.parabola.imgfmt.app.CoordNode;
import uk.me.parabola.imgfmt.app.net.NumberStyle;
import uk.me.parabola.imgfmt.app.net.Numbers;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.Subdivision;
import uk.me.parabola.imgfmt.app.trergn.Zoom;
import uk.me.parabola.mkgmap.srt.SrtTextReader;
import test.display.Displayer;
import test.display.Section;
import test.display.check.Log;
import test.elements.Line;
import test.util.NumberReader;
/**
* Checks the consistency of the NET file.
*
* 1. For each road def, lookup the rgn line by subdiv and line number. Check that
* the name of each is the same. The name could happen to be the same and the better
* check would be against the NET pointer in the rgn section, but this would require
* more code changes.
*
* @author Steve Ratcliffe
*/
public class NetCheck
extends CommonCheck
{
private static final int RD_HAS_NOD_INFO = 0x40
;
private static final int RD_HAS_STREET_ADDRESS_INFO = 0x10
;
private static final int U_FLAG_1 = 0x04
;
private static final int U_FLAG_2 = 0x08
;
private static final int U_FLAG_3 = 0x10
;
private Subdivision
[][] subdivisions =
new Subdivision
[16][];
private Map<Integer,
String> roadNames =
new HashMap<>();
private ArrayList<Integer> roadIndexes =
new ArrayList<>();
private byte mult1
;
private final SortedSet<Integer> roadOffsets =
new TreeSet<>();
private int lowLevel
;
protected void print
() {
readHeaderLen
();
readNetHeader
();
openLbl
();
if (lbl.
getCities().
size() > 255)
citySize =
2;
if (lbl.
getZips().
size() > 255)
zipSize =
2;
openTre
();
openNet
();
openRgn
();
loadRoadIndex
();
findRoads
();
readRoadDefs
();
checkRoadIndex
();
}
private void loadRoadIndex
() {
Section section = sections.
get(2);
int recordSize = section.
getRecordSize();
if (recordSize
> 3)
assert false :
"not implemented, unknown record size " + recordSize
;
long end = section.
getEnd() - section.
getStart();
reader.
position(section.
getStart());
for (int pos =
0; pos
< end
; pos += recordSize
)
roadIndexes.
add(reader.
get3u() & 0x3fffff
);
Collections.
sort(roadIndexes
);
}
/**
* Check the road index.
*
* Each pointer should point to an entry in NET 1.
*
* The entries in the section should be sorted according the defined sort
* order.
*
* Note: the top two bits are an index into the array of road names that
* can be associated with a road.
*/
private void checkRoadIndex
() {
Section section = sections.
get(2);
int recordSize = section.
getRecordSize();
if (recordSize
> 3)
assert false :
"not implemented, unknown record size " + recordSize
;
long end = section.
getEnd() - section.
getStart();
int codepage = lbl.
getCodePage();
Sort sort = SrtTextReader.
sortForCodepage(codepage
);
Collator collator = sort.
getCollator();
collator.
setStrength(Collator.
PRIMARY);
reader.
position(section.
getStart());
String lastName =
null;
int lastValue =
0;
for (int pos =
0; pos
< end
; pos += recordSize
) {
int value = reader.
get3u();
int offset = value
& 0x3fffff
;
int strNumber =
(value
>>> 22) & 0x3
;
String name = roadNames.
get(value
);
info
("Street name: %d %s", value, name
);
if (name ==
null) {
error
("NET 3 not valid: 0x%06x", value
);
} else if (lastName
!=
null){
Name n1 =
new Name(lastName
);
Name n2 =
new Name(name
);
int cmp = collator.
compare(n1.
partialName(), n2.
partialName());
if (cmp
> 0) {
error
("Unsorted partial names /%s/%s/ values %x/%x", lastName, name, lastValue, value
);
} else if (cmp ==
0) {
cmp = collator.
compare(lastName, name
);
if (cmp
> 0) {
error
("Initial part unsorted /%s/%s/ values %x/%x", lastName, name, lastValue, value
);
}
}
}
//if (lastValue >= value)
// info("Values unsorted %x/%x", lastValue & 0x3fffff, offset);
lastName = name
;
lastValue = value
;
}
}
/**
* We have to find all the roads positions before we start.
*/
private void findRoads
() {
openTre
();
openRgn
();
openNet
();
Zoom
[] levels = tre.
getMapLevels();
lowLevel = levels
[levels.
length -
1].
getLevel();
Subdivision
[] subdivisions = tre.
subdivForLevel(lowLevel
);
for (Subdivision div : subdivisions
) {
List<Line> lines = rgn.
linesForSubdiv(div
);
for (Line line : lines
) {
if (line.
hasNet())
roadOffsets.
add(line.
getNetOffset());
}
}
}
/**
* Read in a road def (entry in NET 1) so it can be checked.
*/
private void readRoadDefs
() {
Section section = sections.
get(0);
long start = section.
getStart();
reader.
position(start
);
Iterator<Integer> iterator = roadOffsets.
iterator();
if (!iterator.
hasNext())
return;
// First will be at zero
int nextPos = iterator.
next();
assert nextPos ==
0;
while (reader.
position() < section.
getEnd()) {
while (mult1
!=
0 && (reader.
position() & (2 * mult1 -
1)) !=
0)
reader.
get(); // skip padding
Def def =
new Def
();
def.
name =
"NO NAME SET";
def.
netoff =
(int) (reader.
position() - start
);
int labnum =
0;
int labval
;
do {
labval = reader.
get3u();
String labstr = fetchLabel
(labval
& ~0xc00000
);
if (labstr
!=
null)
roadNames.
put(def.
netoff +
(labnum
<< 22), labstr
);
// we just save the first one for display purposes
if (labnum ==
0)
def.
name = labstr
;
labnum ++
;
} while ((labval
& 0x800000
) ==
0);
int flags = reader.
get1u(); // flags
reader.
get3u(); //road len
readLevelDivs
(def
);
if ((flags
& RD_HAS_STREET_ADDRESS_INFO
) == RD_HAS_STREET_ADDRESS_INFO
) {
int nblocks = reader.
get1u();
int addrFlags = reader.
get1u();
nblocks +=
((addrFlags
& 0x3
) << 8);
def.
nblocks = nblocks
;
readAddrInfo
(addrFlags
>> 2,
"zip", zipSize
);
readAddrInfo
(addrFlags
>> 4,
"city", citySize
);
List<Numbers
> numbers = readAddrInfo
(addrFlags
>> 6,
"numbers",
(nblocks
<< 8) + flags
);
if (numbers
!=
null && !numbers.
isEmpty()) {
def.
nodeIndex = numbers.
get(numbers.
size() -
1).
getIndex();
def.
numbers = numbers
;
}
}
if ((flags
& RD_HAS_NOD_INFO
) == RD_HAS_NOD_INFO
) {
int nfollow = reader.
get1u();
int nodSize =
(nfollow
& 0x3
) +
1;
reader.
get(nodSize
);
if ((nfollow
& 0xfc
) !=
0) {
int counter =
(nfollow
>> 5) & 0x3
;
if (counter
> 0) {
while (counter--
> 0) {
int uflag = reader.
get1u();
//int uflag = d.byteValue("uflag %x") & 0xff;
//d.item().addText("flag1=%b flag2=%b flag3=%b lo=%d hi=%d", (uflag&U_FLAG_1)!=0, (uflag&U_FLAG_2)!=0, (uflag&U_FLAG_3) !=0, uflag&0x3, (uflag>>>4));
// Variable length (?) bit command.
// 0 read number of bytes as given in lo bits, append to uflag and shift down
// 10 shift down uflag, add one, read this many bytes
// 11x shift down uflag, read 1, combine. read that many bytes.
int lo = uflag
& 0x3
;
if ((uflag
& U_FLAG_1
) ==
0) {
int idx = reader.
getNu((uflag
& 0x3
)+
1);
idx +=
(uflag
>>>3);
} else {
int len
;
if ((uflag
& U_FLAG_2
) == U_FLAG_2
) {
len = reader.
get1u() << 3;
len +=
(uflag
>>> 5);
} else {
len =
1 +
(uflag
>>> 4);
}
if (len
> 0) {
reader.
get(len
);
//printAddrInfo(d, 0, "city", len);
} else {
// XXX TODO
}
}
}
} else if ((nfollow
& 0x80
) == 0x80
) {
reader.
get(8); // XXX
}
}
}
checkRoadDef
(def
);
if (!iterator.
hasNext())
break;
nextPos = iterator.
next();
int len =
(int) (nextPos -
(reader.
position() - start
));
if (len
< 0)
reader.
position(reader.
position() + len
);
else
reader.
get(len
);
}
}
/**
* Check a road def (entry in NET1).
*
* All RGN lines that this points to should have a reference back to this NET1 entry.
*
* If there is more than one line in a given level for this road def, then they
* should join up end-to-start. (This is not a hard requirement, full details are not
* known).
*
* When lines join, both ends should be marked with the node-flag.
*
* @param def
*/
private void checkRoadDef
(Def def
) {
info
("Road: %s 0x%x", def.
name, def.
netoff);
Line lastLine =
null;
int lastLevel = -
1;
int countInLevel =
0;
int nodeCount =
0;
List<Div
> segments = def.
segments;
for (int s =
0; s
< segments.
size(); s++
) {
Div d = segments.
get(s
);
Subdivision div = getSubdivision
(d.
level, d.
div);
List<Line> polylines = rgn.
linesForSubdiv(div
);
Line line
;
try {
line = polylines.
get(d.
line -
1);
} catch (Exception e
) {
error
("line %s %d/%d not found", def.
name, d.
div, d.
line);
return;
}
if (lastLevel == d.
level) {
countInLevel++
;
} else {
countInLevel =
0;
}
info
(" - %d/%d/%d Type=%x part=%d", d.
level, d.
div, d.
line, line.
getType(), countInLevel
);
try (Formatter fmt =
new Formatter();) {
List<Coord
> coords = line.
getCoords();
for (int c =
0; c
< coords.
size(); c++
) {
Coord co = coords.
get(c
);
if (Log.
isTrace()) {
fmt.
format(" - %s(%.5f,%.5f) %b %n", co, Utils.
toDegrees(co.
getLatitude()),
Utils.
toDegrees(co.
getLongitude()), co
instanceof CoordNode
);
}
boolean first = s ==
0 && c ==
0;
boolean firstInContinuationLine = s
> 0 && c ==
0;
if (co.
getId() > 0 && !first
&& !firstInContinuationLine
)
nodeCount++
;
}
trace
(fmt.
toString());
}
if (line.
getNetOffset() != def.
netoff) {
error
("road net1 offset=0x%x, line net1 offset=0x%x: %s %d/%d/%d", def.
netoff,
line.
getNetOffset(), def, d.
level, d.
div, d.
line);
}
if (lastLine
!=
null && lastLevel == d.
level) {
if (lastLine.
getLastPoint().
equals(line.
getFirstPoint())) {
// This test is not valid unless we have an independent test of which points
// are nodes. Downgraded to a trace until that can be done.
if (!(lastLine.
getLastPoint() instanceof CoordNode
) && lastLine.
hasNodeFlags() && line.
hasNodeFlags()) {
trace
("Last point of a joined line without node-flag %s",
line.
getLastPoint().
getId() !=
0);
}
} else {
// Lines don't join
// TODO: this check is not really valid, so it may be removed or improved.
// This is not really an error since this is seen in real maps. In many cases
// when it happens, one of lat or longitude is the same, so downgrade to info
// in that case.
if (lastLine.
getLastPoint().
getLatitude() == line.
getFirstPoint().
getLatitude()
|| lastLine.
getLastPoint().
getLongitude() == line.
getFirstPoint().
getLongitude())
{
info
("warning: last %s, first %s", lastLine.
getLastPoint(),
line.
getFirstPoint());
} else {
error
("lines do not join: last %s, first %s", lastLine.
getLastPoint(),
line.
getFirstPoint());
}
}
}
lastLine = line
;
lastLevel = d.
level;
}
if (lastLine
!=
null && lastLine.
getLastPoint().
getId() !=
0)
error
("last point of line in group has node-flag set");
trace
(" nblocks=%d, ncount=%d", def.
nblocks, nodeCount
);
if (def.
nblocks !=
null) {
if (nodeCount
!= def.
nblocks)
error
("node-count=%d, nblocks=%d [%s] %x", nodeCount, def.
nblocks, def.
name, def.
netoff);
if (def.
nodeIndex !=
null && def.
nblocks < def.
nodeIndex)
error
("housenumbers: final-rnode=%d, nblocks=%d [%s] %x", def.
nodeIndex, def.
nblocks, def.
name, def.
netoff);
}
// Check no negative or zero numbers, that must be wrong.
if (def.
numbers !=
null) {
for (Numbers n : def.
numbers) {
if (n.
getLeftNumberStyle() != NumberStyle.
NONE) {
if (n.
getLeftStart() <=
0 || n.
getLeftEnd() <=
0) {
error
("housenumbers: zero or negative: %s %d: left=%d,%d", def.
name, def.
netoff, n.
getLeftStart(), n.
getLeftEnd());
}
}
if (n.
getRightNumberStyle() != NumberStyle.
NONE) {
if (n.
getRightStart() <=
0 || n.
getRightEnd() <=
0) {
error
("housenumbers: zero or negative: %s %d: right=%d,%d", def.
name, def.
netoff, n.
getRightStart(), n.
getRightEnd());
}
}
}
}
}
private void readNetHeader
() {
Displayer d =
new Displayer
(reader
);
readSection
(d,
"road defs",
1,
false,
false);
mult1 = reader.
get();
readSection
(d,
"segmented roads",
2,
false,
false);
reader.
get();
readSection
(d,
"road index",
3,
true,
false);
}
private Subdivision
[] getSubdivisions
(int level
) {
if (subdivisions
[level
] ==
null)
subdivisions
[level
] = tre.
subdivForLevel(level + lowLevel
);
return subdivisions
[level
];
}
private Subdivision getSubdivision
(int level,
int n
) {
Subdivision
[] divs = getSubdivisions
(level
);
return divs
[n - divs
[0].
getNumber()];
}
private List<Numbers
> readAddrInfo
(int flags,
String name,
int size
) {
switch (flags
& 0x3
) {
case 3:
return null;
case 2:
if (size ==
2)
reader.
get2u();
else
reader.
get();
break;
case 1:
int n = reader.
get2u();
reader.
get(n
);
break;
case 0:
n = reader.
get1u();
byte[] bytes = reader.
get(n
<< mult1
);
if (name.
equals("numbers")) {
int rflags = size
& 0xff
;
int nblocks = size
>> 8;
BitReader br =
new BitReader
(bytes
);
NumberReader nr =
new NumberReader
(br
);
nr.
setNumberOfNodes(nblocks
);
return nr.
readNumbers((rflags
& 0x20
) !=
0);
}
break;
}
return null;
}
private void readLevelDivs
(Def def
) {
int[] counts =
new int[8];
int ncounts =
0;
boolean last =
false;
while (!last
) {
byte b = reader.
get();
if ((b
& 0x80
) == 0x80
)
last =
true;
counts
[ncounts++
] = b
& 0x7f
;
}
List<Div
> divs =
new ArrayList<>();
for (int i =
0; i
< ncounts
; i++
) {
int n = counts
[i
];
while (n--
> 0) {
Div div =
new Div
();
div.
level = i
;
div.
line = reader.
get1u();
div.
div = reader.
get2u();
divs.
add(div
);
}
}
def.
segments = divs
;
}
public static void main
(String[] args
) {
runMain
(NetCheck.
class,
"NET", args
);
}
class Def
implements Comparable<Def
> {
private String name
;
private int netoff
;
private List<Div
> segments
;
private Integer nblocks
;
private Integer nodeIndex
;
private List<Numbers
> numbers
;
/**
* Only compares the net1 offset, since that is the only thing we are interested
* in currently.
*/
public int compareTo
(Def o
) {
if (netoff == o.
netoff)
return 0;
else if (netoff
< 0)
return -
1;
else
return 1;
}
public String toString
() {
return String.
format("0x%06x: %s", netoff, name
);
}
}
class Div
{
private int level
;
private int div
;
private int line
;
}
private class Name {
private final String name
;
private int prefix
;
private int suffix
;
private int b
;
private int s
;
private boolean hasOrders
;
Name(String name
) {
this.
name = name
;
if (name.
charAt(0) < 7)
prefix =
1;
int sep = name.
indexOf(0x1e
);
if (sep
< 0)
sep = name.
indexOf(0x1b
);
if (sep
> 0)
prefix = sep +
1;
sep = name.
indexOf(0x1f
);
if (sep
< 0)
sep = name.
indexOf(0x1c
);
if (sep
> 0)
suffix = sep
;
}
public String getName
() {
return name
;
}
String partialName
() {
if (suffix
> 0)
return name.
substring(prefix, suffix
);
else
return name.
substring(prefix
);
}
private String initialPart
() {
int off = prefix
;
if (off ==
0)
return "";
return name.
substring(0, off
);
}
private String suffixPart
() {
if (suffix ==
0)
return "";
return name.
substring(suffix, name.
length());
}
public boolean equals
(Object o
) {
if (this == o
)
return true;
if (o ==
null || getClass
() != o.
getClass())
return false;
Name name1 =
(Name) o
;
if (!name.
equals(name1.
name))
return false;
return true;
}
public int getB
() {
return b
;
}
public void setB
(int b
) {
this.
b = b
;
}
public int getS
() {
return s
;
}
public void setS
(int s
) {
this.
s = s
;
}
public int hashCode
() {
return name.
hashCode();
}
public String toString
() {
return name
;
}
}
}