/*
* Copyright (C) 2013, Gerd Petermann
*
* 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 uk.me.parabola.splitter.solver;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import org.xmlpull.v1.XmlPullParserException;
import crosby.binary.file.BlockInputStream;
import uk.me.parabola.splitter.Area;
import uk.me.parabola.splitter.SplitFailedException;
import uk.me.parabola.splitter.Utils;
import uk.me.parabola.splitter.parser.BinaryMapParser;
import uk.me.parabola.splitter.parser.OSMXMLParser;
/**
* Reader for precompiled sea data. This is mostly a copy of the corresponding
* code in mkgmap SeaGenerator.
*
* @author GerdP
*
*/
public class PrecompSeaReader
{
/** The size (lat and long) of the precompiled sea tiles */
private static final int PRECOMP_RASTER =
1 << 15;
private static final byte SEA_TILE =
's';
private static final byte LAND_TILE =
'l';
private static final byte MIXED_TILE =
'm';
// useful constants defining the min/max map units of the precompiled sea
// tiles
private static final int MIN_LAT = Utils.
toMapUnit(-
90.0);
private static final int MAX_LAT = Utils.
toMapUnit(90.0);
private static final int MIN_LON = Utils.
toMapUnit(-
180.0);
private static final int MAX_LON = Utils.
toMapUnit(180.0);
private static final Pattern keySplitter =
Pattern.
compile(Pattern.
quote("_"));
private final Area bounds
;
private final File precompSeaDir
;
private byte[][] precompIndex
;
private String precompSeaExt
;
private String precompSeaPrefix
;
private String precompZipFileInternalPath
;
private ZipFile zipFile
;
public PrecompSeaReader
(Area bounds,
File precompSeaDir
) {
this.
bounds = bounds
;
this.
precompSeaDir = precompSeaDir
;
init
();
}
/**
* Process all precompiled sea tiles.
*
* @param processor
* The processor that is called
* @throws XmlPullParserException
*/
public void processMap
(DensityMapCollector processor
) throws XmlPullParserException
{
for (String tileName : getPrecompKeyNames
()) {
InputStream is = getStream
(tileName
);
if (is
!=
null) {
try {
if (tileName.
endsWith(".pbf")) {
BinaryMapParser binParser =
new BinaryMapParser
(processor,
null,
0);
BlockInputStream blockinput =
(new BlockInputStream
(is, binParser
));
blockinput.
process();
blockinput.
close();
} else {
// No, try XML.
try (Reader reader =
new InputStreamReader(is, StandardCharsets.
UTF_8)) {
OSMXMLParser parser =
new OSMXMLParser
(processor,
true);
parser.
setReader(reader
);
parser.
parse();
}
}
} catch (Exception e
) {
e.
printStackTrace();
throw new SplitFailedException
(e.
getMessage());
}
}
}
}
/**
* Read the index and set corresponding fields.
*/
private void init
() {
if (precompSeaDir.
exists()) {
String internalPath =
null;
String indexFileName =
"index.txt.gz";
try {
if (precompSeaDir.
isDirectory()) {
File indexFile =
new File(precompSeaDir, indexFileName
);
if (!indexFile.
exists()) {
// check if the unzipped index file exists
indexFileName =
"index.txt";
indexFile =
new File(precompSeaDir, indexFileName
);
}
if (indexFile.
exists()) {
try (InputStream indexStream =
new FileInputStream(indexFile
)) {
loadIndex
(indexStream, indexFileName
);
}
} else {
throw new IllegalArgumentException("Cannot find required index.txt[.gz] in " + precompSeaDir
);
}
} else if (precompSeaDir.
getName().
endsWith(".zip")) {
zipFile =
new ZipFile(precompSeaDir
);
internalPath =
"sea/";
ZipEntry entry = zipFile.
getEntry(internalPath + indexFileName
);
if (entry ==
null) {
indexFileName =
"index.txt";
entry = zipFile.
getEntry(internalPath + indexFileName
);
}
if (entry ==
null) {
internalPath =
"";
indexFileName =
"index.txt.gz";
entry = zipFile.
getEntry(internalPath + indexFileName
);
}
if (entry
!=
null) {
try (InputStream indexStream = zipFile.
getInputStream(entry
)) {
precompZipFileInternalPath = internalPath
;
loadIndex
(indexStream, indexFileName
);
}
} else {
throw new SplitFailedException
("Don't know how to read " + precompSeaDir
);
}
} else {
throw new SplitFailedException
("Don't know how to read " + precompSeaDir
);
}
} catch (IOException exp
) {
exp.
printStackTrace();
throw new SplitFailedException
("Cannot read index file " + indexFileName
);
}
} else {
throw new SplitFailedException
(
"Directory or zip file with precompiled sea does not exist: " + precompSeaDir.
getName());
}
}
private void loadIndex
(InputStream indexStream,
String indexFileName
) throws IOException {
if (indexFileName.
endsWith(".gz")) {
try (InputStream stream =
new GZIPInputStream(indexStream
)) {
loadIndexFromStream
(stream
);
return;
}
}
loadIndexFromStream
(indexStream
);
}
/**
* Read the index from stream and populate the index grid.
*
* @param fileStream
* already opened stream
*/
private void loadIndexFromStream
(InputStream fileStream
) throws IOException {
int indexWidth =
(PrecompSeaReader.
getPrecompTileStart(MAX_LON
) - PrecompSeaReader.
getPrecompTileStart(MIN_LON
))
/ PrecompSeaReader.
PRECOMP_RASTER;
int indexHeight =
(PrecompSeaReader.
getPrecompTileStart(MAX_LAT
)
- PrecompSeaReader.
getPrecompTileStart(MIN_LAT
)) / PrecompSeaReader.
PRECOMP_RASTER;
LineNumberReader indexReader =
new LineNumberReader(new InputStreamReader(fileStream
));
Pattern csvSplitter =
Pattern.
compile(Pattern.
quote(";"));
String indexLine =
null;
byte[][] indexGrid =
new byte[indexWidth +
1][indexHeight +
1];
boolean detectExt =
true;
String prefix =
null;
String ext =
null;
while ((indexLine = indexReader.
readLine()) !=
null) {
if (indexLine.
startsWith("#")) {
// comment
continue;
}
String[] items = csvSplitter.
split(indexLine
);
if (items.
length !=
2) {
throw new IllegalArgumentException("Invalid format in index file name: " + indexLine
);
}
String precompKey = items
[0];
byte type = updatePrecompSeaTileIndex
(precompKey, items
[1], indexGrid
);
if (type ==
'?') {
throw new IllegalArgumentException("Invalid format in index file name: " + indexLine
);
}
if (type == MIXED_TILE
) {
// make sure that all file names are using the same name scheme
int prePos = items
[1].
indexOf(items
[0]);
if (prePos
>=
0) {
if (detectExt
) {
prefix = items
[1].
substring(0, prePos
);
ext = items
[1].
substring(prePos + items
[0].
length());
detectExt =
false;
} else {
StringBuilder sb =
new StringBuilder(prefix
);
sb.
append(precompKey
);
sb.
append(ext
);
if (!items
[1].
equals(sb.
toString())) {
throw new IllegalArgumentException("Unexpected file name in index file: " + indexLine
);
}
}
}
}
}
//
precompIndex = indexGrid
;
precompSeaPrefix = prefix
;
precompSeaExt = ext
;
}
private InputStream getStream
(String tileName
) {
InputStream is =
null;
try {
if (zipFile
!=
null) {
ZipEntry entry = zipFile.
getEntry(precompZipFileInternalPath + tileName
);
if (entry
!=
null) {
is = zipFile.
getInputStream(entry
);
} else {
throw new IOException("Preompiled sea tile " + tileName +
" not found.");
}
} else {
File precompTile =
new File(precompSeaDir, tileName
);
is =
new FileInputStream(precompTile
);
}
} catch (Exception exp
) {
exp.
printStackTrace();
throw new SplitFailedException
(exp.
getMessage());
}
return is
;
}
/**
* Retrieves the start value of the precompiled tile.
*
* @param value
* the value for which the start value is calculated
* @return the tile start value
*/
private static int getPrecompTileStart
(int value
) {
int rem = value
% PRECOMP_RASTER
;
if (rem ==
0) {
return value
;
} else if (value
>=
0) {
return value - rem
;
} else {
return value - PRECOMP_RASTER - rem
;
}
}
/**
* Retrieves the end value of the precompiled tile.
*
* @param value
* the value for which the end value is calculated
* @return the tile end value
*/
private static int getPrecompTileEnd
(int value
) {
int rem = value
% PRECOMP_RASTER
;
if (rem ==
0) {
return value
;
} else if (value
>=
0) {
return value + PRECOMP_RASTER - rem
;
} else {
return value - rem
;
}
}
/**
* Calculates the key names of the precompiled sea tiles for the bounding
* box. The key names are compiled of {@code lat+"_"+lon}.
*
* @return the key names for the bounding box
*/
private List<String> getPrecompKeyNames
() {
List<String> precompKeys =
new ArrayList<>();
for (int lat = getPrecompTileStart
(bounds.
getMinLat()); lat
< getPrecompTileEnd
(
bounds.
getMaxLat()); lat += PRECOMP_RASTER
) {
for (int lon = getPrecompTileStart
(bounds.
getMinLong()); lon
< getPrecompTileEnd
(
bounds.
getMaxLong()); lon += PRECOMP_RASTER
) {
int latIndex =
(MAX_LAT - lat
) / PRECOMP_RASTER
;
int lonIndex =
(MAX_LON - lon
) / PRECOMP_RASTER
;
byte type = precompIndex
[lonIndex
][latIndex
];
if (type == MIXED_TILE
)
precompKeys.
add(precompSeaPrefix + lat +
"_" + lon + precompSeaExt
);
}
}
return precompKeys
;
}
/**
* Update the index grid for the element identified by precompKey.
*
* @param precompKey
* The key name is compiled of {@code lat+"_"+lon}.
* @param fileName
* either "land", "sea", or a file name containing OSM data
* @param indexGrid
* the previously allocated index grid
* @return the byte that was saved in the index grid
*/
private static byte updatePrecompSeaTileIndex
(String precompKey,
String fileName,
byte[][] indexGrid
) {
String[] tileCoords = keySplitter.
split(precompKey
);
byte type =
'?';
if (tileCoords.
length ==
2) {
int lat =
Integer.
parseInt(tileCoords
[0]);
int lon =
Integer.
parseInt(tileCoords
[1]);
int latIndex =
(MAX_LAT - lat
) / PRECOMP_RASTER
;
int lonIndex =
(MAX_LON - lon
) / PRECOMP_RASTER
;
if ("sea".
equals(fileName
))
type = SEA_TILE
;
else if ("land".
equals(fileName
))
type = LAND_TILE
;
else
type = MIXED_TILE
;
indexGrid
[lonIndex
][latIndex
] = type
;
}
return type
;
}
}