mapsforge: estimate whether a closed way is line or polygon

- refactor projection
- cleanups
This commit is contained in:
Hannes Janetzek 2014-11-04 19:02:43 +01:00
parent ef34ba9766
commit 68fe1f8b8a

View File

@ -1,6 +1,6 @@
/* /*
* Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2013 Hannes Janetzek * Copyright 2013, 2014 Hannes Janetzek
* *
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
* *
@ -23,7 +23,6 @@ import static org.oscim.tiling.ITileDataSink.QueryResult.SUCCESS;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import org.oscim.core.GeometryBuffer;
import org.oscim.core.GeometryBuffer.GeometryType; import org.oscim.core.GeometryBuffer.GeometryType;
import org.oscim.core.MapElement; import org.oscim.core.MapElement;
import org.oscim.core.MercatorProjection; import org.oscim.core.MercatorProjection;
@ -140,11 +139,33 @@ public class MapDatabase implements ITileDataSource {
private final MapElement mElem = new MapElement(); private final MapElement mElem = new MapElement();
private int minLat, minLon; private int minDeltaLat, minDeltaLon;
private Tile mTile;
private final TileProjection mTileProjection;
private final TileClipper mTileClipper;
private final MapFileTileSource mTileSource; private final MapFileTileSource mTileSource;
public MapDatabase(MapFileTileSource tileSource) throws IOException {
mTileSource = tileSource;
try {
/* open the file in read only mode */
mInputFile = new RandomAccessFile(tileSource.mapFile, "r");
mFileSize = mInputFile.length();
mReadBuffer = new ReadBuffer(mInputFile);
} catch (IOException e) {
log.error(e.getMessage());
/* make sure that the file is closed */
dispose();
throw new IOException();
}
mTileProjection = new TileProjection();
mTileClipper = new TileClipper(-2, -2, Tile.SIZE + 2, Tile.SIZE + 2);
}
@Override @Override
public void query(MapTile tile, ITileDataSink sink) { public void query(MapTile tile, ITileDataSink sink) {
@ -157,28 +178,29 @@ public class MapDatabase implements ITileDataSource {
mIntBuffer = new int[MAXIMUM_WAY_NODES_SEQUENCE_LENGTH * 2]; mIntBuffer = new int[MAXIMUM_WAY_NODES_SEQUENCE_LENGTH * 2];
try { try {
mTile = tile; mTileProjection.setTile(tile);
//mTile = tile;
// size of tile in map coordinates; /* size of tile in map coordinates; */
double size = 1.0 / (1 << tile.zoomLevel); double size = 1.0 / (1 << tile.zoomLevel);
// simplification tolerance /* simplification tolerance */
int pixel = (tile.zoomLevel > 11) ? 1 : 2; int pixel = (tile.zoomLevel > 11) ? 1 : 2;
int simplify = Tile.SIZE / pixel; int simplify = Tile.SIZE / pixel;
// translate screen pixel for tile to latitude and longitude /* translate screen pixel for tile to latitude and longitude
// tolerance for point reduction before projection. * tolerance for point reduction before projection. */
minLat = (int) (Math.abs(MercatorProjection.toLatitude(tile.y + size) minDeltaLat = (int) (Math.abs(MercatorProjection.toLatitude(tile.y + size)
- MercatorProjection.toLatitude(tile.y)) * 1e6) / simplify; - MercatorProjection.toLatitude(tile.y)) * 1e6) / simplify;
minLon = (int) (Math.abs(MercatorProjection.toLongitude(tile.x + size) minDeltaLon = (int) (Math.abs(MercatorProjection.toLongitude(tile.x + size)
- MercatorProjection.toLongitude(tile.x)) * 1e6) / simplify; - MercatorProjection.toLongitude(tile.x)) * 1e6) / simplify;
QueryParameters queryParameters = new QueryParameters(); QueryParameters queryParameters = new QueryParameters();
queryParameters.queryZoomLevel = queryParameters.queryZoomLevel =
mTileSource.fileHeader.getQueryZoomLevel(tile.zoomLevel); mTileSource.fileHeader.getQueryZoomLevel(tile.zoomLevel);
// get and check the sub-file for the query zoom level /* get and check the sub-file for the query zoom level */
SubFileParameter subFileParameter = SubFileParameter subFileParameter =
mTileSource.fileHeader.getSubFileParameter(queryParameters.queryZoomLevel); mTileSource.fileHeader.getSubFileParameter(queryParameters.queryZoomLevel);
@ -202,22 +224,6 @@ public class MapDatabase implements ITileDataSource {
sink.completed(SUCCESS); sink.completed(SUCCESS);
} }
public MapDatabase(MapFileTileSource tileSource) throws IOException {
mTileSource = tileSource;
try {
// open the file in read only mode
mInputFile = new RandomAccessFile(tileSource.mapFile, "r");
mFileSize = mInputFile.length();
mReadBuffer = new ReadBuffer(mInputFile);
} catch (IOException e) {
log.error(e.getMessage());
// make sure that the file is closed
dispose();
throw new IOException();
}
}
@Override @Override
public void dispose() { public void dispose() {
mReadBuffer = null; mReadBuffer = null;
@ -272,7 +278,7 @@ public class MapDatabase implements ITileDataSource {
int poisOnQueryZoomLevel = zoomTable[zoomTableRow][0]; int poisOnQueryZoomLevel = zoomTable[zoomTableRow][0];
int waysOnQueryZoomLevel = zoomTable[zoomTableRow][1]; int waysOnQueryZoomLevel = zoomTable[zoomTableRow][1];
// get the relative offset to the first stored way in the block /* get the relative offset to the first stored way in the block */
int firstWayOffset = mReadBuffer.readUnsignedInt(); int firstWayOffset = mReadBuffer.readUnsignedInt();
if (firstWayOffset < 0) { if (firstWayOffset < 0) {
log.warn(INVALID_FIRST_WAY_OFFSET + firstWayOffset); log.warn(INVALID_FIRST_WAY_OFFSET + firstWayOffset);
@ -282,7 +288,7 @@ public class MapDatabase implements ITileDataSource {
return; return;
} }
// add the current buffer position to the relative first way offset /* add the current buffer position to the relative first way offset */
firstWayOffset += mReadBuffer.getBufferPosition(); firstWayOffset += mReadBuffer.getBufferPosition();
if (firstWayOffset > mReadBuffer.getBufferSize()) { if (firstWayOffset > mReadBuffer.getBufferSize()) {
log.warn(INVALID_FIRST_WAY_OFFSET + firstWayOffset); log.warn(INVALID_FIRST_WAY_OFFSET + firstWayOffset);
@ -296,7 +302,7 @@ public class MapDatabase implements ITileDataSource {
return; return;
} }
// finished reading POIs, check if the current buffer position is valid /* finished reading POIs, check if the current buffer position is valid */
if (mReadBuffer.getBufferPosition() > firstWayOffset) { if (mReadBuffer.getBufferPosition() > firstWayOffset) {
log.warn("invalid buffer position: " + mReadBuffer.getBufferPosition()); log.warn("invalid buffer position: " + mReadBuffer.getBufferPosition());
if (mDebugFile) { if (mDebugFile) {
@ -305,7 +311,7 @@ public class MapDatabase implements ITileDataSource {
return; return;
} }
// move the pointer to the first way /* move the pointer to the first way */
mReadBuffer.setBufferPosition(firstWayOffset); mReadBuffer.setBufferPosition(firstWayOffset);
if (!processWays(queryParameters, mapDataSink, waysOnQueryZoomLevel)) { if (!processWays(queryParameters, mapDataSink, waysOnQueryZoomLevel)) {
@ -323,21 +329,21 @@ public class MapDatabase implements ITileDataSource {
boolean queryIsWater = true; boolean queryIsWater = true;
// boolean queryReadWaterInfo = false; // boolean queryReadWaterInfo = false;
// read and process all blocks from top to bottom and from left to right /* read and process all blocks from top to bottom and from left to right */
for (long row = queryParameters.fromBlockY; row <= queryParameters.toBlockY; row++) { for (long row = queryParameters.fromBlockY; row <= queryParameters.toBlockY; row++) {
for (long column = queryParameters.fromBlockX; column <= queryParameters.toBlockX; column++) { for (long column = queryParameters.fromBlockX; column <= queryParameters.toBlockX; column++) {
mCurrentCol = column - queryParameters.fromBlockX; mCurrentCol = column - queryParameters.fromBlockX;
mCurrentRow = row - queryParameters.fromBlockY; mCurrentRow = row - queryParameters.fromBlockY;
// calculate the actual block number of the needed block in the /* calculate the actual block number of the needed block in the
// file * file */
long blockNumber = row * subFileParameter.blocksWidth + column; long blockNumber = row * subFileParameter.blocksWidth + column;
// get the current index entry /* get the current index entry */
long currentBlockIndexEntry = long currentBlockIndexEntry =
mTileSource.databaseIndexCache.getIndexEntry(subFileParameter, blockNumber); mTileSource.databaseIndexCache.getIndexEntry(subFileParameter, blockNumber);
// check if the current query would still return a water tile /* check if the current query would still return a water tile */
if (queryIsWater) { if (queryIsWater) {
// check the water flag of the current block in its index // check the water flag of the current block in its index
// entry // entry
@ -345,7 +351,7 @@ public class MapDatabase implements ITileDataSource {
// queryReadWaterInfo = true; // queryReadWaterInfo = true;
} }
// get and check the current block pointer /* get and check the current block pointer */
long currentBlockPointer = currentBlockIndexEntry & BITMASK_INDEX_OFFSET; long currentBlockPointer = currentBlockIndexEntry & BITMASK_INDEX_OFFSET;
if (currentBlockPointer < 1 if (currentBlockPointer < 1
|| currentBlockPointer > subFileParameter.subFileSize) { || currentBlockPointer > subFileParameter.subFileSize) {
@ -355,12 +361,12 @@ public class MapDatabase implements ITileDataSource {
} }
long nextBlockPointer; long nextBlockPointer;
// check if the current block is the last block in the file /* check if the current block is the last block in the file */
if (blockNumber + 1 == subFileParameter.numberOfBlocks) { if (blockNumber + 1 == subFileParameter.numberOfBlocks) {
// set the next block pointer to the end of the file /* set the next block pointer to the end of the file */
nextBlockPointer = subFileParameter.subFileSize; nextBlockPointer = subFileParameter.subFileSize;
} else { } else {
// get and check the next block pointer /* get and check the next block pointer */
nextBlockPointer = nextBlockPointer =
mTileSource.databaseIndexCache.getIndexEntry(subFileParameter, mTileSource.databaseIndexCache.getIndexEntry(subFileParameter,
blockNumber + 1) blockNumber + 1)
@ -373,18 +379,18 @@ public class MapDatabase implements ITileDataSource {
} }
} }
// calculate the size of the current block /* calculate the size of the current block */
int currentBlockSize = (int) (nextBlockPointer - currentBlockPointer); int currentBlockSize = (int) (nextBlockPointer - currentBlockPointer);
if (currentBlockSize < 0) { if (currentBlockSize < 0) {
log.warn("current block size must not be negative: " log.warn("current block size must not be negative: "
+ currentBlockSize); + currentBlockSize);
return; return;
} else if (currentBlockSize == 0) { } else if (currentBlockSize == 0) {
// the current block is empty, continue with the next block /* the current block is empty, continue with the next block */
continue; continue;
} else if (currentBlockSize > ReadBuffer.MAXIMUM_BUFFER_SIZE) { } else if (currentBlockSize > ReadBuffer.MAXIMUM_BUFFER_SIZE) {
// the current block is too large, continue with the next /* the current block is too large, continue with the next
// block * block */
log.warn("current block size too large: " + currentBlockSize); log.warn("current block size too large: " + currentBlockSize);
continue; continue;
} else if (currentBlockPointer + currentBlockSize > mFileSize) { } else if (currentBlockPointer + currentBlockSize > mFileSize) {
@ -393,17 +399,17 @@ public class MapDatabase implements ITileDataSource {
return; return;
} }
// seek to the current block in the map file /* seek to the current block in the map file */
mInputFile.seek(subFileParameter.startAddress + currentBlockPointer); mInputFile.seek(subFileParameter.startAddress + currentBlockPointer);
// read the current block into the buffer /* read the current block into the buffer */
if (!mReadBuffer.readFromFile(currentBlockSize)) { if (!mReadBuffer.readFromFile(currentBlockSize)) {
// skip the current block /* skip the current block */
log.warn("reading current block has failed: " + currentBlockSize); log.warn("reading current block has failed: " + currentBlockSize);
return; return;
} }
// calculate the top-left coordinates of the underlying tile /* calculate the top-left coordinates of the underlying tile */
double tileLatitudeDeg = double tileLatitudeDeg =
Projection.tileYToLatitude(subFileParameter.boundaryTileTop + row, Projection.tileYToLatitude(subFileParameter.boundaryTileTop + row,
subFileParameter.baseZoomLevel); subFileParameter.baseZoomLevel);
@ -438,7 +444,7 @@ public class MapDatabase implements ITileDataSource {
*/ */
private boolean processBlockSignature() { private boolean processBlockSignature() {
if (mDebugFile) { if (mDebugFile) {
// get and check the block signature /* get and check the block signature */
mSignatureBlock = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_BLOCK); mSignatureBlock = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_BLOCK);
if (!mSignatureBlock.startsWith("###TileStart")) { if (!mSignatureBlock.startsWith("###TileStart")) {
log.warn("invalid block signature: " + mSignatureBlock); log.warn("invalid block signature: " + mSignatureBlock);
@ -460,21 +466,13 @@ public class MapDatabase implements ITileDataSource {
*/ */
private boolean processPOIs(ITileDataSink mapDataSink, int numberOfPois) { private boolean processPOIs(ITileDataSink mapDataSink, int numberOfPois) {
Tag[] poiTags = mTileSource.fileInfo.poiTags; Tag[] poiTags = mTileSource.fileInfo.poiTags;
MapElement e = mElem;
int numTags = 0; int numTags = 0;
long x = mTile.tileX * Tile.SIZE;
long y = mTile.tileY * Tile.SIZE + Tile.SIZE;
long z = Tile.SIZE << mTile.zoomLevel;
long dx = (x - (z >> 1));
long dy = (y - (z >> 1));
double divx = 180000000.0 / (z >> 1);
double divy = z / PIx4;
for (int elementCounter = numberOfPois; elementCounter != 0; --elementCounter) { for (int elementCounter = numberOfPois; elementCounter != 0; --elementCounter) {
if (mDebugFile) { if (mDebugFile) {
// get and check the POI signature /* get and check the POI signature */
mSignaturePoi = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_POI); mSignaturePoi = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_POI);
if (!mSignaturePoi.startsWith("***POIStart")) { if (!mSignaturePoi.startsWith("***POIStart")) {
log.warn("invalid POI signature: " + mSignaturePoi); log.warn("invalid POI signature: " + mSignaturePoi);
@ -483,88 +481,77 @@ public class MapDatabase implements ITileDataSource {
} }
} }
// get the POI latitude offset (VBE-S) /* get the POI latitude offset (VBE-S) */
int latitude = mTileLatitude + mReadBuffer.readSignedInt(); int latitude = mTileLatitude + mReadBuffer.readSignedInt();
/* get the POI longitude offset (VBE-S) */
// get the POI longitude offset (VBE-S)
int longitude = mTileLongitude + mReadBuffer.readSignedInt(); int longitude = mTileLongitude + mReadBuffer.readSignedInt();
// get the special byte which encodes multiple flags /* get the special byte which encodes multiple flags */
byte specialByte = mReadBuffer.readByte(); byte specialByte = mReadBuffer.readByte();
// bit 1-4 represent the layer /* bit 1-4 represent the layer */
byte layer = (byte) ((specialByte & POI_LAYER_BITMASK) >>> POI_LAYER_SHIFT); byte layer = (byte) ((specialByte & POI_LAYER_BITMASK) >>> POI_LAYER_SHIFT);
// bit 5-8 represent the number of tag IDs
/* bit 5-8 represent the number of tag IDs */
byte numberOfTags = (byte) (specialByte & POI_NUMBER_OF_TAGS_BITMASK); byte numberOfTags = (byte) (specialByte & POI_NUMBER_OF_TAGS_BITMASK);
if (numberOfTags != 0) { if (numberOfTags != 0) {
if (!mReadBuffer.readTags(mElem.tags, poiTags, numberOfTags)) if (!mReadBuffer.readTags(e.tags, poiTags, numberOfTags))
return false; return false;
numTags = numberOfTags; numTags = numberOfTags;
} }
// reset to common tag position /* reset to common tag position */
mElem.tags.numTags = numTags; e.tags.numTags = numTags;
// get the feature bitmask (1 byte) /* get the feature bitmask (1 byte) */
byte featureByte = mReadBuffer.readByte(); byte featureByte = mReadBuffer.readByte();
// bit 1-3 enable optional features /* bit 1-3 enable optional features
// check if the POI has a name * check if the POI has a name */
if ((featureByte & POI_FEATURE_NAME) != 0) { if ((featureByte & POI_FEATURE_NAME) != 0) {
String str = mReadBuffer.readUTF8EncodedString(); String str = mReadBuffer.readUTF8EncodedString();
mElem.tags.add(new Tag(Tag.KEY_NAME, str, false)); e.tags.add(new Tag(Tag.KEY_NAME, str, false));
} }
// check if the POI has a house number /* check if the POI has a house number */
if ((featureByte & POI_FEATURE_HOUSE_NUMBER) != 0) { if ((featureByte & POI_FEATURE_HOUSE_NUMBER) != 0) {
// mReadBuffer.getPositionAndSkip(); // mReadBuffer.getPositionAndSkip();
// String str = // String str =
mReadBuffer.readUTF8EncodedString(); mReadBuffer.readUTF8EncodedString();
} }
// check if the POI has an elevation /* check if the POI has an elevation */
if ((featureByte & POI_FEATURE_ELEVATION) != 0) { if ((featureByte & POI_FEATURE_ELEVATION) != 0) {
mReadBuffer.readSignedInt(); mReadBuffer.readSignedInt();
// mReadBuffer.getPositionAndSkip();// tags.add(new // mReadBuffer.getPositionAndSkip();// tags.add(new
// Tag(Tag.TAG_KEY_ELE, // Tag(Tag.TAG_KEY_ELE,
// Integer.toString(mReadBuffer.readSignedInt()))); // Integer.toString(mReadBuffer.readSignedInt())));
} }
mTileProjection.projectPoint(latitude, longitude, e);
float lon = (float) (longitude / divx - dx); e.setLayer(layer);
double sinLat = Math.sin(latitude * PI180); mapDataSink.process(e);
float lat = Tile.SIZE
- (float) ((Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy));
mElem.clear();
mElem.setLayer(layer);
mElem.startPoints();
mElem.addPoint(lon, lat);
mapDataSink.process(mElem);
} }
return true; return true;
} }
private boolean processWayDataBlock(boolean doubleDeltaEncoding) { private boolean processWayDataBlock(MapElement e, boolean doubleDeltaEncoding, boolean isLine) {
// get and check the number of way coordinate blocks (VBE-U) /* get and check the number of way coordinate blocks (VBE-U) */
int numBlocks = mReadBuffer.readUnsignedInt(); int numBlocks = mReadBuffer.readUnsignedInt();
if (numBlocks < 1 || numBlocks > Short.MAX_VALUE) { if (numBlocks < 1 || numBlocks > Short.MAX_VALUE) {
log.warn("invalid number of way coordinate blocks: " + numBlocks); log.warn("invalid number of way coordinate blocks: " + numBlocks);
return false; return false;
} }
//short[] wayLengths = new short[numBlocks]; int[] wayLengths = e.ensureIndexSize(numBlocks, false);
int[] wayLengths = mElem.ensureIndexSize(numBlocks, false);
if (wayLengths.length > numBlocks) if (wayLengths.length > numBlocks)
wayLengths[numBlocks] = -1; wayLengths[numBlocks] = -1;
//mElem.pointPos = 0; /* read the way coordinate blocks */
// read the way coordinate blocks
for (int coordinateBlock = 0; coordinateBlock < numBlocks; ++coordinateBlock) { for (int coordinateBlock = 0; coordinateBlock < numBlocks; ++coordinateBlock) {
// get and check the number of way nodes (VBE-U) // get and check the number of way nodes (VBE-U)
int numWayNodes = mReadBuffer.readUnsignedInt(); int numWayNodes = mReadBuffer.readUnsignedInt();
@ -575,13 +562,13 @@ public class MapDatabase implements ITileDataSource {
return false; return false;
} }
// each way node consists of latitude and longitude /* each way node consists of latitude and longitude */
int len = numWayNodes * 2; int len = numWayNodes * 2;
if (doubleDeltaEncoding) { if (doubleDeltaEncoding) {
len = decodeWayNodesDoubleDelta(len); len = decodeWayNodesDoubleDelta(e, len, isLine);
} else { } else {
len = decodeWayNodesSingleDelta(len); len = decodeWayNodesSingleDelta(e, len, isLine);
} }
wayLengths[coordinateBlock] = (short) len; wayLengths[coordinateBlock] = (short) len;
} }
@ -589,19 +576,19 @@ public class MapDatabase implements ITileDataSource {
return true; return true;
} }
private int decodeWayNodesDoubleDelta(int length) { private int decodeWayNodesDoubleDelta(MapElement e, int length, boolean isLine) {
int[] buffer = mIntBuffer; int[] buffer = mIntBuffer;
mReadBuffer.readSignedInt(buffer, length); mReadBuffer.readSignedInt(buffer, length);
float[] outBuffer = mElem.ensurePointSize(mElem.pointPos + length, true); float[] outBuffer = e.ensurePointSize(e.pointPos + length, true);
int outPos = mElem.pointPos; int outPos = e.pointPos;
int lat, lon; int lat, lon;
// get the first way node latitude offset /* get the first way node latitude offset */
int firstLat = lat = mTileLatitude + buffer[0]; int firstLat = lat = mTileLatitude + buffer[0];
// get the first way node longitude offset /* get the first way node longitude offset */
int firstLon = lon = mTileLongitude + buffer[1]; int firstLon = lon = mTileLongitude + buffer[1];
outBuffer[outPos++] = lon; outBuffer[outPos++] = lon;
@ -619,45 +606,43 @@ public class MapDatabase implements ITileDataSource {
lon += deltaLon; lon += deltaLon;
if (pos == length - 2) { if (pos == length - 2) {
boolean line = (lon != firstLon && lat != firstLat); boolean line = isLine || (lon != firstLon && lat != firstLat);
// this also removes closed ways that are not polygon, /* this also removes closed ways that are not polygon,
// but how do we know? * but how do we know? */
if (line) { if (line) {
outBuffer[outPos++] = lon; outBuffer[outPos++] = lon;
outBuffer[outPos++] = lat; outBuffer[outPos++] = lat;
cnt += 2; cnt += 2;
} }
if (mElem.type == GeometryType.NONE) { if (e.type == GeometryType.NONE) {
mElem.type = line ? GeometryType.LINE : GeometryType.POLY; e.type = line ? GeometryType.LINE : GeometryType.POLY;
} }
} else if (deltaLon > minLon || deltaLon < -minLon } else if (deltaLon > minDeltaLon || deltaLon < -minDeltaLon
|| deltaLat > minLat || deltaLat < -minLat) { || deltaLat > minDeltaLat || deltaLat < -minDeltaLat) {
outBuffer[outPos++] = lon; outBuffer[outPos++] = lon;
outBuffer[outPos++] = lat; outBuffer[outPos++] = lat;
cnt += 2; cnt += 2;
} }
} }
//mReductionCnt += length - cnt; e.pointPos = outPos;
mElem.pointPos = outPos;
return cnt; return cnt;
} }
private int decodeWayNodesSingleDelta(int length) { private int decodeWayNodesSingleDelta(MapElement e, int length, boolean isLine) {
int[] buffer = mIntBuffer; int[] buffer = mIntBuffer;
mReadBuffer.readSignedInt(buffer, length); mReadBuffer.readSignedInt(buffer, length);
float[] outBuffer = mElem.ensurePointSize(mElem.pointPos + length, true); float[] outBuffer = e.ensurePointSize(e.pointPos + length, true);
int outPos = mElem.pointPos; int outPos = e.pointPos;
int lat, lon; int lat, lon;
// get the first way node latitude single-delta offset /* get the first way node latitude single-delta offset */
int firstLat = lat = mTileLatitude + buffer[0]; int firstLat = lat = mTileLatitude + buffer[0];
// get the first way node longitude single-delta offset /* get the first way node longitude single-delta offset */
int firstLon = lon = mTileLongitude + buffer[1]; int firstLon = lon = mTileLongitude + buffer[1];
outBuffer[outPos++] = lon; outBuffer[outPos++] = lon;
@ -672,7 +657,7 @@ public class MapDatabase implements ITileDataSource {
lon += deltaLon; lon += deltaLon;
if (pos == length - 2) { if (pos == length - 2) {
boolean line = (lon != firstLon && lat != firstLat); boolean line = isLine || (lon != firstLon && lat != firstLat);
if (line) { if (line) {
outBuffer[outPos++] = lon; outBuffer[outPos++] = lon;
@ -680,19 +665,18 @@ public class MapDatabase implements ITileDataSource {
cnt += 2; cnt += 2;
} }
if (mElem.type == GeometryType.NONE) if (e.type == GeometryType.NONE)
mElem.type = line ? GeometryType.LINE : GeometryType.POLY; e.type = line ? GeometryType.LINE : GeometryType.POLY;
} else if (deltaLon > minLon || deltaLon < -minLon } else if (deltaLon > minDeltaLon || deltaLon < -minDeltaLon
|| deltaLat > minLat || deltaLat < -minLat) { || deltaLat > minDeltaLat || deltaLat < -minDeltaLat) {
outBuffer[outPos++] = lon; outBuffer[outPos++] = lon;
outBuffer[outPos++] = lat; outBuffer[outPos++] = lat;
cnt += 2; cnt += 2;
} }
} }
//mReductionCnt += length - cnt; e.pointPos = outPos;
mElem.pointPos = outPos;
return cnt; return cnt;
} }
@ -715,6 +699,8 @@ public class MapDatabase implements ITileDataSource {
ITileDataSink mapDataSink, int numberOfWays) { ITileDataSink mapDataSink, int numberOfWays) {
Tag[] wayTags = mTileSource.fileInfo.wayTags; Tag[] wayTags = mTileSource.fileInfo.wayTags;
MapElement e = mElem;
int numTags = 0; int numTags = 0;
int wayDataBlocks; int wayDataBlocks;
@ -754,6 +740,7 @@ public class MapDatabase implements ITileDataSource {
if (mCurrentRow < numRows) if (mCurrentRow < numRows)
ymax = (int) (mCurrentRow * h + h); ymax = (int) (mCurrentRow * h + h);
//log.debug(xmin + " " + ymin + " " + xmax + " " + ymax); //log.debug(xmin + " " + ymin + " " + xmax + " " + ymax);
mTileClipper.setRect(xmin, ymin, xmax, ymax); mTileClipper.setRect(xmin, ymin, xmax, ymax);
} else { } else {
mTileClipper.setRect(-2, -2, Tile.SIZE + 2, Tile.SIZE + 2); mTileClipper.setRect(-2, -2, Tile.SIZE + 2, Tile.SIZE + 2);
@ -786,7 +773,7 @@ public class MapDatabase implements ITileDataSource {
byte numberOfTags = byte numberOfTags =
(byte) (mReadBuffer.readByte() & WAY_NUMBER_OF_TAGS_BITMASK); (byte) (mReadBuffer.readByte() & WAY_NUMBER_OF_TAGS_BITMASK);
if (!mReadBuffer.readTags(mElem.tags, wayTags, numberOfTags)) if (!mReadBuffer.readTags(e.tags, wayTags, numberOfTags))
return false; return false;
numTags = numberOfTags; numTags = numberOfTags;
@ -804,30 +791,30 @@ public class MapDatabase implements ITileDataSource {
return false; return false;
} }
// ignore the way tile bitmask (2 bytes) /* ignore the way tile bitmask (2 bytes) */
mReadBuffer.skipBytes(2); mReadBuffer.skipBytes(2);
} }
// get the special byte which encodes multiple flags /* get the special byte which encodes multiple flags */
byte specialByte = mReadBuffer.readByte(); byte specialByte = mReadBuffer.readByte();
// bit 1-4 represent the layer /* bit 1-4 represent the layer */
byte layer = (byte) ((specialByte & WAY_LAYER_BITMASK) >>> WAY_LAYER_SHIFT); byte layer = (byte) ((specialByte & WAY_LAYER_BITMASK) >>> WAY_LAYER_SHIFT);
// bit 5-8 represent the number of tag IDs /* bit 5-8 represent the number of tag IDs */
byte numberOfTags = (byte) (specialByte & WAY_NUMBER_OF_TAGS_BITMASK); byte numberOfTags = (byte) (specialByte & WAY_NUMBER_OF_TAGS_BITMASK);
if (numberOfTags != 0) { if (numberOfTags != 0) {
if (!mReadBuffer.readTags(mElem.tags, wayTags, numberOfTags)) if (!mReadBuffer.readTags(e.tags, wayTags, numberOfTags))
return false; return false;
numTags = numberOfTags; numTags = numberOfTags;
} }
// get the feature bitmask (1 byte) /* get the feature bitmask (1 byte) */
byte featureByte = mReadBuffer.readByte(); byte featureByte = mReadBuffer.readByte();
// bit 1-6 enable optional features /* bit 1-6 enable optional features */
boolean featureWayDoubleDeltaEncoding = boolean featureWayDoubleDeltaEncoding =
(featureByte & WAY_FEATURE_DOUBLE_DELTA_ENCODING) != 0; (featureByte & WAY_FEATURE_DOUBLE_DELTA_ENCODING) != 0;
@ -835,36 +822,36 @@ public class MapDatabase implements ITileDataSource {
boolean hasHouseNr = (featureByte & WAY_FEATURE_HOUSE_NUMBER) != 0; boolean hasHouseNr = (featureByte & WAY_FEATURE_HOUSE_NUMBER) != 0;
boolean hasRef = (featureByte & WAY_FEATURE_REF) != 0; boolean hasRef = (featureByte & WAY_FEATURE_REF) != 0;
mElem.tags.numTags = numTags; e.tags.numTags = numTags;
if (mTileSource.experimental) { if (mTileSource.experimental) {
if (hasName) { if (hasName) {
int textPos = mReadBuffer.readUnsignedInt(); int textPos = mReadBuffer.readUnsignedInt();
String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos); String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos);
mElem.tags.add(new Tag(Tag.KEY_NAME, str, false)); e.tags.add(new Tag(Tag.KEY_NAME, str, false));
} }
if (hasHouseNr) { if (hasHouseNr) {
int textPos = mReadBuffer.readUnsignedInt(); int textPos = mReadBuffer.readUnsignedInt();
String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos); String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos);
mElem.tags.add(new Tag(Tag.KEY_HOUSE_NUMBER, str, false)); e.tags.add(new Tag(Tag.KEY_HOUSE_NUMBER, str, false));
} }
if (hasRef) { if (hasRef) {
int textPos = mReadBuffer.readUnsignedInt(); int textPos = mReadBuffer.readUnsignedInt();
String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos); String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos);
mElem.tags.add(new Tag(Tag.KEY_REF, str, false)); e.tags.add(new Tag(Tag.KEY_REF, str, false));
} }
} else { } else {
if (hasName) { if (hasName) {
String str = mReadBuffer.readUTF8EncodedString(); String str = mReadBuffer.readUTF8EncodedString();
mElem.tags.add(new Tag(Tag.KEY_NAME, str, false)); e.tags.add(new Tag(Tag.KEY_NAME, str, false));
} }
if (hasHouseNr) { if (hasHouseNr) {
String str = mReadBuffer.readUTF8EncodedString(); String str = mReadBuffer.readUTF8EncodedString();
mElem.tags.add(new Tag(Tag.KEY_HOUSE_NUMBER, str, false)); e.tags.add(new Tag(Tag.KEY_HOUSE_NUMBER, str, false));
} }
if (hasRef) { if (hasRef) {
String str = mReadBuffer.readUTF8EncodedString(); String str = mReadBuffer.readUTF8EncodedString();
mElem.tags.add(new Tag(Tag.KEY_REF, str, false)); e.tags.add(new Tag(Tag.KEY_REF, str, false));
} }
} }
if ((featureByte & WAY_FEATURE_LABEL_POSITION) != 0) if ((featureByte & WAY_FEATURE_LABEL_POSITION) != 0)
@ -883,42 +870,49 @@ public class MapDatabase implements ITileDataSource {
wayDataBlocks = 1; wayDataBlocks = 1;
} }
for (int wayDataBlock = 0; wayDataBlock < wayDataBlocks; wayDataBlock++) { boolean linearFeature = e.tags.containsKey("highway") ||
mElem.clear(); e.tags.containsKey("boundary") ||
e.tags.containsKey("railway");
if (linearFeature) {
Tag areaTag = e.tags.get("area");
if (areaTag != null && areaTag.value == Tag.VALUE_YES)
linearFeature = false;
}
if (!processWayDataBlock(featureWayDoubleDeltaEncoding)) for (int wayDataBlock = 0; wayDataBlock < wayDataBlocks; wayDataBlock++) {
e.clear();
if (!processWayDataBlock(e, featureWayDoubleDeltaEncoding, linearFeature))
return false; return false;
if (mElem.isPoly() && mElem.index[0] < 6) { /* drop invalid outer ring */
//mSkipPoly++; if (e.isPoly() && e.index[0] < 6) {
continue; continue;
} }
projectToTile(mElem); mTileProjection.project(e);
if (mElem.isPoly()) { if (e.isPoly()) {
if (!mTileClipper.clip(mElem)) { if (!mTileClipper.clip(e)) {
continue; continue;
} }
} }
mElem.setLayer(layer); e.setLayer(layer);
mapDataSink.process(mElem); mapDataSink.process(e);
} }
} }
return true; return true;
} }
private final TileClipper mTileClipper = new TileClipper(-2, -2, Tile.SIZE + 2, Tile.SIZE + 2);
private float[] readOptionalLabelPosition() { private float[] readOptionalLabelPosition() {
float[] labelPosition = new float[2]; float[] labelPosition = new float[2];
// get the label position latitude offset (VBE-S) /* get the label position latitude offset (VBE-S) */
labelPosition[1] = mTileLatitude + mReadBuffer.readSignedInt(); labelPosition[1] = mTileLatitude + mReadBuffer.readSignedInt();
// get the label position longitude offset (VBE-S) /* get the label position longitude offset (VBE-S) */
labelPosition[0] = mTileLongitude + mReadBuffer.readSignedInt(); labelPosition[0] = mTileLongitude + mReadBuffer.readSignedInt();
return labelPosition; return labelPosition;
@ -960,65 +954,92 @@ public class MapDatabase implements ITileDataSource {
return zoomTable; return zoomTable;
} }
private static final double PI180 = (Math.PI / 180) / 1000000.0; static class TileProjection {
private static final double PIx4 = Math.PI * 4; private static final double COORD_SCALE = 1000000.0;
private boolean projectToTile(GeometryBuffer geom) { long dx, dy;
double divx, divy;
float[] coords = geom.points; void setTile(Tile tile) {
int[] indices = geom.index; /* tile position in pixels at tile zoom */
long x = tile.tileX * Tile.SIZE;
long y = tile.tileY * Tile.SIZE + Tile.SIZE;
long x = mTile.tileX * Tile.SIZE; /* size of the map in pixel at tile zoom */
long y = mTile.tileY * Tile.SIZE + Tile.SIZE; long mapExtents = Tile.SIZE << tile.zoomLevel;
long z = Tile.SIZE << mTile.zoomLevel;
double divx, divy = 0; /* offset relative to lat/lon == 0 */
long dx = (x - (z >> 1)); dx = (x - (mapExtents >> 1));
long dy = (y - (z >> 1)); dy = (y - (mapExtents >> 1));
divx = 180000000.0 / (z >> 1); /* scales longitude(1e6) to map-pixel */
divy = z / PIx4; divx = (180.0 * COORD_SCALE) / (mapExtents >> 1);
for (int pos = 0, outPos = 0, i = 0, m = indices.length; i < m; i++) { /* scale latidute to map-pixel */
int len = indices[i]; divy = (mapExtents >> 1) / (Math.PI * 2.0);
}
public void projectPoint(int lat, int lon, MapElement out) {
out.clear();
out.startPoints();
out.addPoint(projectLon(lon), projectLat(lat));
}
public float projectLat(double lat) {
double s = Math.sin(lat * ((Math.PI / 180) / COORD_SCALE));
double r = Math.log((1.0 + s) / (1.0 - s));
return Tile.SIZE - (float) (r * divy + dy);
}
public float projectLon(double lon) {
return (float) (lon / divx - dx);
}
void project(MapElement e) {
float[] coords = e.points;
int[] indices = e.index;
int inPos = 0;
int outPos = 0;
for (int idx = 0, m = indices.length; idx < m; idx++) {
int len = indices[idx];
if (len == 0) if (len == 0)
continue; continue;
if (len < 0) if (len < 0)
break; break;
int cnt = 0; float lat, lon, pLon = 0, pLat = 0;
float lat, lon, prevLon = 0, prevLat = 0; int cnt = 0, first = outPos;
int first = outPos;
for (int end = pos + len; pos < end; pos += 2) { for (int end = inPos + len; inPos < end; inPos += 2) {
lon = projectLon(coords[inPos]);
lon = (float) ((coords[pos]) / divx - dx); lat = projectLat(coords[inPos + 1]);
double sinLat = Math.sin(coords[pos + 1] * PI180);
lat = (float) (Tile.SIZE - (Math.log((1.0 + sinLat) / (1.0 - sinLat)) * divy + dy));
if (cnt != 0) { if (cnt != 0) {
// drop small distance intermediate nodes /* drop small distance intermediate nodes */
if (lat == prevLat && lon == prevLon) { if (lat == pLat && lon == pLon) {
//log.debug("drop zero delta "); //log.debug("drop zero delta ");
continue; continue;
} }
} }
coords[outPos++] = prevLon = lon; coords[outPos++] = pLon = lon;
coords[outPos++] = prevLat = lat; coords[outPos++] = pLat = lat;
cnt += 2; cnt += 2;
} }
if (coords[first] == prevLon && coords[first + 1] == prevLat) { if (coords[first] == pLon && coords[first + 1] == pLat) {
//log.debug("drop closed"); /* remove identical start/end point */
indices[i] = (short) (cnt - 2); log.debug("would drop closing point {}", e);
outPos -= 2; //indices[idx] = (short) (cnt - 2);
//outPos -= 2;
}
//else {
indices[idx] = (short) cnt;
//}
} }
else
indices[i] = (short) cnt;
} }
return true;
} }
} }