/*
* Copyright (C) 2011.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 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.display;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.charset.Charset;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import test.check.CommonCheck;
import test.display.check.BoundChecker;
import test.display.check.MapDetailList;
import test.display.check.MapDetails;
import test.display.check.MdrStrings;
import test.display.check.Street;
import test.display.check.SubsectionList;
import uk.me.parabola.imgfmt.Utils;
import uk.me.parabola.imgfmt.app.lbl.City;
import uk.me.parabola.imgfmt.app.mdr.Mdr14Record;
import uk.me.parabola.imgfmt.app.mdr.Mdr28Record;
import uk.me.parabola.imgfmt.app.mdr.Mdr29Record;
import uk.me.parabola.imgfmt.app.srt.Sort;
import uk.me.parabola.imgfmt.app.trergn.MapObject;
import uk.me.parabola.imgfmt.fs.DirectoryEntry;
import uk.me.parabola.imgfmt.fs.FileSystem;
import uk.me.parabola.imgfmt.fs.ImgChannel;
import uk.me.parabola.imgfmt.mdxfmt.MapInfo;
import uk.me.parabola.imgfmt.mdxfmt.MdxFileReader;
import uk.me.parabola.imgfmt.mps.MapBlock;
import uk.me.parabola.imgfmt.mps.MpsFileReader;
import uk.me.parabola.imgfmt.sys.FileImgChannel;
import uk.me.parabola.imgfmt.sys.ImgFS;
import uk.me.parabola.mkgmap.srt.SrtTextReader;
/**
* Program to check the contents of an mdr file against the files that it indexes.
*
* @author Steve Ratcliffe
*/
public class MdrCheck
extends CommonCheck
{
private int numberOfMaps
;
private Charset charSet
;
private boolean[] print =
new boolean[100];
private final MapDetailList details =
new MapDetailList
();
private MdrStrings mdrStrings
;
private final List<Mdr20Info
> cityMdr20 =
new ArrayList<>();
private final List<Name> streets =
new ArrayList<>();
private int[] streetMapNumbers
;
private int[] mdr20ToStreet
;
// These are the key to reading several other 2x sections
private List<Mdr28Record
> regionNames =
new ArrayList<>();
private List<Mdr29Record
> countryNames =
new ArrayList<>();
private final List<TypeInfo> typeInfos =
new ArrayList<>();
private final Map<Integer,
Integer> poiType =
new HashMap<>();
private final Map<Integer,
Integer> groupEnds =
new LinkedHashMap<>(); // from MDR9
private final List<TypeInfoMdr10
> typesFromMdr10 =
new ArrayList<>();
private Collator collator
;
private boolean mdr9IsOK
;
private int codepage
;
private boolean isMulti
;
public MdrCheck
() {
Arrays.
fill(print,
true);
}
@
Override
protected void print
() {
readHeader
();
readMaps
();
HuffmanDecoder decoder =
new HuffmanDecoder
();
check16
(decoder
);
mdrStrings =
new MdrStrings
(reader, getSection
(15), charSet, decoder,
true /* double check */);
check5
();
check7
();
check20
();
check1
();
streetMapNumbers =
null;
mdr20ToStreet =
null;
check9
();
check10
();
check11
();
check13
();
check18
();
check19
();
// region and country keys to the other sections
check28
();
check29
();
check23
();
check24
();
check25
();
check27
();
check21
();
check22
();
check26
();
}
private void check1
() {
setShowLogs
(print
[1]);
info
("mdr1 check");
if ((getSection
(1).
getMagic() & 1) ==
0)
return;
SubsectionList list =
new SubsectionList
(numberOfMaps
);
for (int i =
1; i
<= numberOfMaps
; i++
) {
MapDetails map = details.
getMap(i
);
if (map ==
null)
return; // If we don't have all the maps, then give up currently.
list.
read(reader, i, map.
getSubHeaderOffset());
}
// Check that the totals summed across all the maps are correct.
for (int s =
1; s
<=
8; s++
) {
int[] subNumbers =
{0,
11,
10,
7,
5,
6,
20,
21,
22};
String[] subs =
new String[]{
"n/a",
"mdr11",
"mdr10",
"mdr7",
"mdr5",
"mdr6",
"mdr20",
"mdr21",
"mdr22"
};
int total = list.
totalForSection(s
);
int expected = getSection
(subNumbers
[(s ==
2) ? s -
1 : s
]).
getNumberOfRecords();
checkEqual
(expected, total,
"sub%d (%s) total number", s, subs
[s
]);
list.
setMax(s, expected
);
}
// Now check that every pointer points to something that is in the correct map
for (int ss =
1; ss
<=
8; ss++
) {
int max =
0;
for (int m =
1; m
<= numberOfMaps
; m++
) {
int off = list.
getOffset(m, ss
);
int length = list.
getLength(m, ss
);
int size = list.
getSize(ss
);
int end = off + size
* length
;
reader.
position(off
);
info
("mdr1 map%d sub%d; len %d, recsize %d", m, ss, length, size
);
int count =
0;
while (reader.
position() < end
) {
count++
;
int val = read
(size
);
if (val
> max
) max = val
;
list.
checkValue(m, ss, val
);
// In some cases we follow the pointer and check that it points to something
// in the correct map.
if (ss ==
6) {
int v2 = mdr20ToStreet
[val
];
assert v2
!=
0;
int foundMapNumber = streetMapNumbers
[v2
];
checkEqual
(m, foundMapNumber,
"mdr1 map%d sub5; map number wrong, pointer %d, value %d",
m, count, val
);
}
}
}
int expectedMax = list.
getMax(ss
);
checkEqual
(expectedMax, max,
"mdr1 sub%d; expected max value", ss
);
}
}
/**
* Check details of the cities.
*/
private void check5
() {
setShowLogs
(print
[5]);
info
("mdr5 check");
Section section = getSection
(5);
long start = section.
getStart();
long end = section.
getEnd();
int magic = section.
getMagic();
cityMdr20.
add(null);
int cityPtrSize =
(magic
& 0x3
) +
1;
boolean hasRegionCountry =
(magic
& 0x4
) !=
0;
boolean hasStr =
(magic
& 0x8
) !=
0;
boolean has20 =
(magic
& 0x100
) !=
0;
boolean has20offset =
(magic
& 0x800
) !=
0;
int mdr20PointerSize =
0;
if (getSection
(20).
getLen() > 0) {
if (has20
)
mdr20PointerSize = getSection
(20).
getBytesForRecords();
if (has20offset
)
mdr20PointerSize = getSection
(20).
getBytesForSize();
}
boolean has28_29_offset =
(magic
& 0x400
) !=
0;
int mdr28_29PointerSize =
0;
if (has28_29_offset
)
mdr28_29PointerSize = getSection
(28).
getBytesForRecords();
if (has28_29_offset
&& mdr20PointerSize ==
0)
mdr28_29PointerSize = getSection
(29).
getBytesForRecords();
test
("Sect5 flags %x %d %d %d", magic, cityPtrSize, mdr20PointerSize, mdr28_29PointerSize
);
reader.
position(start
);
int citynum =
0;
int lastMdr20 =
0;
Mdr20Info last20Info =
null;
while (reader.
position() < end
) {
citynum++
;
int mapNumber = readMapNumber
();
assert mapNumber
> 0 && mapNumber
<= numberOfMaps :
"Bad map number " + mapNumber
;
int localCityNum = read
(cityPtrSize
);
int lbl = read
(3);
int region =
0;
int country =
0;
boolean hasRegion =
false;
if (hasRegionCountry
) {
int val = read
(2);
if ((val
& 0x4000
) ==
0) {
hasRegion =
true;
region = val
& 0x3fff
;
} else {
country = val
& 0x3fff
;
}
}
String strText =
null;
if (hasStr
)
strText = readString
();
int off20 =
0;
if (mdr20PointerSize
> 0) {
off20 = read
(mdr20PointerSize
);
if (off20
!=
0 && off20
< lastMdr20
)
error
("%d map%d; out of order off20=%d last=%d", citynum, mapNumber, off20, lastMdr20
);
if (off20
!=
0)
lastMdr20 = off20
;
}
if (has28_29_offset
)
read
(mdr28_29PointerSize
);
Mdr20Info m20 =
new Mdr20Info
(off20
);
m20.
mapNumber = mapNumber
;
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
if (hasRegionCountry
) {
// There is always a country. If there is a region, then it will have a country.
if (hasRegion
)
country = map.
getCountryFromRegion(region
);
m20.
regionName = map.
getRegionName(region
);
m20.
countryName = map.
getCountryName(country
);
checkNotZero
(country,
"%d no country", citynum
);
} else {
// Get from the city.
City city = map.
getCity(localCityNum
);
int nreg = city.
getRegionNumber();
int ncountry
;
// If region, then get country from the region.
if (hasRegion
(city
)) {
ncountry = map.
getCountryFromRegion(nreg
);
} else {
ncountry = city.
getCountryNumber();
}
m20.
regionName = map.
getRegionName(nreg
);
m20.
countryName = map.
getCountryName(ncountry
);
}
// Print out the city information collected
String name = map.
getLabelText(lbl
& 0x7fffff
);
boolean repeated =
(lbl
& 0x800000
) ==
0;
info
("%d map%d; %s mapCity=%d reg=%d (%s) country=%d (%s) ind20=%d rep=%b\n",
citynum, mapNumber, name, localCityNum,
region, m20.
regionName, country, m20.
countryName,
m20.
mdr20index, repeated
);
City city = map.
getCity(localCityNum
);
String nameFromCity = city.
getName();
if (strText
!=
null && !strText.
isEmpty())
checkEqual
(name, strText,
"map %d: city str table %d", mapNumber, citynum
);
checkEqual
(name, nameFromCity,
"map %d: city %d city name", mapNumber, citynum
);
m20.
cityName = nameFromCity
;
if (m20.
sameCity(last20Info
)) {
if (!repeated
)
error
("%d map%d; repeat flag not set on repeated name %s", citynum, mapNumber, nameFromCity
);
} else {
if (repeated
)
error
("%d map%d; repeat flag set when not a repeat", citynum, mapNumber
);
}
last20Info = m20
;
} else {
if (strText
!=
null && !strText.
isEmpty())
m20.
cityName = strText
;
}
cityMdr20.
add(m20
);
}
// Mark the last street
Mdr20Info m =
new Mdr20Info
(getSection
(20).
getNumberOfRecords() +
1);
cityMdr20.
add(m
);
}
private static boolean hasRegion
(City city
) {
return (city.
getRegionCountryNumber() & 0x4000
) ==
0;
}
/**
* Check details of the streets.
*/
private void check7
() {
setShowLogs
(print
[7]);
info
("mdr7 check");
Section section = getSection
(7);
long start = section.
getStart();
long end = section.
getEnd();
int magic = section.
getMagic();
streetMapNumbers =
new int[section.
getNumberOfRecords() +
1];
// fill up the 0 element.
streets.
add(null);
reader.
position(start
);
int streetnum =
0;
Name lastName =
null;
boolean hasStr =
(magic
& 0x01
) !=
0;
boolean hasNameOffset =
(magic
& 0x20
) !=
0;
int partialInfoSize =
((magic
>> 6) & 0x7
);
int partialBShift =
((magic
>> 9) & 0xf
);
int partialBMask =
(1 << partialBShift
) -
1;
Map<Integer,
BitSet> roadsWithOffset0 =
new HashMap<>();
Map<Integer,
BitSet> roadsWithOffset =
new HashMap<>();
while (reader.
position() < end
) {
streetnum++
;
int mapNumber = readMapNumber
();
streetMapNumbers
[streetnum
] = mapNumber
;
int lblData = read
(3);
int lblOff = lblData
& 0x7fffff
;
String stringData =
(hasStr
) ? readString
() :
null;
int nameOffset =
0;
if (hasNameOffset
) {
nameOffset = read
(1);
Map<Integer,
BitSet> map = nameOffset ==
0 ? roadsWithOffset0 : roadsWithOffset
;
BitSet bs = map.
get(mapNumber
);
if (bs ==
null) {
bs =
new BitSet();
map.
put(mapNumber, bs
);
}
bs.
set(lblOff
);
}
int partialInfo =
0;
boolean partialRepeat =
false;
if (partialInfoSize
> 0) {
partialInfo = read
(partialInfoSize
);
if ((partialInfo
& 0x1
) ==
0) {
// I call it a partial repeat when you are using the separators between the name
// and the Street, Road etc. and the first part is repeated.
// So
// MAIN^ROAD
// MAIN^ROAD -- full repeat
// MAIN^STREET -- partial repeat
partialRepeat =
true;
}
}
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
String sname = map.
getLabelText(lblOff
);
Name name =
new Name(mapNumber, sname, nameOffset
);
streets.
add(name
);
boolean repeated =
(lblData
& 0x800000
) ==
0;
String repeatStr = repeatString
(repeated, partialRepeat
);
if (partialBShift
> 0) {
name.
hasOrders =
true;
name.
b =
(partialInfo
>>> 1) & partialBMask
;
name.
s =
(partialInfo
>>> partialBShift +
1);
}
info
("%d map%d lbl=0x%x street=%s %s off:%d b=%d t=%d",
streetnum, mapNumber, lblOff, name, repeatStr, nameOffset,
name.
b,
name.
s);
checkNames
(lastName, name, repeated, partialRepeat
);
lastName = name
;
} else {
lastName =
null;
streets.
add(null);
}
assert streets.
size() == streetnum +
1 :
"streets size " + streets.
size() +
"/" + streetnum
;
}
if (hasNameOffset
) {
info
("mdr7 name offset check");
for (Entry
<Integer,
BitSet> e : roadsWithOffset.
entrySet()) {
BitSet bs0 = roadsWithOffset0.
get(e.
getKey());
if (bs0
!=
null) {
e.
getValue().
andNot(bs0
);
}
e.
getValue().
stream().
forEach(lblOff -
> {
MapDetails map = details.
getMap(e.
getKey());
String sname =
null;
if (map
!=
null) {
sname = map.
getLabelText(lblOff
);
}
if (sname
!=
null) {
error
("map%d lbl=0x%x street=%s: no off=0 entry found, but off>0 was used", e.
getKey(), lblOff, sname
);
} else {
error
("map%d lbl=0x%d: no off=0 entry found", e.
getKey(), lblOff
);
}
});
}
}
}
private static String repeatString
(boolean repeated
) {
return repeatString
(repeated,
null);
}
private static String repeatString
(boolean repeated,
Boolean partialRepeat
) {
return String.
format("(%s%s)", repeated
? "R" :
"",
Boolean.
TRUE.
equals(partialRepeat
) ? "r" :
"");
}
private void check9
() {
setShowLogs
(print
[9]);
info
("mdr9 check");
Section section = getSection
(9);
long start = section.
getStart();
long end = section.
getEnd();
if (start == end
)
return;
int recsize = section.
getRecordSize();
reader.
position(start
);
final Set<Integer> knownGroups =
new HashSet<>(Arrays.
asList(1,
2,
3,
4,
5,
6,
7,
8,
9,
11,
12,
13,
64,
65,
66));
Map<Integer,
Integer> groups =
new LinkedHashMap<>();
while (reader.
position() < end
) {
int group = read
(1);
int mdr10Start = read
(recsize -
1);
if (knownGroups.
contains(group
))
info
("group %d starts with mdr-10 record %d", group, mdr10Start
);
else {
error
("unknown group %d starts with mdr-10 record %d",group, mdr10Start
);
}
if (groups.
isEmpty() && mdr10Start
!=
1) {
error
("first group doesn't point to first mdr-10 record");
mdr9IsOK =
false;
}
groups.
put(mdr10Start, group
);
}
Iterator<Entry
<Integer,
Integer>> iter = groups.
entrySet().
iterator();
Entry
<Integer,
Integer> prevEntry = iter.
next();
while (iter.
hasNext()) {
Entry
<Integer,
Integer> e = iter.
next();
groupEnds.
put(e.
getKey()-
1, prevEntry.
getValue());
prevEntry = e
;
}
groupEnds.
put(Integer.
MAX_VALUE, prevEntry.
getValue());
}
private void check10
() {
setShowLogs
(print
[10]);
info
("mdr10 check");
Section section = getSection
(10);
long start = section.
getStart();
long end = section.
getEnd();
if (start == end
)
return;
int numPois = getSection
(11).
getNumberOfRecords();
int recsize = Section.
numberOfBytes(numPois
*2) +
1;
int expectedItems = section.
getLen() / recsize
;
if (expectedItems
!= numPois
) {
error
("Unexpected section size %d for %d pois in Mdr10, cannot check", section.
getLen(),numPois
);
return;
}
int mask =
(1 << ((recsize -
1) * 8) -
1) -
1;
reader.
position(start
);
int record =
0;
Iterator<Entry
<Integer,
Integer>> iter = groupEnds.
entrySet().
iterator();
Entry
<Integer,
Integer> groupEntry = iter.
next();
while (reader.
position() < end
) {
record++
;
if (record
> groupEntry.
getKey())
groupEntry = iter.
next();
int group = groupEntry.
getValue();
int typeOrSubType = read
(1);
TypeInfoMdr10 ti =
new TypeInfoMdr10
();
ti.
isPolygon = group
>=
64;
ti.
type = toType
(group, typeOrSubType
);
ti.
mdr11Rec = read
(recsize -
1) & mask
;
typesFromMdr10.
add(ti
);
}
typesFromMdr10.
sort((o1, o2
) -
> Integer.
compare(o1.
mdr11Rec, o2.
mdr11Rec));
}
private void check11
() {
setShowLogs
(print
[11]);
info
("mdr11 check");
Section section = getSection
(11);
long start = section.
getStart();
long end = section.
getEnd();
int recsize = section.
getRecordSize();
reader.
position(start
);
int record =
0;
while (reader.
position() < end
) {
record++
;
int next =
(int) (reader.
position() + recsize
);
int mapNumber = readMapNumber
();
int num = read
(1);
int subdiv = read
(2);
int lbloff = read
(3);
boolean ok =
true;
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
String text = map.
getLabelText(lbloff
);
TypeInfoMdr10 expectedType =
null;
if (record -
1 < typesFromMdr10.
size()) {
expectedType = typesFromMdr10.
get(record-
1);
assert expectedType.
mdr11Rec == record
;
MapObject o
;
String kind
;
if (expectedType.
isPolygon) {
kind =
"polygon";
o = map.
getShape(subdiv, num
);
} else {
kind =
"point";
o = map.
getPoint(subdiv, num
);
}
if (o ==
null) {
error
("%d: map%d; %s; %s not found subdiv=%d, ind=%d", record, mapNumber, kind, text, subdiv, num
);
} else {
if (o.
getType() != expectedType.
type) {
error
("%d: map%d; %s; %s subdiv/num=%d/%d index says t=0x%x , RGN says t=0x%04x", record, mapNumber, kind, text, subdiv, num, expectedType.
type,o.
getType());
} else {
info
("%d: map%d; t=0x%04x subdiv/num=%d/%d %s %s", record, mapNumber, o.
getType(), subdiv, num, kind, text
);
int type = o.
getType();
poiType.
put(record, type
);
}
}
}
}
reader.
position(next
);
}
}
/**
* Regions. We read this mainly to get a mapping of region names to country names
* to check within other sections.
*/
private void check13
() {
setShowLogs
(print
[13]);
info
("mdr13 check");
Section section = getSection
(13);
long start = section.
getStart();
long end = section.
getEnd();
reader.
position(start
);
int record =
0;
while (reader.
position() < end
) {
record++
;
int mapNumber = readMapNumber
();
assert mapNumber
> 0 && mapNumber
<= numberOfMaps :
"Bad map number " + mapNumber
;
int regNum = read
(2);
int countryNum = read
(2);
String strText = readString
();
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
String regionName = map.
getRegionName(regNum
);
String countryName = map.
getCountryName(countryNum
);
info
("%d map%d; region %s country %s", record, mapNumber, regionName, countryName
);
if (!regionName.
equals(strText
))
error
("%d map%d; region %s, text string %s", record, mapNumber, regionName, strText
);
}
}
}
private void check16
(HuffmanDecoder decoder
) {
setShowLogs
(print
[16]);
info
("mdr16 check");
Section section = getSection
(16);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
reader.
position(start
);
decoder.
setHuffmanDecodingFailed(true); // being pessimistic here
AtomicInteger varLength =
new AtomicInteger();
int remSize = reader.
readVarLength(varLength
);
if (section.
getLen() - remSize
!= varLength.
get()) {
error
("bad remaining size %d", remSize
);
return;
}
decoder.
setLookupBits(reader.
get1u() & 0xf
); // spotted values: 0x15 and 0x16
/** typically a 32x2 byte lookup table */
int huffmanTab2Size =
(1 << decoder.
getLookupBits()) * 2;
int maxDepth = reader.
get1u(); // max code length
if (maxDepth
>=
32) {
error
("maximum depth too high: %d", maxDepth
);
return; // don't know how to handle this
}
int rowsTab1 = reader.
get1u(); // rows in search table
if (rowsTab1
>=
32) {
error
("number of rows for search table too high: %d", rowsTab1
);
return; // don't know how to handle this
}
int symWidth = reader.
get1u();
if (symWidth
!=
8) {
return; // don't know yet how to handle this
}
int symBytes = reader.
readVarLength(varLength
);
int offsetSize = varLength.
get();
int headerSize =
(int) (reader.
position() - start
);
int minCodeBytes =
(int) Math.
ceil(maxDepth /
8.0);
int tab1Width =
1 + offsetSize + minCodeBytes
;
int tab1Bytes = section.
getLen() - huffmanTab2Size - symBytes - headerSize
;
if (rowsTab1
* tab1Width
!= tab1Bytes
) {
error
("search table: rows x width doesn't match table expected size");
//error("rows=%d, width=%d, secLen=%d, hdrSize=%d, tab2Size=%d symBytes=%d",
// rowsTab1, tab1Width, section.getLen(), headerSize, huffmanTab2Size, symBytes);
return; // don't know how to handle this
}
for (int i =
0; i
< rowsTab1
; i++
) {
// the minCode value is shifted to allow a binary search
int minCodeShifted = reader.
getNu(minCodeBytes
);
int depth = reader.
get1u();
int offset = reader.
getNu(offsetSize
);
decoder.
addSearchTab(minCodeShifted, depth, offset
);
}
decoder.
setLookupTable(reader.
get(huffmanTab2Size
));
decoder.
setSymbols(reader.
get((int) (end - reader.
position())));
decoder.
setHuffmanDecodingFailed(false); // probably
decoder.
buildHuffmanTree();
if (decoder.
isHuffmanDecodingFailed())
info
("building Huffman tree for double check");
if (!decoder.
isHuffmanDecodingFailed())
info
("Huffman decoder table and tree OK");
}
private void check18
() {
setShowLogs
(print
[18]);
info
("mdr18 check");
Section section = getSection
(18);
long start = section.
getStart();
long end = section.
getEnd();
int psize = getSection
(19).
getBytesForRecords();
reader.
position(start
);
while (reader.
position() < end
) {
char type =
(char)reader.
get2u();
int rec = read
(psize
);
TypeInfo ti =
new TypeInfo();
ti.
type = type
;
ti.
start = rec
;
typeInfos.
add(ti
);
}
}
private void check19
() {
setShowLogs
(print
[19]);
info
("mdr19 check");
Section section = getSection
(19);
if (section.
getNumberOfRecords() ==
0)
return;
if (typeInfos.
isEmpty()) {
error
("no type info from mdr 18 available");
return;
}
long start = section.
getStart();
// The number of bytes required to represent a POI (mdr11) record number and a flag bit.
int recsize = Section.
numberOfBytes(getSection
(11).
getNumberOfRecords() << 1);
int flag =
0;
switch (recsize
) {
case 1:
flag = 0x80
;
break;
case 2:
flag = 0x8000
;
break;
case 3:
flag = 0x800000
;
break;
}
reader.
position(start
);
int record =
0;
for (int i =
0, typeInfosSize = typeInfos.
size(); i
< typeInfosSize -
1; i++
) {
TypeInfo ti = typeInfos.
get(i
);
TypeInfo ti2 = typeInfos.
get(i +
1);
int expected = ti.
type & ~0xe000
;
expected =
((expected
<< 3) & ~0xff
) |
(expected
& 0x1f
);
if (ti2.
start == ti.
start)
error
("empty range for type %x", ti.
type);
for (int j = ti.
start; j
< ti2.
start; j++
) {
record++
;
int rec = read
(recsize
);
rec
&= ~flag
;
Integer type = poiType.
get(rec
);
if (type ==
null) {
error
("could not get type for %d", rec
);
} else {
if (type == expected ||
(type
<= 0x7f
&& (expected
>> 8) == type
)) info
("%d: poi %d type %x", record, rec, type
);
else error
("%d: poi %d type was %x, expected %x", record, rec, type, expected
);
}
}
}
}
/**
* Check that the mdr20 records point to streets in the correct city.
*/
private void check20
() {
setShowLogs
(print
[20]);
info
("mdr20 check");
Section section = getSection
(20);
long start = section.
getStart();
int magic = section.
getMagic();
mdr20ToStreet =
new int[section.
getNumberOfRecords() +
1];
int size = Section.
numberOfBytes(getSection
(7).
getNumberOfRecords() << 1);
// Mdr20Info first = cityMdr20.get(1);
// checkEqual(1, first.mdr20index, "first mdr20 not 1");
reader.
position(start
);
int record =
1;
int partialInfoSize =
((magic
>> 3) & 0x7
);
int partialBShift =
((magic
>> 6) & 0xf
);
int partialBMask =
(1 << partialBShift
) -
1;
for (int i =
1; i
< cityMdr20.
size() -
1; i++
) {
Mdr20Info from = cityMdr20.
get(i
);
if (from.
mdr20index ==
0)
continue;
Mdr20Info next =
null;
for (int j = i +
1; next ==
null || next.
mdr20index ==
0; j++
)
next = cityMdr20.
get(j
);
if (record
!= from.
mdr20index) {
error
("mdr20 record %d out of step with index %d", record, from.
mdr20index);
record = from.
mdr20index;
}
info
("mdr20 %d city=%s r=%s c=%s, from=%d to=%d %s", record, from.
cityName,
from.
regionName, from.
countryName, from.
mdr20index, next.
mdr20index,
(from.
mdr20index == next.
mdr20index) ? "empty" :
"");
Name lastName =
null;
boolean hasLabel =
(magic
& 0x2
) !=
0; // A guess
boolean hasOffset =
(magic
& 0x4
) !=
0; // A guess, but less so
boolean hasStreetIndex =
(magic
& 0x800
) !=
0; // Guess
while (record
< next.
mdr20index) {
assert from.
mdr20index != next.
mdr20index :
"from " + from.
mdr20index +
" to " + next.
mdr20index;
Name name =
null;
List<Street
> streetList =
null;
boolean repeated =
false;
// As far as I know the hasStreetIndex and hasLabel are mutually exclusive
if (hasStreetIndex
) {
int val = read
(size
);
repeated =
(val
& 1) ==
0;
int ind = val
>> 1;
mdr20ToStreet
[record
] = ind
;
//info("street index %d", ind);
name = streets.
get(ind
);
streetList = getStreetsByName
(name.
mapNumber, name.
name);
}
if (hasLabel
) {
int mapNumber = readMapNumber
();
int val = read
(3);
repeated =
(val
& 0x800000
) ==
0;
int nameOffset =
0;
if (hasOffset
) {
nameOffset = read
(1);
}
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
int lblOffset = val
& 0x7fffff
;
String sname = map.
getLabelText(lblOffset
);
streetList = map.
getStreetsWithName(sname
);
name =
new Name(mapNumber, sname, nameOffset
);
}
}
Boolean partialRepeat =
null;
if (partialInfoSize
> 0) {
int partialInfo = read
(partialInfoSize
);
partialRepeat =
(partialInfo
& 1) ==
0;
if (partialBShift
> 0) {
name.
hasOrders =
true;
name.
b =
(partialInfo
>>> 1) & partialBMask
;
name.
s =
(partialInfo
>>> partialBShift +
1);
}
}
// At this point we have a name, nameOffset and a streetList
if (streetList
!=
null && !streetList.
isEmpty()) {
boolean found =
false;
for (Street s : streetList
) {
//System.out.println("from city " + from.cityName + "/r=" + from.regionName);
//System.out.println("s " + s.getCityName() + "/r=" + s.getRegionName());
if (from.
cityName.
equals(s.
getCityName()) && from.
regionName.
equals(s.
getRegionName()) && from.
countryName.
equals(s.
getCountryName())) {
found =
true;
info
("%d map%d st=%s%s, city=%s, r=%s, c=%s; b=%d s=%d",
record, s.
getMapIndex(),
s.
getName(), repeatString
(repeated, partialRepeat
), s.
getCityName(),
s.
getRegionName(), s.
getCountryName(),
name.
b,
name.
s
);
break;
}
}
if (!found
) {
error
("%d map%d no road name=%s in city %s", record, name.
mapNumber, name.
name, from.
cityName);
for (Street s : streetList
) {
info
(" ... map%d %s, in %s r=%s c=%s", s.
getMapIndex(), s.
getName(), s.
getCityName(), s.
getRegionName(), s.
getCountryName());
}
}
checkNames
(lastName, name, repeated, partialRepeat
);
} else {
// This is an error if we have saved all the maps
if (details.
mapSaved(numberOfMaps
)) {
error
("%d could not find any streets", record
);
}
}
lastName = name
;
record++
;
}
}
}
private List<Street
> getStreetsByName
(int mapNumber,
String name
) {
MapDetails map = details.
getMap(mapNumber
);
if (map ==
null)
return null;
return map.
getStreetsWithName(name
);
}
private void checkNames
(Name last,
Name name,
boolean repeatFlat
) {
checkNames
(last, name, repeatFlat,
null);
}
private void checkNames
(Name last,
Name name,
boolean repeatFlag,
Boolean partialFlag
) {
if (last ==
null)
return;
// Checks on the partial name
String lastPartial = last.
partialName();
String partial = name.
partialName();
int cmp = collator.
compare(lastPartial, partial
);
if (partialFlag
!=
null) {
if (cmp ==
0) {
if (!partialFlag
)
error
("partial repeat flag not set for partial repeated name");
} else {
if (partialFlag
)
error
("partial repeat flag set when not repeated {%s}{%s}", last, name
);
}
}
// Last partial name must not be larger than this one
if (cmp
> 0) {
error
("Unordered partial name /%s/%s/ off=%d/%d",
lastPartial, partial, last.
offset, name.
offset);
} else if (cmp ==
0) {
// If the partial name is equal, check the beginning part of the name
cmp = collator.
compare(last.
name, name.
name);
if (cmp
> 0) {
error
("Unordered partial name including initial part {%s}{%s}",
last, name
);
}
}
cmp = collator.
compare(last.
name, name.
name);
if (cmp ==
0 && (partialFlag ==
null || partialFlag
)) {
// A full repeat is not flagged, unless it is also a partial repeat.
if (!repeatFlag
)
error
("repeat flag not set for repeated name {%s}{%s}",
last, name
);
} else {
if (repeatFlag
)
error
("repeat flag set when not repeated");
}
if (repeatFlag
&& Boolean.
FALSE.
equals(partialFlag
)) {
error
("Flagged as full repeat but not partial repeat");
}
// If there is no partial flag given, or no orderings available then we are done.
if (partialFlag ==
null ||
!name.
hasOrders)
return;
// Compare the initial part of the name
cmp = last.
compareInitial(name
);
if (repeatFlag
) {
if (last.
b != name.
b)
error
("B is different for repeated name %d/%d", last.
b, name.
b);
if (last.
s != name.
s)
error
("S is different for repeated name %d/%d", last.
s, name.
s);
} else if (partialFlag
) {
if (name.
b ==
0 && name.
startOffset() > 0 && name.
s ==
0)
if (cmp
< 0)
error
("B is zero for partial repeat");
}
// If the repeat flag is set, then we are done
if (repeatFlag
)
return;
if (name.
startOffset() > 0 && partialFlag
) {
// Check b
if (cmp
< 0) {
//info("B normal initial order");
if (last.
b >= name.
b)
error
("B does not order the initial part /%s/%s/", last.
initialPart(), name.
initialPart());
} else if (cmp
> 0){
//info("B reversed initial order");
if (name.
b >= last.
b)
error
("B does not order the initial part (reversed) /%s/%s/", last.
initialPart(), name.
initialPart());
} else {
if (name.
s ==
0)
error
("S is unexpectedly zero");
}
}
// Compare suffix part
if (name.
suffix > 0 && last.
suffix > 0 && partialFlag
) {
cmp = last.
compareSuffix(name
);
if (cmp
< 0) {
//info("S normal initial order");
if (last.
s >= name.
s)
error
("S does not order the suffix /%s/%s/", last.
suffixPart(), name.
suffixPart());
} else if (cmp
> 0) {
//info("S reversed initial order");
if (name.
s >= last.
s)
error
("S does not order the suffix (reversed) /%s/%s/", last.
suffixPart(), name.
suffixPart());
}
}
}
private void check21
() {
setShowLogs
(print
[21]);
info
("mdr21 check");
Section section = getSection
(21);
long start = section.
getStart();
long end = section.
getEnd();
if (checkEmpty
(section
))
return;
int size = Section.
numberOfBytes(getSection
(7).
getNumberOfRecords() << 1);
if (section.
getRecordSize() != size
) {
error
("mdr21: record size %d, byte required for mdr7 is %d", section.
getRecordSize(), size
);
error
(" ... don't know how to deal with this version of the section");
return;
}
reader.
position(start
);
Name lastName =
null;
for (int i =
1; i
< regionNames.
size() -
1; i++
) {
Mdr28Record from = regionNames.
get(i
);
if (from.
getMdr21() ==
0)
continue;
Mdr28Record next =
null;
for (int j = i +
1; next ==
null || next.
getMdr21() ==
0; j++
)
next = regionNames.
get(j
);
String expectedName = from.
getName();
info
("mdr21 from %d to %d; expect region %s", from.
getMdr21(), next.
getMdr21(),
expectedName
);
lastName =
null;
for (int record = from.
getMdr21(); record
< next.
getMdr21() && reader.
position() < end
; record++
) {
int val = read
(size
);
int idx = val
>> 1;
Name name = streets.
get(idx
);
List<Street
> streetList = getStreetsByName
(name.
mapNumber, name.
name);
if (streetList
!=
null && !streetList.
isEmpty()) {
boolean repeated =
(val
& 1) ==
0;
checkNames
(lastName, name, repeated
);
lastName = name
;
boolean found =
false;
for (Street s1 : streetList
) {
if (expectedName.
equals(s1.
getRegionName())) {
found =
true;
info
("%d map%d st=%d, %s%s, r=%s, c=%s",
record, s1.
getMapIndex(), idx,
s1.
getName(), repeatString
(repeated
), s1.
getRegionName(),
s1.
getCountryName());
}
}
if (!found
) {
error
("%d no road %d name=%s in region %s", record, idx, streetList.
get(0).
getName(), expectedName
);
for (Street s1 : streetList
) {
info
(" ... %s, in %s", s1.
getName(), s1.
getRegionName());
}
}
} else {
// This is an error if we have saved all the maps
if (details.
mapSaved(numberOfMaps
)) {
error
("%d; could not find any streets", idx
);
}
}
}
}
}
private void check22
() {
setShowLogs
(print
[22]);
info
("mdr22 check");
Section section = getSection
(22);
long start = section.
getStart();
int magic = section.
getMagic();
setShowLogs
(print
[22]);
info
("mdr22");
int recordSize = Section.
numberOfBytes(getSection
(7).
getNumberOfRecords() << 1);
reader.
position(start
);
int record =
1;
boolean hasLabel =
(magic
& 0x2
) !=
0; // A guess
boolean hasOffset =
(magic
& 0x4
) !=
0; // A guess, but less so
int partialInfoSize =
((magic
>> 3) & 0x7
);
int partialBShift =
((magic
>> 6) & 0xf
);
int partialBMask =
(1 << partialBShift
) -
1;
for (int i =
1; i
< countryNames.
size() -
1; i++
) {
Name lastName =
null;
Mdr29Record from = countryNames.
get(i
);
if (from.
getMdr22() ==
0)
continue;
Mdr29Record next =
null;
for (int j = i +
1; next ==
null || next.
getMdr22() ==
0; j++
)
next = countryNames.
get(j
);
if (record
!= from.
getMdr22()) {
error
("mdr22 record %d out of step with index %d", record, from.
getMdr22());
record = from.
getMdr22();
}
String expectedName = from.
getName();
info
("mdr22 from %d to %d; expect country %s", from.
getMdr22(), next.
getMdr22(), expectedName
);
for (; record
< next.
getMdr22(); record++
) {
assert from.
getMdr22() != next.
getMdr22() :
"from " + from.
getMdr22() +
" to " + next.
getMdr22();
Name name =
null;
// 1 map number, 2 lbl offset, 3 a flag
int val
;
List<Street
> streetList =
null;
boolean repeated
;
if (hasLabel
) {
int mapNumber = readMapNumber
();
val = read
(3);
repeated =
(val
& 0x800000
) ==
0;
int nameOffset =
0;
if (hasOffset
)
nameOffset = read
(1);
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
int lblOffset = val
& 0x7fffff
;
name =
new Name(mapNumber, map.
getLabelText(lblOffset
), nameOffset
);
streetList = map.
getStreetsWithName(name.
getName());
}
} else {
// Just has a LBL pointer
val = read
(recordSize
);
int idx = val
>> 1;
repeated =
(val
& 1) ==
0;
name = streets.
get(idx
);
streetList = getStreetsByName
(name.
mapNumber, name.
name);
}
Boolean partialRepeat =
null;
int partialInfo =
0;
if (partialInfoSize
> 0) {
partialInfo = read
(partialInfoSize
);
partialRepeat =
(partialInfo
& 1) ==
0;
}
if (name
!=
null) {
if (partialBShift
> 0) {
name.
hasOrders =
true;
name.
b =
(partialInfo
>>> 1) & partialBMask
;
name.
s =
(partialInfo
>>> partialBShift +
1);
}
if (streetList
!=
null && !streetList.
isEmpty()) {
boolean found =
false;
for (Street s1 : streetList
) {
if (expectedName.
equals(s1.
getCountryName())) {
found =
true;
info
("%d map%d %s%s, c=%s b=%d s=%d",
record, s1.
getMapIndex(),
s1.
getName(), repeatString
(repeated, partialRepeat
),
s1.
getCountryName(),
name.
b,
name.
s
);
break;
}
}
if (!found
) {
error
("%d no road name=%s in country %s", record, streetList.
get(0).
getName(),
expectedName
);
for (Street s1 : streetList
) {
info
(" ... %s, in %s", s1.
getName(), s1.
getCountryName());
}
}
} else {
// This is an error if we have saved all the maps
if (details.
mapSaved(numberOfMaps
)) {
error
("%d; could not find any streets", record
);
}
}
checkNames
(lastName, name, repeated, partialRepeat
);
} else {
error
("%d No name", record
);
}
lastName = name
;
}
}
}
private void check23
() {
setShowLogs
(print
[23]);
info
("mdr23 check");
Section section = getSection
(23);
long start = section.
getStart();
long end = section.
getEnd();
reader.
position(start
);
int regionnum =
0;
Name lastName =
null;
while (reader.
position() < end
) {
regionnum++
;
int mapNumber = readMapNumber
();
int region = read
(2);
int country = read
(2);
int lbl = read
(3);
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
int lblOffset = lbl
& 0x7fffff
;
Name name =
new Name(mapNumber, map.
getLabelText(lblOffset
));
info
("%d map%d; region %s, r %d, c %d", regionnum, mapNumber, name, region, country
);
if (name.
equals(lastName
)) {
error
("%d map%d; region name repeated in same map %s",
regionnum, mapNumber, name
);
}
boolean repeated =
(lbl
& 0x800000
) ==
0;
checkNames
(lastName, name, repeated
);
lastName = name
;
}
}
}
private void check24
() {
setShowLogs
(print
[24]);
info
("mdr24 check (countries)");
Section section = getSection
(24);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
reader.
position(start
);
Name lastName =
null;
for (int i =
1; i
< countryNames.
size() -
1; i++
) {
Mdr29Record from = countryNames.
get(i
);
Mdr29Record next = countryNames.
get(i +
1);
info
("mdr24 from %d to %d", from.
getMdr24(), next.
getMdr24());
for (int countrynum = from.
getMdr24(); countrynum
< next.
getMdr24() && reader.
position() < end
; countrynum++
) {
String expectedCountry = from.
getName();
int mapNumber = readMapNumber
();
int country = read
(2);
int lbl = read
(3);
MapDetails map = details.
getMap(mapNumber
);
if (map
!=
null) {
int lblOffset = lbl
& 0x7fffff
;
String text = map.
getLabelText(lblOffset
);
Name name =
new Name(mapNumber, text
);
if (expectedCountry.
isEmpty()) {
expectedCountry = text
;
from.
setName(text
);
}
info
("%d map%d; country %s, %d", countrynum, mapNumber, name, country
);
checkEqual
(expectedCountry, name.
getName(),
"%d map%d; country name", countrynum, mapNumber
);
if (name.
equals(lastName
)) {
error
("%d map%d; name repeated in same map %s", countrynum, mapNumber, name
);
}
boolean repeated =
(lbl
& 0x800000
) ==
0;
checkNames
(lastName, name, repeated
);
lastName = name
;
}
}
}
}
/**
* Cities by country.
*/
private void check25
() {
setShowLogs
(print
[25]);
info
("mdr25 check");
Section section = getSection
(25);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
int size = Section.
numberOfBytes(getSection
(5).
getNumberOfRecords());
if (section.
getRecordSize() != size
) {
error
("mdr25 record size %d, bytes required for mdr5 is %d", section.
getRecordSize(), size
);
error
(" ... don't know how to deal with this version of the section");
return;
}
reader.
position(start
);
int lastMapNumber =
0;
Mdr20Info lastInfo =
null;
int record =
0;
while (reader.
position() < end
) {
record++
;
int val = read
(size
);
Mdr20Info info = cityMdr20.
get(val
);
String countryName = info.
countryName;
String cityName = info.
cityName;
info
("%d country %s; map%d city %d %s", record, countryName, info.
mapNumber, val,
cityName
);
if (info.
mapNumber == lastMapNumber
&& isSameByName
(info, lastInfo
))
error
("%d country %s; repeated city name %s in same map", record, countryName, cityName
);
lastInfo = info
;
lastMapNumber = info.
mapNumber;
}
}
private static boolean isSameByName
(Mdr20Info first, Mdr20Info other
) {
return other
!=
null && first.
cityName.
equals(other.
cityName) && first.
regionName
.
equals(other.
regionName) && first.
countryName.
equals(other.
countryName);
}
/**
* Mdr 28 ordered by country.
*/
private void check26
() {
setShowLogs
(print
[26]);
info
("mdr26 check");
Section section = getSection
(26);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
int size = Section.
numberOfBytes(getSection
(28).
getNumberOfRecords());
if (section.
getRecordSize() != size
) {
error
("mdr26 record size %d, bytes required for mdr25 is %d", section.
getRecordSize(), size
);
error
(" ... don't know how to deal with this version of the section");
return;
}
reader.
position(start
);
for (int i =
1; i
< countryNames.
size() -
1; i++
) {
Mdr29Record from = countryNames.
get(i
);
Mdr29Record next = countryNames.
get(i +
1);
if (from.
getMdr26() ==
0)
continue;
info
("mdr26 from %d to %d; country %s", from.
getMdr26(), next.
getMdr26(),
from.
getName());
for (int record = from.
getMdr26(); record
< next.
getMdr26(); record++
) {
if (reader.
position() >= end
) {
error
("mdr26 from %d; read beyond end of section", from.
getMdr26());
break;
}
int idx = read
(size
);
checkNotZero
(idx,
"%d: pointer zero", record
);
Mdr28Record region = regionNames.
get(idx
);
if (region
!=
null) {
info
("%d region %s; country %s", record, region.
getName(),
region.
getMdr14().
getName());
}
}
}
}
/**
* Cities sorted by region.
*/
private void check27
() {
setShowLogs
(print
[27]);
info
("mdr27 check");
Section section = getSection
(27);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
int size = Section.
numberOfBytes(getSection
(5).
getNumberOfRecords());
if (section.
getRecordSize() != size
) {
error
("mdr27 record size %d, bytes required for mdr5 is %d", section.
getRecordSize(), size
);
error
(" ... don't know how to deal with this version of the section");
return;
}
reader.
position(start
);
boolean mdr28HasName =
(sections.
get(28).
getMagic() & 0x1
) !=
0;
int lastMapNumber =
0;
String lastCityName =
"";
for (int i =
1; i
< regionNames.
size() -
1; i++
) {
Mdr28Record from = regionNames.
get(i
);
if (from.
getMdr27() ==
0)
continue;
Mdr28Record next = regionNames.
get(i +
1);
for (int j = i +
2; next.
getMdr27() ==
0; j++
)
next = regionNames.
get(j
);
assert next.
getMdr27() !=
0;
//checkNotZero(next.getMdr27(), "mdr27 %d; zero mdr27 pointer", from.getIndex());
String expectedName = from.
getName();
if (mdr28HasName
)
info
("mdr27 from %d to %d; expect region %s", from.
getMdr27(), next.
getMdr27(), expectedName
);
else
info
("mdr27 from %d to %d", from.
getMdr27(), next.
getMdr27());
for (int regionnum = from.
getMdr27(); regionnum
< next.
getMdr27() && reader.
position() < end
; regionnum++
) {
int val = read
(size
);
Mdr20Info info = cityMdr20.
get(val
);
String regionName = info.
regionName;
if (expectedName
!=
null || mdr28HasName
)
checkEqual
(expectedName, regionName,
"%d mdr27 region name", regionnum
);
String cityName = info.
cityName;
if (info.
mapNumber == lastMapNumber
&& cityName.
equals(lastCityName
))
error
("%d region %s; repeated city name %s in same map", regionnum, regionName, cityName
);
info
("%d region %s; map%d city %d %s", regionnum, regionName, info.
mapNumber, val,
cityName
);
lastCityName = cityName
;
lastMapNumber = info.
mapNumber;
}
}
}
private void check28
() {
setShowLogs
(print
[28]);
info
("mdr28 check");
Section section = getSection
(28);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
int magic = section.
getMagic();
int size21 = getSection
(21).
getBytesForRecords();
int size23 = getSection
(23).
getBytesForRecords();
int size27 = getSection
(27).
getBytesForRecords();
int strSize =
0;
if ((magic
& 0x1
) !=
0)
strSize =
(getSection
(15).
getLen() > 0xffffff
) ? 4 :
3;
int unkSize = section.
getRecordSize() -
(size21 + size23 + size27 + strSize
);
if (unkSize
!=
0) {
error
("section size has unexpected value, don't know how to interpret mdr28");
}
regionNames.
add(null);
reader.
position(start
);
int number =
0;
BoundChecker bound23 =
new BoundChecker
("mdr23", getSection
(23).
getNumberOfRecords());
BoundChecker bound21 =
new BoundChecker
("mdr21", getSection
(21).
getNumberOfRecords());
BoundChecker bound27 =
new BoundChecker
("mdr27", getSection
(27).
getNumberOfRecords());
while (reader.
position() < end
) {
number++
;
int m23 = read
(size23
);
bound23.
update(number, m23
);
String name =
null;
if (strSize
> 0)
name = readString
();
int m21 = read
(size21
);
bound21.
update(number, m21
);
int m27 = read
(size27
);
bound27.
update(number, m27
);
info
("%d m21 %d, m23 %d, m27 %d, %s", number, m21, m23, m27, name
);
Mdr28Record reg =
new Mdr28Record
();
reg.
setName(name
);
reg.
setMdr21(m21
);
reg.
setMdr23(m23
);
reg.
setMdr27(m27
);
Mdr14Record country =
new Mdr14Record
();
reg.
setMdr14(country
);
regionNames.
add(reg
);
if (unkSize
> 0)
read
(unkSize
);
}
Mdr28Record last =
new Mdr28Record
();
last.
setMdr21(getSection
(21).
getNumberOfRecords() +
1);
last.
setMdr23(getSection
(23).
getNumberOfRecords() +
1);
last.
setMdr27(getSection
(27).
getNumberOfRecords() +
1);
regionNames.
add(last
);
}
private void check29
() {
setShowLogs
(print
[29]);
info
("mdr29 check");
countryNames.
add(null);
Section section = getSection
(29);
if (checkEmpty
(section
))
return;
long start = section.
getStart();
long end = section.
getEnd();
int magic = section.
getMagic();
boolean hasStr =
(magic
& 0x1
) !=
0;
boolean has17 =
(magic
& 0x30
) !=
0;
boolean has26 =
(magic
& 0x8
) !=
0;
int size24 = getSection
(24).
getBytesForRecords();
int size22 = getSection
(22).
getBytesForRecords();
int size25 = getSection
(25).
getBytesForRecords();
int size17 =
(has17
) ? ((magic
& 0x30
) >> 4) :
0;
int size26 =
(has26
) ? getSection
(26).
getBytesForRecords() :
0;
reader.
position(start
);
int number =
0;
BoundChecker bound24 =
new BoundChecker
("mdr24", getSection
(24).
getNumberOfRecords());
BoundChecker bound22 =
new BoundChecker
("mdr22", getSection
(22).
getNumberOfRecords());
BoundChecker bound25 =
new BoundChecker
("mdr25", getSection
(25).
getNumberOfRecords());
BoundChecker bound26 = has26
? new BoundChecker
("mdr26", getSection
(26).
getNumberOfRecords()) :
null;
BoundChecker bound17 = has17
? new BoundChecker
("mdr17", getSection
(17).
getNumberOfRecords()) :
null;
while (reader.
position() < end
) {
number++
;
int m24 = read
(size24
);
bound24.
update(number, m24
);
String name =
"";
if (hasStr
)
name = readString
();
int m22 = read
(size22
);
bound22.
update(number, m22
);
int m25 = read
(size25
);
bound25.
update(number, m25
);
int m17_26 =
0; // Assume is either a 17 or 26 not both
if (has26
) {
m17_26 = read
(size26
);
bound26.
update(number, m17_26
);
}
if (has17
) {
m17_26 = read
(size17
);
bound17.
update(number, m17_26
);
}
info
("%d m22 %d, m24 %d, m25 %d, m%s %d, %s", number, m22, m24, m25,
has17
? "17" :
"26", m17_26, name
);
Mdr29Record c =
new Mdr29Record
();
c.
setName(name
);
c.
setMdr22(m22
);
c.
setMdr24(m24
);
c.
setMdr25(m25
);
c.
setMdr26(m17_26
);
countryNames.
add(c
);
}
Mdr29Record last =
new Mdr29Record
();
last.
setMdr22(getSection
(22).
getNumberOfRecords() +
1);
last.
setMdr24(getSection
(24).
getNumberOfRecords() +
1);
last.
setMdr25(getSection
(25).
getNumberOfRecords() +
1);
last.
setMdr26(getSection
(has17
? 17 :
26).
getNumberOfRecords() +
1);
countryNames.
add(last
);
}
/**
* Read in a map number, the number of bytes depends on the number of maps.
*
* @return A map number. If the number read is out of range then an exception is
* thrown.
*/
private int readMapNumber
() {
int mn
;
if (numberOfMaps
> 255)
mn = reader.
get2u();
else
mn = reader.
get1u();
if (mn
> numberOfMaps
) {
assert false :
"not a map number " + mn
;
}
return mn
;
}
private String readString
() {
int off
;
if (getSection
(15).
getLen() > 0xffffff
)
off = reader.
get4();
else
off = reader.
get3u();
String s = mdrStrings.
getTextString(off
);
if (off
!=
0 && s ==
null)
throw new IllegalArgumentException("did not find String for offset" + off
);
return s
;
}
private int read
(int n
) {
switch (n
) {
case 1:
return reader.
get1u();
case 2:
return reader.
get2u();
case 3:
return reader.
get3u();
case 4:
return reader.
get4();
}
throw new IllegalArgumentException("integer size must be 1 to 4");
}
/**
* Read mdr1 to obtain all the maps.
*/
private void readMaps
() {
Section section = getSection
(1);
long start = section.
getStart();
long end = section.
getEnd();
int magic = section.
getMagic();
reader.
position(start
);
int mapIndex =
0;
while (reader.
position() < end
) {
mapIndex++
;
int mapid = reader.
get4();
int offset =
0;
if ((magic
& 0x1
) !=
0) {
offset = reader.
get4();
}
// Read in the actual map tile and save enough information to help us
// cross check the mdr file.
details.
readMap(mapIndex, mapid, offset
);
}
}
/**
* Read in the complete header.
*/
private void readHeader
() {
readHeaderLen
();
codepage = reader.
get2u();
charSet = Sort.
charsetFromCodepage(codepage
);
// %%% TODO: might need to get the decoder if strings can be in Format6
Sort sort = SrtTextReader.
sortForCodepage(codepage
);
isMulti = sort.
isMulti();
collator = sort.
getCollator();
collator.
setStrength(Collator.
TERTIARY);
reader.
get2u(); // id1
reader.
get2u(); // id2
reader.
get2u(); // unknown
Section sect = addSection
(1,
true,
true);
numberOfMaps = sect.
getNumberOfRecords();
addSection
(2,
true,
true);
addSection
(3,
true,
true);
addSection
(4,
true,
true);
addSection
(5,
true,
true);
addSection
(6,
true,
true);
addSection
(7,
true,
true);
addSection
(8,
true,
true);
addSection
(9,
true,
true);
addSection
(10,
false,
true);
addSection
(11,
true,
true);
addSection
(12,
true,
true);
addSection
(13,
true,
true);
addSection
(14,
true,
true);
addSection
(15,
false,
false);
getSection
(15).
setMagic(reader.
get());
addSection
(16,
true,
true);
addSection
(17,
false,
true);
addSection
(18,
true,
true);
addSection
(19,
true,
true);
if (getHeaderLen
() <=
286) // 0x011e
return;
addSection
(20,
true,
true);
addSection
(21,
true,
true);
addSection
(22,
true,
true);
addSection
(23,
true,
true);
addSection
(24,
true,
true);
addSection
(25,
true,
true);
addSection
(26,
true,
true);
addSection
(27,
true,
true);
addSection
(28,
true,
true);
addSection
(29,
true,
true);
if (getHeaderLen
() <=
426) // 0x01aa
return;
addSection
(30,
true,
true);
addSection
(31,
false,
false);
addSection
(32,
true,
true);
addSection
(33,
false,
false);
addSection
(34,
true,
true);
addSection
(35,
true,
true);
addSection
(36,
true,
true);
addSection
(37,
true,
true);
addSection
(38,
true,
true);
addSection
(39,
true,
true);
addSection
(40,
true,
true);
//if (getHeaderLen() <= 568) // 0x0238
// return;
// looks like three sections, two with 4 ints, one with 5 ints
//if (getHeaderLen() <= 620) // 0x26c
// return;
// mostly zeros
//if (getHeaderLen() <= 668) // 0x029c
// return;
// possibly some sections, but mostly zeros so difficult to tell
//if (getHeaderLen() <= 700) // 0x02bc
// return;
// at 0x02dc, maybe 5 sections without magic or recsize
// haven't seen anything after 772 0x0304
}
/**
* Read the header for a section.
*
* @param number The section number.
* @param hasRecSize True if the section to be read has a record size field of two bytes. It
* will be read and saved.
* @param hasMagic True if the section has a flags field of 4 bytes. It will be read and saved.
* @return
*/
private Section addSection
(int number,
boolean hasRecSize,
boolean hasMagic
) {
assert number
!=
0;
int start = reader.
get4();
int len = reader.
get4();
Section section =
new Section
("", start, len
);
if (hasRecSize
) {
int recordSize = reader.
get2u();
section.
setRecordSize(recordSize
);
}
if (hasMagic
) {
int magic = reader.
get4();
section.
setMagic(magic
);
}
while (sections.
size() < number -
1)
sections.
add(null);
sections.
add(number -
1, section
);
return section
;
}
private static Map<Integer,
Integer> makeNumberMapping
(String name
) {
Map<Integer,
Integer> numberMap =
new HashMap<>();
if (name.
toLowerCase().
endsWith("_mdr.img")) {
String base = name.
substring(0, name.
length() -
8);
String[] look =
{
base +
".MDX",
base +
".mdx",
base.
toLowerCase() +
".MDX",
base.
toLowerCase() +
".mdx",
};
for (String fname : look
) {
RandomAccessFile raf =
null;
try {
raf =
new RandomAccessFile(fname,
"r");
ImgChannel chan =
new FileImgChannel
(raf.
getChannel());
MdxFileReader mdx =
new MdxFileReader
(chan
);
List<MapInfo
> maps = mdx.
getMaps();
for (MapInfo bl : maps
) {
int mapNumber = bl.
getMapname();
int hexNumber = bl.
getHexMapname();
numberMap.
put(hexNumber, mapNumber
);
}
break;
} catch (IOException e
) {
// can't find a usable mdx file, try the next
} finally {
Utils.
closeFile(raf
);
}
}
} else if (name.
toLowerCase().
endsWith(".img")) {
FileSystem fs =
null;
try {
fs = ImgFS.
openFs(name
);
List<DirectoryEntry
> list = fs.
list();
for (DirectoryEntry ent : list
) {
if (ent.
getExt().
equalsIgnoreCase("mps")) {
String fullName = ent.
getFullName();
ImgChannel r = fs.
open(fullName,
"r");
MpsFileReader mps =
new MpsFileReader
(r,
1252);
List<MapBlock
> maps = mps.
getMaps();
for (MapBlock bl : maps
) {
int mapNumber = bl.
getMapNumber();
int hexNumber = bl.
getHexNumber();
numberMap.
put(hexNumber, mapNumber
);
}
System.
out.
println("# Using mps file");
break;
}
}
} catch (FileNotFoundException e
) {
// There isn't a usable mps file.
} finally {
Utils.
closeFile(fs
);
}
}
return numberMap
;
}
private void setNumberMap
(Map<Integer,
Integer> numberMap
) {
this.
details.
setNumberMap(numberMap
);
}
public void extraArgs
(Map<String,
String> args
) {
String val = args.
remove("errors");
if (val
!=
null) {
// show errors only
setShowLogs
(false);
Arrays.
fill(print,
false);
}
val = args.
remove("print");
if (val
!=
null) {
// only print logs for the given sections
String[] strings = val.
split(",");
Arrays.
fill(print,
false);
for (String sect : strings
)
print
[Integer.
parseInt(sect
)] =
true;
}
Map<Integer,
Integer> numberMap = makeNumberMapping
(getName
());
setNumberMap
(numberMap
);
// Anything else is common or an error
System.
out.
println("remaining " + args
);
super.
extraArgs(args
);
}
public static void main
(String[] args
) {
runMain
(MdrCheck.
class,
"MDR", args
);
}
private class Name {
private final int mapNumber
;
private final String name
;
private int offset
;
private int prefix
;
private int suffix
;
private int b
;
private int s
;
private boolean hasOrders
;
Name(int mapNumber,
String name
) {
this.
mapNumber = mapNumber
;
this.
name = name
;
}
Name(int mapNumber,
String name,
int offset
) {
this.
mapNumber = mapNumber
;
this.
name = name
;
this.
offset = offset
;
// the given offset refers to the utf8 encoding, convert it to the position in the java string
int togo = offset
;
int nameOffset =
0;
while (togo
> 0) {
int c = name.
codePointAt(prefix + nameOffset
);
togo -= outSize
(c
);
nameOffset++
;
}
this.
offset = nameOffset
;
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 + offset, suffix
);
else
return name.
substring(prefix + offset
);
}
private String initialPart
() {
int off = prefix + offset
;
if (off ==
0)
return "";
return name.
substring(0, off
);
}
private String suffixPart
() {
if (suffix ==
0)
return "";
return name.
substring(suffix, name.
length());
}
int compareInitial
(Name other
) {
String n1 =
this.
initialPart();
String n2 = other.
initialPart();
return collator.
compare(n1, n2
);
}
public boolean equals
(Object o
) {
if (this == o
) return true;
if (o ==
null || getClass
() != o.
getClass()) return false;
Name name1 =
(Name) o
;
if (mapNumber
!= name1.
mapNumber) return false;
if (offset
!= name1.
offset) return false;
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
() {
int result = mapNumber
;
result =
31 * result + name.
hashCode();
result =
31 * result + offset
;
return result
;
}
public String toString
() {
StringBuilder sb =
new StringBuilder(name
);
if (offset
!=
0)
sb.
insert(prefix + offset,
'|');
return sb.
toString();
}
int startOffset
() {
return prefix + offset
;
}
int compareSuffix
(Name other
) {
return collator.
compare(this.
suffixPart(), other.
suffixPart());
}
}
/**
* Return the number of bytes that the given character will consume in the output encoded
* format.
*/
private int outSize
(int c
) {
if (codepage ==
65001) {
// For unicode a simple lookup gives the number of bytes.
if (c
< 0x80
) {
return 1;
} else if (c
<= 0x7FF
) {
return 2;
} else if (c
<= 0xFFFF
) {
return 3;
} else if (c
<= 0x10FFFF
) {
return 4;
} else {
throw new IllegalArgumentException(String.
format("Invalid code point: 0x%x", c
));
}
} else if (!isMulti
) {
// The traditional single byte code-pages, always one byte.
return 1;
} else {
// Other multi-byte code-pages (eg ms932); can't currently create index for these anyway.
return 0;
}
}
private static class Mdr20Info
{
private int mdr20index
;
private String cityName
;
private int mapNumber
;
private String regionName
;
private String countryName
;
private Mdr20Info
(int mdr20index
) {
this.
mdr20index = mdr20index
;
}
boolean sameCity
(Mdr20Info prev
) {
return prev
!=
null
&& cityName.
equals(prev.
cityName)
&& regionName.
equals(prev.
regionName)
&& countryName.
equals(prev.
countryName);
}
}
private static class TypeInfo {
int type
;
int start
;
}
private static class TypeInfoMdr10
{
int type
;
int mdr11Rec
;
boolean isPolygon
;
}
private static int toType
(int group,
int typeOrSubType
) {
// see mkgmap imgfmt/app/mdr/MdrUtils.java:getGroupForPoi()
// and mkgmap imgfmt/app/mdr/Mdr10.java:addPoiType()
if (group ==
1)
return typeOrSubType
<< 8;
if (group
>=
64)
return typeOrSubType
;
int t = -
1;
if (group
>=
2 && group
<=
8)
t =
(group + 0x28
) << 8;
else if (group ==
9)
return 0x28
<< 8;
else if (group
>=
11 && group
<=
13)
t =
(group + 0x59
) << 8;
if (typeOrSubType
!=
0)
t |= typeOrSubType
;
return t
;
}
private boolean checkEmpty
(Section section
) {
if (section.
getLen() ==
0) {
info
("section is empty");
}
return section.
getLen() ==
0;
}
}