/* * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2013, 2014 Hannes Janetzek * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * * This program is free software: you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License as published by the Free Software * Foundation, either version 3 of the License, or (at your option) any later version. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package org.oscim.tiling.source.mapfile; import static org.oscim.core.GeometryBuffer.GeometryType.LINE; import static org.oscim.core.GeometryBuffer.GeometryType.POLY; import static org.oscim.tiling.ITileDataSink.QueryResult.FAILED; import static org.oscim.tiling.ITileDataSink.QueryResult.SUCCESS; import java.io.IOException; import java.io.RandomAccessFile; import org.oscim.core.GeometryBuffer.GeometryType; import org.oscim.core.MapElement; import org.oscim.core.MercatorProjection; import org.oscim.core.Tag; import org.oscim.core.Tile; import org.oscim.layers.tile.MapTile; import org.oscim.tiling.ITileDataSink; import org.oscim.tiling.ITileDataSource; import org.oscim.tiling.source.mapfile.header.SubFileParameter; import org.oscim.utils.geom.TileClipper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A class for reading binary map files. * * @see <a * href="http://code.google.com/p/mapsforge/wiki/SpecificationBinaryMapFile">Specification</a> */ public class MapDatabase implements ITileDataSource { /** Bitmask to extract the block offset from an index entry. */ private static final long BITMASK_INDEX_OFFSET = 0x7FFFFFFFFFL; /** Bitmask to extract the water information from an index entry. */ private static final long BITMASK_INDEX_WATER = 0x8000000000L; /** Debug message prefix for the block signature. */ private static final String DEBUG_SIGNATURE_BLOCK = "block signature: "; /** Debug message prefix for the POI signature. */ // private static final String DEBUG_SIGNATURE_POI = "POI signature: "; /** Debug message prefix for the way signature. */ private static final String DEBUG_SIGNATURE_WAY = "way signature: "; /** Error message for an invalid first way offset. */ private static final String INVALID_FIRST_WAY_OFFSET = "invalid first way offset: "; static final Logger log = LoggerFactory.getLogger(MapDatabase.class); /** Maximum way nodes sequence length which is considered as valid. */ private static final int MAXIMUM_WAY_NODES_SEQUENCE_LENGTH = 8192; /** * Maximum number of map objects in the zoom table which is considered as * valid. */ private static final int MAXIMUM_ZOOM_TABLE_OBJECTS = 65536 * 2; /** Bitmask for the optional POI feature "elevation". */ private static final int POI_FEATURE_ELEVATION = 0x20; /** Bitmask for the optional POI feature "house number". */ private static final int POI_FEATURE_HOUSE_NUMBER = 0x40; /** Bitmask for the optional POI feature "name". */ private static final int POI_FEATURE_NAME = 0x80; /** Bitmask for the POI layer. */ private static final int POI_LAYER_BITMASK = 0xf0; /** Bit shift for calculating the POI layer. */ private static final int POI_LAYER_SHIFT = 4; /** Bitmask for the number of POI tags. */ private static final int POI_NUMBER_OF_TAGS_BITMASK = 0x0f; /** Length of the debug signature at the beginning of each block. */ private static final byte SIGNATURE_LENGTH_BLOCK = 32; /** Length of the debug signature at the beginning of each POI. */ private static final byte SIGNATURE_LENGTH_POI = 32; /** Length of the debug signature at the beginning of each way. */ private static final byte SIGNATURE_LENGTH_WAY = 32; /** Bitmask for the optional way data blocks byte. */ private static final int WAY_FEATURE_DATA_BLOCKS_BYTE = 0x08; /** Bitmask for the optional way double delta encoding. */ private static final int WAY_FEATURE_DOUBLE_DELTA_ENCODING = 0x04; /** Bitmask for the optional way feature "house number". */ private static final int WAY_FEATURE_HOUSE_NUMBER = 0x40; /** Bitmask for the optional way feature "label position". */ private static final int WAY_FEATURE_LABEL_POSITION = 0x10; /** Bitmask for the optional way feature "name". */ private static final int WAY_FEATURE_NAME = 0x80; /** Bitmask for the optional way feature "reference". */ private static final int WAY_FEATURE_REF = 0x20; /** Bitmask for the way layer. */ private static final int WAY_LAYER_BITMASK = 0xf0; /** Bit shift for calculating the way layer. */ private static final int WAY_LAYER_SHIFT = 4; /** Bitmask for the number of way tags. */ private static final int WAY_NUMBER_OF_TAGS_BITMASK = 0x0f; private long mFileSize; private boolean mDebugFile; private RandomAccessFile mInputFile; private ReadBuffer mReadBuffer; private String mSignatureBlock; private String mSignaturePoi; private String mSignatureWay; private int mTileLatitude; private int mTileLongitude; private int[] mIntBuffer; private final MapElement mElem = new MapElement(); private int minDeltaLat, minDeltaLon; private final TileProjection mTileProjection; private final TileClipper mTileClipper; 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(0, 0, 0, 0); } @Override public void query(MapTile tile, ITileDataSink sink) { if (mTileSource.fileHeader == null) { sink.completed(FAILED); return; } if (mIntBuffer == null) mIntBuffer = new int[MAXIMUM_WAY_NODES_SEQUENCE_LENGTH * 2]; try { mTileProjection.setTile(tile); //mTile = tile; /* size of tile in map coordinates; */ double size = 1.0 / (1 << tile.zoomLevel); /* simplification tolerance */ int pixel = (tile.zoomLevel > 11) ? 1 : 2; int simplify = Tile.SIZE / pixel; /* translate screen pixel for tile to latitude and longitude * tolerance for point reduction before projection. */ minDeltaLat = (int) (Math.abs(MercatorProjection.toLatitude(tile.y + size) - MercatorProjection.toLatitude(tile.y)) * 1e6) / simplify; minDeltaLon = (int) (Math.abs(MercatorProjection.toLongitude(tile.x + size) - MercatorProjection.toLongitude(tile.x)) * 1e6) / simplify; QueryParameters queryParameters = new QueryParameters(); queryParameters.queryZoomLevel = mTileSource.fileHeader.getQueryZoomLevel(tile.zoomLevel); /* get and check the sub-file for the query zoom level */ SubFileParameter subFileParameter = mTileSource.fileHeader.getSubFileParameter(queryParameters.queryZoomLevel); if (subFileParameter == null) { log.warn("no sub-file for zoom level: " + queryParameters.queryZoomLevel); sink.completed(FAILED); return; } QueryCalculations.calculateBaseTiles(queryParameters, tile, subFileParameter); QueryCalculations.calculateBlocks(queryParameters, subFileParameter); processBlocks(sink, queryParameters, subFileParameter); } catch (IOException e) { log.error(e.getMessage()); sink.completed(FAILED); return; } sink.completed(SUCCESS); } @Override public void dispose() { mReadBuffer = null; if (mInputFile != null) { try { mInputFile.close(); mInputFile = null; } catch (IOException e) { log.error(e.getMessage()); } } } @Override public void cancel() { } /** * Logs the debug signatures of the current way and block. */ private void logDebugSignatures() { if (mDebugFile) { log.warn(DEBUG_SIGNATURE_WAY + mSignatureWay); log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } } /** * Processes a single block and executes the callback functions on all map * elements. * * @param queryParameters * the parameters of the current query. * @param subFileParameter * the parameters of the current map file. * @param mapDataSink * the callback which handles the extracted map elements. */ private void processBlock(QueryParameters queryParameters, SubFileParameter subFileParameter, ITileDataSink mapDataSink) { if (!processBlockSignature()) { return; } int[][] zoomTable = readZoomTable(subFileParameter); if (zoomTable == null) { return; } int zoomTableRow = queryParameters.queryZoomLevel - subFileParameter.zoomLevelMin; int poisOnQueryZoomLevel = zoomTable[zoomTableRow][0]; int waysOnQueryZoomLevel = zoomTable[zoomTableRow][1]; /* get the relative offset to the first stored way in the block */ int firstWayOffset = mReadBuffer.readUnsignedInt(); if (firstWayOffset < 0) { log.warn(INVALID_FIRST_WAY_OFFSET + firstWayOffset); if (mDebugFile) { log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } return; } /* add the current buffer position to the relative first way offset */ firstWayOffset += mReadBuffer.getBufferPosition(); if (firstWayOffset > mReadBuffer.getBufferSize()) { log.warn(INVALID_FIRST_WAY_OFFSET + firstWayOffset); if (mDebugFile) { log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } return; } if (!processPOIs(mapDataSink, poisOnQueryZoomLevel)) { return; } /* finished reading POIs, check if the current buffer position is valid */ if (mReadBuffer.getBufferPosition() > firstWayOffset) { log.warn("invalid buffer position: " + mReadBuffer.getBufferPosition()); if (mDebugFile) { log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } return; } /* move the pointer to the first way */ mReadBuffer.setBufferPosition(firstWayOffset); if (!processWays(queryParameters, mapDataSink, waysOnQueryZoomLevel)) { return; } } // private long mCurrentRow; // private long mCurrentCol; private int xmin, ymin, xmax, ymax; private void setTileClipping(QueryParameters queryParameters, long mCurrentRow, long mCurrentCol) { long numRows = queryParameters.toBlockY - queryParameters.fromBlockY; long numCols = queryParameters.toBlockX - queryParameters.fromBlockX; //log.debug(numCols + "/" + numRows + " " + mCurrentCol + " " + mCurrentRow); xmin = -16; ymin = -16; xmax = Tile.SIZE + 16; ymax = Tile.SIZE + 16; if (numRows > 0) { int w = (int) (Tile.SIZE / (numCols + 1)); int h = (int) (Tile.SIZE / (numRows + 1)); if (mCurrentCol > 0) xmin = (int) (mCurrentCol * w); if (mCurrentCol < numCols) xmax = (int) (mCurrentCol * w + w); if (mCurrentRow > 0) ymin = (int) (mCurrentRow * h); if (mCurrentRow < numRows) ymax = (int) (mCurrentRow * h + h); } mTileClipper.setRect(xmin, ymin, xmax, ymax); } private final static Tag mWaterTag = new Tag("natural", "water"); private void processBlocks(ITileDataSink mapDataSink, QueryParameters queryParams, SubFileParameter subFileParameter) throws IOException { /* read and process all blocks from top to bottom and from left to right */ for (long row = queryParams.fromBlockY; row <= queryParams.toBlockY; row++) { for (long column = queryParams.fromBlockX; column <= queryParams.toBlockX; column++) { //mCurrentCol = column - queryParameters.fromBlockX; //mCurrentRow = row - queryParameters.fromBlockY; setTileClipping(queryParams, row - queryParams.fromBlockY, column - queryParams.fromBlockX); /* calculate the actual block number of the needed block in the * file */ long blockNumber = row * subFileParameter.blocksWidth + column; /* get the current index entry */ long blockIndexEntry = mTileSource.databaseIndexCache.getIndexEntry(subFileParameter, blockNumber); /* check the water flag of the block in its index entry */ if ((blockIndexEntry & BITMASK_INDEX_WATER) != 0) { MapElement e = mElem; e.clear(); e.tags.clear(); e.tags.add(mWaterTag); e.startPolygon(); e.addPoint(xmin, ymin); e.addPoint(xmax, ymin); e.addPoint(xmax, ymax); e.addPoint(xmin, ymax); mapDataSink.process(e); } /* get and check the current block pointer */ long blockPointer = blockIndexEntry & BITMASK_INDEX_OFFSET; if (blockPointer < 1 || blockPointer > subFileParameter.subFileSize) { log.warn("invalid current block pointer: " + blockPointer); log.warn("subFileSize: " + subFileParameter.subFileSize); return; } long nextBlockPointer; /* check if the current block is the last block in the file */ if (blockNumber + 1 == subFileParameter.numberOfBlocks) { /* set the next block pointer to the end of the file */ nextBlockPointer = subFileParameter.subFileSize; } else { /* get and check the next block pointer */ nextBlockPointer = mTileSource.databaseIndexCache.getIndexEntry(subFileParameter, blockNumber + 1); nextBlockPointer &= BITMASK_INDEX_OFFSET; if (nextBlockPointer < 1 || nextBlockPointer > subFileParameter.subFileSize) { log.warn("invalid next block pointer: " + nextBlockPointer); log.warn("sub-file size: " + subFileParameter.subFileSize); return; } } /* calculate the size of the current block */ int blockSize = (int) (nextBlockPointer - blockPointer); if (blockSize < 0) { log.warn("current block size must not be negative: " + blockSize); return; } else if (blockSize == 0) { /* the current block is empty, continue with the next block */ continue; } else if (blockSize > ReadBuffer.MAXIMUM_BUFFER_SIZE) { /* the current block is too large, continue with the next * block */ log.warn("current block size too large: " + blockSize); continue; } else if (blockPointer + blockSize > mFileSize) { log.warn("current block larger than file size: " + blockSize); return; } /* seek to the current block in the map file */ mInputFile.seek(subFileParameter.startAddress + blockPointer); /* read the current block into the buffer */ if (!mReadBuffer.readFromFile(blockSize)) { /* skip the current block */ log.warn("reading current block has failed: " + blockSize); return; } /* calculate the top-left coordinates of the underlying tile */ double tileLatitudeDeg = Projection.tileYToLatitude(subFileParameter.boundaryTileTop + row, subFileParameter.baseZoomLevel); double tileLongitudeDeg = Projection.tileXToLongitude(subFileParameter.boundaryTileLeft + column, subFileParameter.baseZoomLevel); mTileLatitude = (int) (tileLatitudeDeg * 1E6); mTileLongitude = (int) (tileLongitudeDeg * 1E6); processBlock(queryParams, subFileParameter, mapDataSink); } } } /** * Processes the block signature, if present. * * @return true if the block signature could be processed successfully, * false otherwise. */ private boolean processBlockSignature() { if (mDebugFile) { /* get and check the block signature */ mSignatureBlock = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_BLOCK); if (!mSignatureBlock.startsWith("###TileStart")) { log.warn("invalid block signature: " + mSignatureBlock); return false; } } return true; } /** * Processes the given number of POIs. * * @param mapDataSink * the callback which handles the extracted POIs. * @param numberOfPois * how many POIs should be processed. * @return true if the POIs could be processed successfully, false * otherwise. */ private boolean processPOIs(ITileDataSink mapDataSink, int numberOfPois) { Tag[] poiTags = mTileSource.fileInfo.poiTags; MapElement e = mElem; int numTags = 0; for (int elementCounter = numberOfPois; elementCounter != 0; --elementCounter) { if (mDebugFile) { /* get and check the POI signature */ mSignaturePoi = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_POI); if (!mSignaturePoi.startsWith("***POIStart")) { log.warn("invalid POI signature: " + mSignaturePoi); log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); return false; } } /* get the POI latitude offset (VBE-S) */ int latitude = mTileLatitude + mReadBuffer.readSignedInt(); /* get the POI longitude offset (VBE-S) */ int longitude = mTileLongitude + mReadBuffer.readSignedInt(); /* get the special byte which encodes multiple flags */ byte specialByte = mReadBuffer.readByte(); /* bit 1-4 represent the layer */ byte layer = (byte) ((specialByte & POI_LAYER_BITMASK) >>> POI_LAYER_SHIFT); /* bit 5-8 represent the number of tag IDs */ byte numberOfTags = (byte) (specialByte & POI_NUMBER_OF_TAGS_BITMASK); if (numberOfTags != 0) { if (!mReadBuffer.readTags(e.tags, poiTags, numberOfTags)) return false; numTags = numberOfTags; } /* reset to common tag position */ e.tags.numTags = numTags; /* get the feature bitmask (1 byte) */ byte featureByte = mReadBuffer.readByte(); /* bit 1-3 enable optional features * check if the POI has a name */ if ((featureByte & POI_FEATURE_NAME) != 0) { String str = mReadBuffer.readUTF8EncodedString(); e.tags.add(new Tag(Tag.KEY_NAME, str, false)); } /* check if the POI has a house number */ if ((featureByte & POI_FEATURE_HOUSE_NUMBER) != 0) { // mReadBuffer.getPositionAndSkip(); // String str = mReadBuffer.readUTF8EncodedString(); } /* check if the POI has an elevation */ if ((featureByte & POI_FEATURE_ELEVATION) != 0) { mReadBuffer.readSignedInt(); // mReadBuffer.getPositionAndSkip();// tags.add(new // Tag(Tag.TAG_KEY_ELE, // Integer.toString(mReadBuffer.readSignedInt()))); } mTileProjection.projectPoint(latitude, longitude, e); e.setLayer(layer); mapDataSink.process(e); } return true; } private boolean processWayDataBlock(MapElement e, boolean doubleDeltaEncoding, boolean isLine) { /* get and check the number of way coordinate blocks (VBE-U) */ int numBlocks = mReadBuffer.readUnsignedInt(); if (numBlocks < 1 || numBlocks > Short.MAX_VALUE) { log.warn("invalid number of way coordinate blocks: " + numBlocks); return false; } int[] wayLengths = e.ensureIndexSize(numBlocks, false); if (wayLengths.length > numBlocks) wayLengths[numBlocks] = -1; /* read the way coordinate blocks */ for (int coordinateBlock = 0; coordinateBlock < numBlocks; ++coordinateBlock) { int numWayNodes = mReadBuffer.readUnsignedInt(); if (numWayNodes < 2 || numWayNodes > MAXIMUM_WAY_NODES_SEQUENCE_LENGTH) { log.warn("invalid number of way nodes: " + numWayNodes); logDebugSignatures(); return false; } /* each way node consists of latitude and longitude */ int len = numWayNodes * 2; wayLengths[coordinateBlock] = decodeWayNodes(doubleDeltaEncoding, e, len, isLine);; } return true; } private int decodeWayNodes(boolean doubleDelta, MapElement e, int length, boolean isLine) { int[] buffer = mIntBuffer; mReadBuffer.readSignedInt(buffer, length); float[] outBuffer = e.ensurePointSize(e.pointPos + length, true); int outPos = e.pointPos; int lat, lon; /* first node latitude single-delta offset */ int firstLat = lat = mTileLatitude + buffer[0]; int firstLon = lon = mTileLongitude + buffer[1]; outBuffer[outPos++] = lon; outBuffer[outPos++] = lat; int cnt = 2; int deltaLat = 0; int deltaLon = 0; for (int pos = 2; pos < length; pos += 2) { if (doubleDelta) { deltaLat = buffer[pos] + deltaLat; deltaLon = buffer[pos + 1] + deltaLon; } else { deltaLat = buffer[pos]; deltaLon = buffer[pos + 1]; } lat += deltaLat; lon += deltaLon; if (pos == length - 2) { boolean line = isLine || (lon != firstLon && lat != firstLat); if (line) { outBuffer[outPos++] = lon; outBuffer[outPos++] = lat; cnt += 2; } if (e.type == GeometryType.NONE) e.type = line ? LINE : POLY; } else if (deltaLon > minDeltaLon || deltaLon < -minDeltaLon || deltaLat > minDeltaLat || deltaLat < -minDeltaLat) { outBuffer[outPos++] = lon; outBuffer[outPos++] = lat; cnt += 2; } } e.pointPos = outPos; return cnt; } private int stringOffset = -1; /** * Processes the given number of ways. * * @param queryParameters * the parameters of the current query. * @param mapDataSink * the callback which handles the extracted ways. * @param numberOfWays * how many ways should be processed. * @return true if the ways could be processed successfully, false * otherwise. */ private boolean processWays(QueryParameters queryParameters, ITileDataSink mapDataSink, int numberOfWays) { Tag[] wayTags = mTileSource.fileInfo.wayTags; MapElement e = mElem; int numTags = 0; int wayDataBlocks; // skip string block int stringsSize = 0; stringOffset = 0; if (mTileSource.experimental) { stringsSize = mReadBuffer.readUnsignedInt(); stringOffset = mReadBuffer.getBufferPosition(); mReadBuffer.skipBytes(stringsSize); } //setTileClipping(queryParameters); for (int elementCounter = numberOfWays; elementCounter != 0; --elementCounter) { if (mDebugFile) { // get and check the way signature mSignatureWay = mReadBuffer.readUTF8EncodedString(SIGNATURE_LENGTH_WAY); if (!mSignatureWay.startsWith("---WayStart")) { log.warn("invalid way signature: " + mSignatureWay); log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); return false; } } if (queryParameters.useTileBitmask) { elementCounter = mReadBuffer.skipWays(queryParameters.queryTileBitmask, elementCounter); if (elementCounter == 0) return true; if (elementCounter < 0) return false; if (mTileSource.experimental && mReadBuffer.lastTagPosition > 0) { int pos = mReadBuffer.getBufferPosition(); mReadBuffer.setBufferPosition(mReadBuffer.lastTagPosition); byte numberOfTags = (byte) (mReadBuffer.readByte() & WAY_NUMBER_OF_TAGS_BITMASK); if (!mReadBuffer.readTags(e.tags, wayTags, numberOfTags)) return false; numTags = numberOfTags; mReadBuffer.setBufferPosition(pos); } } else { int wayDataSize = mReadBuffer.readUnsignedInt(); if (wayDataSize < 0) { log.warn("invalid way data size: " + wayDataSize); if (mDebugFile) { log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } log.error("BUG way 2"); return false; } /* ignore the way tile bitmask (2 bytes) */ mReadBuffer.skipBytes(2); } /* get the special byte which encodes multiple flags */ byte specialByte = mReadBuffer.readByte(); /* bit 1-4 represent the layer */ byte layer = (byte) ((specialByte & WAY_LAYER_BITMASK) >>> WAY_LAYER_SHIFT); /* bit 5-8 represent the number of tag IDs */ byte numberOfTags = (byte) (specialByte & WAY_NUMBER_OF_TAGS_BITMASK); if (numberOfTags != 0) { if (!mReadBuffer.readTags(e.tags, wayTags, numberOfTags)) return false; numTags = numberOfTags; } /* get the feature bitmask (1 byte) */ byte featureByte = mReadBuffer.readByte(); /* bit 1-6 enable optional features */ boolean featureWayDoubleDeltaEncoding = (featureByte & WAY_FEATURE_DOUBLE_DELTA_ENCODING) != 0; boolean hasName = (featureByte & WAY_FEATURE_NAME) != 0; boolean hasHouseNr = (featureByte & WAY_FEATURE_HOUSE_NUMBER) != 0; boolean hasRef = (featureByte & WAY_FEATURE_REF) != 0; e.tags.numTags = numTags; if (mTileSource.experimental) { if (hasName) { int textPos = mReadBuffer.readUnsignedInt(); String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos); e.tags.add(new Tag(Tag.KEY_NAME, str, false)); } if (hasHouseNr) { int textPos = mReadBuffer.readUnsignedInt(); String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos); e.tags.add(new Tag(Tag.KEY_HOUSE_NUMBER, str, false)); } if (hasRef) { int textPos = mReadBuffer.readUnsignedInt(); String str = mReadBuffer.readUTF8EncodedStringAt(stringOffset + textPos); e.tags.add(new Tag(Tag.KEY_REF, str, false)); } } else { if (hasName) { String str = mReadBuffer.readUTF8EncodedString(); e.tags.add(new Tag(Tag.KEY_NAME, str, false)); } if (hasHouseNr) { String str = mReadBuffer.readUTF8EncodedString(); e.tags.add(new Tag(Tag.KEY_HOUSE_NUMBER, str, false)); } if (hasRef) { String str = mReadBuffer.readUTF8EncodedString(); e.tags.add(new Tag(Tag.KEY_REF, str, false)); } } if ((featureByte & WAY_FEATURE_LABEL_POSITION) != 0) // labelPosition = readOptionalLabelPosition(); if ((featureByte & WAY_FEATURE_DATA_BLOCKS_BYTE) != 0) { wayDataBlocks = mReadBuffer.readUnsignedInt(); if (wayDataBlocks < 1) { log.warn("invalid number of way data blocks: " + wayDataBlocks); logDebugSignatures(); return false; } } else { wayDataBlocks = 1; } /* some guessing if feature is a line or a polygon */ boolean linearFeature = e.tags.containsKey("highway") || 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; } for (int wayDataBlock = 0; wayDataBlock < wayDataBlocks; wayDataBlock++) { e.clear(); if (!processWayDataBlock(e, featureWayDoubleDeltaEncoding, linearFeature)) return false; /* drop invalid outer ring */ if (e.isPoly() && e.index[0] < 6) { continue; } mTileProjection.project(e); if (!e.tags.containsKey("building")) if (!mTileClipper.clip(e)) { continue; } e.simplify(1, true); e.setLayer(layer); mapDataSink.process(e); } } return true; } private float[] readOptionalLabelPosition() { float[] labelPosition = new float[2]; /* get the label position latitude offset (VBE-S) */ labelPosition[1] = mTileLatitude + mReadBuffer.readSignedInt(); /* get the label position longitude offset (VBE-S) */ labelPosition[0] = mTileLongitude + mReadBuffer.readSignedInt(); return labelPosition; } private int[][] readZoomTable(SubFileParameter subFileParameter) { int rows = subFileParameter.zoomLevelMax - subFileParameter.zoomLevelMin + 1; int[][] zoomTable = new int[rows][2]; int cumulatedNumberOfPois = 0; int cumulatedNumberOfWays = 0; for (int row = 0; row < rows; row++) { cumulatedNumberOfPois += mReadBuffer.readUnsignedInt(); cumulatedNumberOfWays += mReadBuffer.readUnsignedInt(); if (cumulatedNumberOfPois < 0 || cumulatedNumberOfPois > MAXIMUM_ZOOM_TABLE_OBJECTS) { log.warn("invalid cumulated number of POIs in row " + row + ' ' + cumulatedNumberOfPois); if (mDebugFile) { log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } return null; } else if (cumulatedNumberOfWays < 0 || cumulatedNumberOfWays > MAXIMUM_ZOOM_TABLE_OBJECTS) { log.warn("invalid cumulated number of ways in row " + row + ' ' + cumulatedNumberOfWays); if (mTileSource.fileInfo.debugFile) { log.warn(DEBUG_SIGNATURE_BLOCK + mSignatureBlock); } return null; } zoomTable[row][0] = cumulatedNumberOfPois; zoomTable[row][1] = cumulatedNumberOfWays; } return zoomTable; } static class TileProjection { private static final double COORD_SCALE = 1000000.0; long dx, dy; double divx, divy; void setTile(Tile tile) { /* tile position in pixels at tile zoom */ long x = tile.tileX * Tile.SIZE; long y = tile.tileY * Tile.SIZE + Tile.SIZE; /* size of the map in pixel at tile zoom */ long mapExtents = Tile.SIZE << tile.zoomLevel; /* offset relative to lat/lon == 0 */ dx = (x - (mapExtents >> 1)); dy = (y - (mapExtents >> 1)); /* scales longitude(1e6) to map-pixel */ divx = (180.0 * COORD_SCALE) / (mapExtents >> 1); /* scale latidute to map-pixel */ divy = (Math.PI * 2.0) / (mapExtents >> 1); } 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; boolean isPoly = e.isPoly(); for (int idx = 0, m = indices.length; idx < m; idx++) { int len = indices[idx]; if (len == 0) continue; if (len < 0) break; float lat, lon, pLon = 0, pLat = 0; int cnt = 0, first = outPos; for (int end = inPos + len; inPos < end; inPos += 2) { lon = projectLon(coords[inPos]); lat = projectLat(coords[inPos + 1]); if (cnt != 0) { /* drop small distance intermediate nodes */ if (lat == pLat && lon == pLon) { //log.debug("drop zero delta "); continue; } } coords[outPos++] = pLon = lon; coords[outPos++] = pLat = lat; cnt += 2; } if (isPoly && coords[first] == pLon && coords[first + 1] == pLat) { /* remove identical start/end point */ //log.debug("drop closing point {}", e); indices[idx] = (short) (cnt - 2); outPos -= 2; } else { indices[idx] = (short) cnt; } } } } }