MapFileWriter.java :  » Tools » mapsforge » org » mapsforge » preprocessing » map » osmosis » Android Open Source

Android Open Source » Tools » mapsforge 
mapsforge » org » mapsforge » preprocessing » map » osmosis » MapFileWriter.java
/*
 * Copyright 2010, 2011 mapsforge.org
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.mapsforge.preprocessing.map.osmosis;

import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import org.mapsforge.core.GeoCoordinate;
import org.mapsforge.core.MercatorProjection;
import org.mapsforge.preprocessing.map.osmosis.TileData.TDNode;
import org.mapsforge.preprocessing.map.osmosis.TileData.TDWay;

/**
 * Writes the binary file format for mapsforge maps.
 * 
 * @author bross
 * 
 */
class MapFileWriter {

  private static final int DEBUG_BLOCK_SIZE = 32;

  private static final String DEBUG_INDEX_START_STRING = "+++IndexStart+++";

  private static final int SIZE_ZOOMINTERVAL_CONFIGURATION = 13;

  private static final int PIXEL_COMPRESSION_MAX_DELTA = 5;

  private static final int BYTE_AMOUNT_SUBFILE_INDEX_PER_TILE = 5;

  private static final String MAGIC_BYTE = "mapsforge binary OSM";

  private static final CoastlineHandler COASTLINE_HANDLER = new CoastlineHandler();

  // DEBUG STRINGS
  private static final String DEBUG_STRING_POI_HEAD = "***POIStart";
  private static final String DEBUG_STRING_POI_TAIL = "***";
  private static final String DEBUG_STRING_TILE_HEAD = "###TileStart";
  private static final String DEBUG_STRING_TILE_TAIL = "###";
  private static final String DEBUG_STRING_WAY_HEAD = "---WayStart";
  private static final String DEBUG_STRING_WAY_TAIL = "---";

  // bitmap flags for pois and ways
  private static final short BITMAP_NAME = 128;

  // bitmap flags for pois
  private static final short BITMAP_ELEVATION = 64;
  private static final short BITMAP_HOUSENUMBER = 32;

  // bitmap flags for ways
  private static final short BITMAP_REF = 64;
  private static final short BITMAP_LABEL = 32;
  private static final short BITMAP_MULTIPOLYGON = 16;
  private static final short BITMAP_HIGHWAY = 128;
  private static final short BITMAP_RAILWAY = 64;
  private static final short BITMAP_BUILDING = 32;
  private static final short BITMAP_LANDUSE = 16;
  private static final short BITMAP_LEISURE = 8;
  private static final short BITMAP_AMENITY = 4;
  private static final short BITMAP_NATURAL = 2;
  private static final short BITMAP_WATERWAY = 1;

  // bitmap flags for file features
  private static final short BITMAP_DEBUG = 128;
  private static final short BITMAP_MAP_START_POSITION = 64;

  private static final int BITMAP_INDEX_ENTRY_WATER = 0x80;

  private static final Logger LOGGER = Logger.getLogger(MapFileWriter.class
      .getName());

  private static final String PROJECTION = "Mercator";

  private static final byte MAX_ZOOMLEVEL_PIXEL_FILTER = 11;

  // private static final byte MIN_ZOOMLEVEL_POLYGON_CLIPPING = 8;

  private static final Charset UTF8_CHARSET = Charset.forName("utf8");

  private static final int PROGRESS_PERCENT_STEP = 10;

  // data
  private TileBasedDataStore dataStore;

  private static final TileInfo TILE_INFO = TileInfo.getInstance();
  // private static final CoastlineHandler COASTLINE_HANDLER = new CoastlineHandler();

  // IO
  private static final int HEADER_BUFFER_SIZE = 0x100000; // 1MB
  private static final int MIN_TILE_BUFFER_SIZE = 0xF00000; // 15MB
  private static final int TILE_BUFFER_SIZE = 0x3200000; // 50MB
  private static final int WAY_BUFFER_SIZE = 0x100000; // 1MB
  private static final int POI_BUFFER_SIZE = 0x100000; // 1MB
  private final RandomAccessFile randomAccessFile;
  private ByteBuffer bufferZoomIntervalConfig;

  // concurrent computation of subtile bitmask
  private final ExecutorService executorService;

  // accounting
  private long tilesProcessed = 0;
  private long amountOfTilesInPercentStep;
  private long emptyTiles = 0;
  private long maxTileSize = 0;
  private long cumulatedTileSizeOfNonEmptyTiles = 0;
  private int maxWaysPerTile = 0;
  private int cumulatedNumberOfWaysInTiles = 0;

  private int posZoomIntervalConfig;
  final int bboxEnlargement;

  MapFileWriter(TileBasedDataStore dataStore, RandomAccessFile file,
      int threadpoolSize, int bboxEnlargement) {
    super();
    this.dataStore = dataStore;
    this.randomAccessFile = file;
    amountOfTilesInPercentStep = dataStore.cumulatedNumberOfTiles() / PROGRESS_PERCENT_STEP;
    if (amountOfTilesInPercentStep == 0)
      amountOfTilesInPercentStep = 1;
    executorService = Executors.newFixedThreadPool(threadpoolSize);
    this.bboxEnlargement = bboxEnlargement;
  }

  final void writeFile(long date, int version, short tilePixel, String comment,
      boolean debugStrings, boolean waynodeCompression, boolean polygonClipping,
      boolean pixelCompression, GeoCoordinate mapStartPosition)
      throws IOException {

    // CONTAINER HEADER
    long totalHeaderSize = writeContainerHeader(date, version, tilePixel,
        comment, debugStrings, mapStartPosition);

    int amountOfZoomIntervals = dataStore.getZoomIntervalConfiguration()
        .getNumberOfZoomIntervals();

    // SUB FILES
    // for each zoom interval write a sub file
    long currentFileSize = totalHeaderSize;
    for (int i = 0; i < amountOfZoomIntervals; i++) {
      // SUB FILE INDEX AND DATA
      long subfileSize = writeSubfile(currentFileSize, i, debugStrings,
          waynodeCompression, polygonClipping, pixelCompression);
      // SUB FILE META DATA IN CONTAINER HEADER
      writeSubfileMetaDataToContainerHeader(i, currentFileSize, subfileSize);
      currentFileSize += subfileSize;
    }

    randomAccessFile.seek(posZoomIntervalConfig);
    byte[] containerB = bufferZoomIntervalConfig.array();
    randomAccessFile.write(containerB);
    randomAccessFile.close();

    LOGGER.fine("number of empty tiles: " + emptyTiles);
    LOGGER.fine("percentage of empty tiles: " + (float) emptyTiles
        / dataStore.cumulatedNumberOfTiles());
    LOGGER.fine("cumulated size of non-empty tiles: " + cumulatedTileSizeOfNonEmptyTiles);
    LOGGER.fine("average tile size of non-empty tile: "
        + (float) cumulatedTileSizeOfNonEmptyTiles
        / (dataStore.cumulatedNumberOfTiles() - emptyTiles));
    LOGGER.fine("maximum size of a tile: " + maxTileSize);
    LOGGER.fine("cumulated number of ways in all non-empty tiles: "
        + cumulatedNumberOfWaysInTiles);
    LOGGER.fine("maximum number of ways in a tile: " + maxWaysPerTile);
    LOGGER.fine("average number of ways in non-empty tiles: "
        + (float) cumulatedNumberOfWaysInTiles
        / (dataStore.cumulatedNumberOfTiles() - emptyTiles));

  }

  // private void writeByteArray(int pos, byte[] array, ByteBuffer buffer) {
  // int currentPos = buffer.position();
  // buffer.position(pos);
  // buffer.put(array);
  // buffer.position(currentPos);
  // }

  private void writeUTF8(String string, ByteBuffer buffer) {
    buffer.put(Serializer.getVariableByteUnsigned(string.getBytes(UTF8_CHARSET).length));
    buffer.put(string.getBytes(UTF8_CHARSET));
  }

  private long writeContainerHeader(long date, int version, short tilePixel, String comment,
      boolean debugStrings, GeoCoordinate mapStartPosition)
      throws IOException {

    // get metadata for the map file
    int numberOfZoomIntervals = dataStore.getZoomIntervalConfiguration()
        .getNumberOfZoomIntervals();

    LOGGER.fine("writing header");

    ByteBuffer containerHeaderBuffer = ByteBuffer.allocate(HEADER_BUFFER_SIZE);

    // write file header
    // magic byte
    byte[] magicBytes = MAGIC_BYTE.getBytes();
    containerHeaderBuffer.put(magicBytes);

    // write container header size
    int headerSizePosition = containerHeaderBuffer.position();
    containerHeaderBuffer.position(headerSizePosition + 4);

    // version number of the binary file format
    containerHeaderBuffer.putInt(version);

    // meta info byte
    containerHeaderBuffer.put(infoByteOptmizationParams(debugStrings,
        mapStartPosition != null));

    // amount of map files inside this file
    containerHeaderBuffer.put((byte) numberOfZoomIntervals);

    // projection type
    writeUTF8(PROJECTION, containerHeaderBuffer);

    // width and height of a tile in pixel
    containerHeaderBuffer.putShort(tilePixel);

    LOGGER.fine("Bounding box for file: "
        + dataStore.getBoundingBox().maxLatitudeE6 + ", "
        + dataStore.getBoundingBox().minLongitudeE6 + ", "
        + dataStore.getBoundingBox().minLatitudeE6 + ", "
        + dataStore.getBoundingBox().maxLongitudeE6);
    // upper left corner of the bounding box
    containerHeaderBuffer.putInt(dataStore.getBoundingBox().maxLatitudeE6);
    containerHeaderBuffer.putInt(dataStore.getBoundingBox().minLongitudeE6);
    containerHeaderBuffer.putInt(dataStore.getBoundingBox().minLatitudeE6);
    containerHeaderBuffer.putInt(dataStore.getBoundingBox().maxLongitudeE6);

    if (mapStartPosition != null) {
      containerHeaderBuffer.putInt(mapStartPosition.getLatitudeE6());
      containerHeaderBuffer.putInt(mapStartPosition.getLongitudeE6());
    }

    // date of the map data
    containerHeaderBuffer.putLong(date);

    // store the mapping of tags to tag ids
    containerHeaderBuffer.putShort((short) MapFileWriterTask.TAG_MAPPING.optimizedPoiIds()
        .size());

    for (Entry<Short, Short> idMapping : MapFileWriterTask.TAG_MAPPING.optimizedPoiIds()
        .entrySet()) {
      OSMTag tag = MapFileWriterTask.TAG_MAPPING.getPoiTag(idMapping.getKey());
      writeUTF8(tag.tagKey(), containerHeaderBuffer);
      containerHeaderBuffer.putShort(idMapping.getValue());
    }

    containerHeaderBuffer.putShort((short) MapFileWriterTask.TAG_MAPPING.optimizedWayIds()
        .size());

    for (Entry<Short, Short> idMapping : MapFileWriterTask.TAG_MAPPING.optimizedWayIds()
        .entrySet()) {
      OSMTag tag = MapFileWriterTask.TAG_MAPPING.getWayTag(idMapping.getKey());
      writeUTF8(tag.tagKey(), containerHeaderBuffer);
      containerHeaderBuffer.putShort(idMapping.getValue());
    }

    // comment
    if (comment != null && !comment.equals("")) {
      writeUTF8(comment, containerHeaderBuffer);
    } else {
      writeUTF8("", containerHeaderBuffer);
    }

    // initialize buffer for writing zoom interval configurations
    this.posZoomIntervalConfig = containerHeaderBuffer.position();
    bufferZoomIntervalConfig = ByteBuffer.allocate(SIZE_ZOOMINTERVAL_CONFIGURATION
        * numberOfZoomIntervals);

    containerHeaderBuffer.position(containerHeaderBuffer.position()
        + SIZE_ZOOMINTERVAL_CONFIGURATION
        * numberOfZoomIntervals);

    // -4 bytes of header size variable itself
    int headerSize = containerHeaderBuffer.position() - headerSizePosition - 4;
    containerHeaderBuffer.putInt(headerSizePosition, headerSize);

    if (!containerHeaderBuffer.hasArray()) {
      randomAccessFile.close();
      throw new RuntimeException(
          "unsupported operating system, byte buffer not backed by array");
    }
    randomAccessFile.write(containerHeaderBuffer.array(), 0,
        containerHeaderBuffer.position());

    return containerHeaderBuffer.position();
  }

  private void writeSubfileMetaDataToContainerHeader(int i, long startIndexOfSubfile,
      long subfileSize) {

    // HEADER META DATA FOR SUB FILE
    // write zoom interval configuration to header
    byte minZoomCurrentInterval = dataStore.getZoomIntervalConfiguration().getMinZoom(i);
    byte maxZoomCurrentInterval = dataStore.getZoomIntervalConfiguration().getMaxZoom(i);
    byte baseZoomCurrentInterval = dataStore.getZoomIntervalConfiguration().getBaseZoom(i);

    bufferZoomIntervalConfig.put(baseZoomCurrentInterval);
    bufferZoomIntervalConfig.put(minZoomCurrentInterval);
    bufferZoomIntervalConfig.put(maxZoomCurrentInterval);
    bufferZoomIntervalConfig.put(Serializer.getFiveBytes(startIndexOfSubfile));
    bufferZoomIntervalConfig.put(Serializer.getFiveBytes(subfileSize));
  }

  private long writeSubfile(long startPositionSubfile, int zoomIntervalIndex,
      boolean debugStrings, boolean waynodeCompression, boolean polygonClipping,
      boolean pixelCompression)
      throws IOException {

    LOGGER.fine("writing data for zoom interval " + zoomIntervalIndex
        + ", number of tiles: "
        + dataStore.getTileGridLayout(zoomIntervalIndex).getAmountTilesHorizontal()
        * dataStore.getTileGridLayout(zoomIntervalIndex).getAmountTilesVertical());

    TileCoordinate upperLeft = dataStore.getTileGridLayout(zoomIntervalIndex)
        .getUpperLeft();
    int lengthX = dataStore.getTileGridLayout(zoomIntervalIndex).getAmountTilesHorizontal();
    int lengthY = dataStore.getTileGridLayout(zoomIntervalIndex).getAmountTilesVertical();

    byte minZoomCurrentInterval = dataStore.getZoomIntervalConfiguration().getMinZoom(
        zoomIntervalIndex);
    byte maxZoomCurrentInterval = dataStore.getZoomIntervalConfiguration().getMaxZoom(
        zoomIntervalIndex);
    byte baseZoomCurrentInterval = dataStore.getZoomIntervalConfiguration().getBaseZoom(
        zoomIntervalIndex);
    // byte maxMaxZoomlevel = dataStore.getZoomIntervalConfiguration().getMaxMaxZoom();

    int tileAmountInBytes = lengthX * lengthY * BYTE_AMOUNT_SUBFILE_INDEX_PER_TILE;
    int indexBufferSize = tileAmountInBytes
        + (debugStrings ? DEBUG_INDEX_START_STRING.getBytes().length : 0);
    ByteBuffer indexBuffer = ByteBuffer.allocate(indexBufferSize);
    ByteBuffer tileBuffer = ByteBuffer.allocate(TILE_BUFFER_SIZE);
    ByteBuffer wayBuffer = ByteBuffer.allocate(WAY_BUFFER_SIZE);
    ByteBuffer poiBuffer = ByteBuffer.allocate(POI_BUFFER_SIZE);

    // write debug strings for tile index segment if necessary
    if (debugStrings)
      indexBuffer.put(DEBUG_INDEX_START_STRING.getBytes());

    long currentSubfileOffset = indexBufferSize;
    randomAccessFile.seek(startPositionSubfile + indexBufferSize);

    // loop over tiles (row-wise)
    for (int tileY = upperLeft.getY(); tileY < upperLeft.getY() + lengthY; tileY++) {
      for (int tileX = upperLeft.getX(); tileX < upperLeft.getX() + lengthX; tileX++) {
        // logger.info("writing data for tile (" + tileX + ", " + tileY + ")");

        // ***************** TILE INDEX SEGMENT ********************
        long currentTileOffsetInBuffer = tileBuffer.position();
        TileCoordinate currentTileCoordinate = new TileCoordinate(tileX, tileY,
            baseZoomCurrentInterval);
        int currentTileLat = GeoCoordinate.doubleToInt(MercatorProjection
            .tileYToLatitude(currentTileCoordinate.getY(),
                currentTileCoordinate.getZoomlevel()));
        int currentTileLon = GeoCoordinate.doubleToInt(MercatorProjection
            .tileXToLongitude(currentTileCoordinate.getX(),
                currentTileCoordinate.getZoomlevel()));

        byte[] indexBytes = Serializer.getFiveBytes(currentSubfileOffset);
        if (TILE_INFO.isWaterTile(currentTileCoordinate)) {
          indexBytes[0] |= BITMAP_INDEX_ENTRY_WATER;
        } else {
          // the TileInfo class may produce false negatives for tiles on zoom level
          // greater than TileInfo.TILE_INFO_ZOOMLEVEL
          // we need to run the coastline algorithm to detect whether the tile is
          // completely covered by water or not
          if (currentTileCoordinate.getZoomlevel() > TileInfo.TILE_INFO_ZOOMLEVEL) {
            if (COASTLINE_HANDLER.isWaterTile(currentTileCoordinate,
                dataStore.getCoastLines(currentTileCoordinate))) {
              indexBytes[0] |= BITMAP_INDEX_ENTRY_WATER;
            }
          }
        }

        // seek to index frame of this tile and write relative offset of this
        // tile as five bytes to the index
        indexBuffer.put(indexBytes);

        // ***************** TILE DATA SEGMENT ********************
        // get statistics for tile
        TileData currentTile = dataStore.getTile(zoomIntervalIndex, tileX, tileY);

        // TODO we need to rethink the semantic of zoom levels
        // ***************** POIs ********************
        // write amount of POIs and ways for each zoom level
        Map<Byte, List<TDNode>> poisByZoomlevel = currentTile
            .poisByZoomlevel(minZoomCurrentInterval, maxZoomCurrentInterval);
        Map<Byte, List<TDWay>> waysByZoomlevel = currentTile
            .waysByZoomlevel(minZoomCurrentInterval, maxZoomCurrentInterval);

        if (poisByZoomlevel.size() > 0 || waysByZoomlevel.size() > 0) {
          if (debugStrings) {
            // write tile header
            StringBuilder sb = new StringBuilder();
            sb.append(DEBUG_STRING_TILE_HEAD).append(tileX).append(",")
                .append(tileY)
                .append(DEBUG_STRING_TILE_TAIL);
            tileBuffer.put(sb.toString().getBytes());
            // append withespaces so that block has 32 bytes
            appendWhitespace(DEBUG_BLOCK_SIZE - sb.toString().getBytes().length,
                tileBuffer);
          }

          short cumulatedPOIs = 0;
          short cumulatedWays = 0;
          for (byte zoomlevel = minZoomCurrentInterval; zoomlevel <= maxZoomCurrentInterval; zoomlevel++) {
            if (poisByZoomlevel.get(zoomlevel) != null)
              cumulatedPOIs += poisByZoomlevel.get(zoomlevel).size();
            if (waysByZoomlevel.get(zoomlevel) != null)
              cumulatedWays += waysByZoomlevel.get(zoomlevel).size();
            tileBuffer.putShort(cumulatedPOIs);
            tileBuffer.putShort(cumulatedWays);
          }

          if (maxWaysPerTile < cumulatedWays)
            maxWaysPerTile = cumulatedWays;
          cumulatedNumberOfWaysInTiles += cumulatedWays;

          // clear poi buffer
          poiBuffer.clear();

          // write POIs for each zoom level beginning with lowest zoom level
          for (byte zoomlevel = minZoomCurrentInterval; zoomlevel <= maxZoomCurrentInterval; zoomlevel++) {
            List<TDNode> pois = poisByZoomlevel.get(zoomlevel);
            if (pois == null)
              continue;
            for (TDNode poi : pois) {
              if (debugStrings) {
                StringBuilder sb = new StringBuilder();
                sb.append(DEBUG_STRING_POI_HEAD).append(poi.getId())
                    .append(DEBUG_STRING_POI_TAIL);
                poiBuffer.put(sb.toString().getBytes());
                // append withespaces so that block has 32 bytes
                appendWhitespace(DEBUG_BLOCK_SIZE
                    - sb.toString().getBytes().length,
                    poiBuffer);
              }

              // write poi features to the file
              poiBuffer.put(Serializer.getVariableByteSigned(poi.getLatitude()
                  - currentTileLat));
              poiBuffer.put(Serializer.getVariableByteSigned(poi.getLongitude()
                  - currentTileLon));

              // write byte with layer and tag amount info
              poiBuffer.put(infoByteLayerAndTagAmount(poi.getLayer(),
                  poi.getTags() == null ? 0 : (short) poi.getTags().length));

              // write tag ids to the file
              if (poi.getTags() != null) {
                for (short tagID : poi.getTags()) {
                  poiBuffer
                      .put(Serializer
                          .getVariableByteUnsigned(MapFileWriterTask.TAG_MAPPING
                              .optimizedPoiIds().get(tagID)));
                }
              }

              // write byte with bits set to 1 if the poi has a name, an elevation
              // or a housenumber
              poiBuffer.put(infoBytePOI(poi.getName(),
                  poi.getElevation(),
                  poi.getHouseNumber()));

              if (poi.getName() != null && poi.getName().length() > 0) {
                writeUTF8(poi.getName(), poiBuffer);

              }
              if (poi.getElevation() != 0) {
                poiBuffer.put(Serializer.getVariableByteSigned(poi
                    .getElevation()));
              }
              if (poi.getHouseNumber() != null
                  && poi.getHouseNumber().length() > 0) {
                writeUTF8(poi.getHouseNumber(), poiBuffer);
              }
            }
          } // end for loop over POIs

          // write offset to first way in the tile header
          tileBuffer.put(Serializer.getVariableByteUnsigned(poiBuffer.position()));
          tileBuffer.put(poiBuffer.array(), 0, poiBuffer.position());

          // ***************** WAYS ********************
          // loop over all relevant zoom levels
          for (byte zoomlevel = minZoomCurrentInterval; zoomlevel <= maxZoomCurrentInterval; zoomlevel++) {
            List<TDWay> ways = waysByZoomlevel.get(zoomlevel);
            if (ways == null)
              continue;

            // use executor service to parallelize computation of subtile bitmasks
            // for all
            // ways in the current tile
            short[] bitmaskComputationResults = computeSubtileBitmasks(ways,
                currentTileCoordinate);
            assert bitmaskComputationResults.length == ways.size();
            // needed to access bitmask computation results in the foreach loop
            int i = 0;
            for (TDWay way : ways) {
              wayBuffer.clear();

              WayNodePreprocessingResult wayNodePreprocessingResult = preprocessWayNodes(
                  way, waynodeCompression, pixelCompression, polygonClipping,
                  maxZoomCurrentInterval, baseZoomCurrentInterval,
                  currentTileCoordinate);

              if (wayNodePreprocessingResult == null) {
                continue;
              }
              if (debugStrings) {
                StringBuilder sb = new StringBuilder();
                sb.append(DEBUG_STRING_WAY_HEAD).append(way.getId())
                    .append(DEBUG_STRING_WAY_TAIL);
                tileBuffer.put(sb.toString().getBytes());
                // append withespaces so that block has 32 bytes
                appendWhitespace(DEBUG_BLOCK_SIZE
                    - sb.toString().getBytes().length,
                    tileBuffer);
              }

              // write way features
              wayBuffer.putShort(bitmaskComputationResults[i++]);

              // write byte with layer and tag amount
              wayBuffer.put(infoByteLayerAndTagAmount(way.getLayer(),
                  way.getTags() == null ? 0 : (short) way.getTags().length));

              // write byte with amount of tags which are rendered
              wayBuffer.put(infoByteWayAmountRenderedTags(
                  way.getTags()));

              // write tag bitmap
              wayBuffer.put(infoByteTagBitmask(way.getTags()));

              // write tag ids
              if (way.getTags() != null) {
                for (short tagID : way.getTags()) {
                  wayBuffer.put(
                      Serializer.getVariableByteUnsigned(
                          MapFileWriterTask.TAG_MAPPING
                              .optimizedWayIds().get(tagID)));
                }
              }
              // write the amount of way nodes to the file
              wayBuffer
                  .put(Serializer
                      .getVariableByteUnsigned(wayNodePreprocessingResult
                          .getCoordinates()
                          .size() / 2));

              // write the way nodes:
              // the first node is always stored with four bytes
              // the remaining way node differences are stored according to the
              // compression type
              writeWayNodes(wayNodePreprocessingResult.getCoordinates(),
                  currentTileLat,
                  currentTileLon, wayBuffer);

              // write a byte with name, label and way type information
              wayBuffer.put(infoByteWay(way.getName(), way.getRef(),
                    wayNodePreprocessingResult.getLabelPosition() != null,
                  way.getShape() == TDWay.MULTI_POLYGON));

              // // if the way has a name, write it to the file
              if (way.getName() != null && way.getName().length() > 0) {
                writeUTF8(way.getName(), wayBuffer);
              }

              // if the way has a ref, write it to the file
              if (way.getRef() != null && way.getRef().length() > 0) {
                writeUTF8(way.getRef(), wayBuffer);
              }

              if (wayNodePreprocessingResult.getLabelPosition() != null) {
                // if (way.getId() == 36056459)
                // System.out.println("rewe");
                // logger.info("writing label position: "
                // + wayNodePreprocessingResult.getLabelPosition());
                wayBuffer.put(Serializer
                    .getVariableByteSigned(wayNodePreprocessingResult
                        .getLabelPosition().getLatitudeE6()
                        - wayNodePreprocessingResult.getCoordinates()
                            .get(0)));
                wayBuffer.put(Serializer
                    .getVariableByteSigned(wayNodePreprocessingResult
                        .getLabelPosition().getLongitudeE6()
                        - wayNodePreprocessingResult.getCoordinates()
                            .get(1)));
              }

              // ***************** MULTIPOLYGONS WITH INNER WAYS ***************
              if (way.getShape() == TDWay.MULTI_POLYGON) {
                List<TDWay> innerways = dataStore
                    .getInnerWaysOfMultipolygon(way.getId());
                if (innerways != null && innerways.size() > 0) {

                  wayBuffer.put(Serializer.getVariableByteUnsigned(innerways
                      .size()));
                  for (TDWay innerway : innerways) {
                    WayNodePreprocessingResult innerWayAsList =
                          preprocessWayNodes(innerway,
                              waynodeCompression,
                              pixelCompression,
                              false,
                              maxZoomCurrentInterval,
                              baseZoomCurrentInterval,
                              currentTileCoordinate);
                    // write the amount of way nodes to the file
                    wayBuffer
                        .put(Serializer
                            .getVariableByteUnsigned(innerWayAsList
                                .getCoordinates()
                                .size() / 2));
                    writeInnerWayNodes(wayNodePreprocessingResult
                        .getCoordinates().get(0),
                        wayNodePreprocessingResult.getCoordinates()
                            .get(1),
                        innerWayAsList.getCoordinates(), wayBuffer);
                  }
                }
              }
              tileBuffer.put(Serializer.getVariableByteUnsigned(wayBuffer
                  .position()));
              tileBuffer.put(wayBuffer.array(), 0, wayBuffer.position());
            }
          } // end for loop over ways
        } // end if clause checking if tile is empty or not
        else {
          emptyTiles++;
        }
        long tileSize = tileBuffer.position() - currentTileOffsetInBuffer;
        currentSubfileOffset += tileSize;

        // accounting
        if (maxTileSize < tileSize)
          maxTileSize = tileSize;
        if (tileSize > 0)
          cumulatedTileSizeOfNonEmptyTiles += tileSize;

        // if necessary, allocate new buffer
        if (tileBuffer.remaining() < MIN_TILE_BUFFER_SIZE) {
          randomAccessFile.write(tileBuffer.array(), 0, tileBuffer.position());
          tileBuffer.clear();
        }

        tilesProcessed++;
        if (tilesProcessed % amountOfTilesInPercentStep == 0) {
          LOGGER.info("written " + (tilesProcessed / amountOfTilesInPercentStep)
              * PROGRESS_PERCENT_STEP
              + "% of file");
        }
      } // end for loop over tile columns
    } // /end for loop over tile rows

    // write remaining tiles
    if (tileBuffer.position() > 0) {
      // byte buffer was not previously cleared
      randomAccessFile.write(tileBuffer.array(), 0, tileBuffer.position());
    }

    // write index
    randomAccessFile.seek(startPositionSubfile);
    randomAccessFile.write(indexBuffer.array());
    randomAccessFile.seek(currentSubfileOffset);

    // return size of sub file in bytes
    return currentSubfileOffset;
  } // end writeSubfile()

  private void appendWhitespace(int amount, ByteBuffer buffer) {
    for (int i = 0; i < amount; i++) {
      buffer.put((byte) ' ');
    }
  }

  private List<Integer> waynodesAsList(List<GeoCoordinate> waynodeCoordinates) {
    ArrayList<Integer> result = new ArrayList<Integer>();
    for (GeoCoordinate geoCoordinate : waynodeCoordinates) {
      result.add(geoCoordinate.getLatitudeE6());
      result.add(geoCoordinate.getLongitudeE6());
    }

    return result;
  }

  private WayNodePreprocessingResult preprocessWayNodes(TDWay way,
      boolean waynodeCompression,
      boolean pixelCompression, boolean polygonClipping, byte maxZoomCurrentInterval,
      byte baseZoomCurrentInterval,
      TileCoordinate tile) {
    List<GeoCoordinate> waynodeCoordinates = way.wayNodesAsCoordinateList();
    GeoCoordinate polygonCentroid = null;

    // if the sub file for lower zoom levels is written, remove all way
    // nodes from the list which are projected on the same pixel
    if (pixelCompression && maxZoomCurrentInterval <= MAX_ZOOMLEVEL_PIXEL_FILTER) {
      waynodeCoordinates = GeoUtils.filterWaynodesOnSamePixel(
          waynodeCoordinates,
          maxZoomCurrentInterval, PIXEL_COMPRESSION_MAX_DELTA);
    }

    // if the way is a polygon, clip the way to the current tile
    if (polygonClipping && way.getMinimumZoomLevel() >= baseZoomCurrentInterval) {
      if (way.isPolygon() && waynodeCoordinates.size() >= GeoUtils.MIN_NODES_POLYGON) {
        List<GeoCoordinate> clipped = GeoUtils.clipPolygonToTile(
            waynodeCoordinates, tile, bboxEnlargement, false);

        if (clipped != null && !clipped.isEmpty()) {
          waynodeCoordinates = clipped;
          // DO WE NEED TO COMPUTE A LABEL POSITION?
          if (way.getName() != null && way.getName().length() > 0) {
            // check if the polygon is completely contained in the current tile
            // in that case clipped polygon equals the original polygon
            // as a consequence we do not try to compute a label position
            // this is left to the renderer for more flexibility
            if (clipped.size() != waynodeCoordinates.size()
                && !waynodeCoordinates.containsAll(clipped)) {

              polygonCentroid = GeoUtils.computePolygonCentroid(
                  waynodeCoordinates, way);
            }
            // if the label position is not located in the current tile
            // we can ignore it
            if (polygonCentroid != null
                && !GeoUtils.pointInTile(polygonCentroid, tile))
              polygonCentroid = null;
          }
        } else {
          // the clipped polygon is not included in the current tile
          // !! should not happen !! (as the way would not have been mapped to this
          // tile)
          LOGGER.warning("clipped polygon not in tile: " + way.getId() + " tile: "
              + tile.toString());
          return null;
        }
      }
    }

    // if the wayNodeCompression flag is set, compress the way nodes
    // with a minimal amount of bytes
    List<Integer> waynodesAsList = null;
    if (waynodeCompression) {
      waynodesAsList = GeoUtils.waynodeAbsoluteCoordinatesToOffsets(waynodeCoordinates);
    } else {
      waynodesAsList = waynodesAsList(waynodeCoordinates);
    }

    return new WayNodePreprocessingResult(waynodesAsList, polygonCentroid);
  }

  private byte infoByteLayerAndTagAmount(byte layer, short tagAmount) {
    // make sure layer is in [0,10]
    return (byte) ((layer < 0 ? 0 : layer > 10 ? 10 : layer) << 4 | tagAmount);
  }

  private byte infoByteOptmizationParams(boolean debug, boolean mapStartPosition) {
    byte infoByte = 0;

    if (debug)
      infoByte |= BITMAP_DEBUG;
    if (mapStartPosition)
      infoByte |= BITMAP_MAP_START_POSITION;

    return infoByte;
  }

  private byte infoBytePOI(String name, int elevation, String housenumber) {
    byte infoByte = 0;

    if (name != null && name.length() > 0) {
      infoByte |= BITMAP_NAME;
    }
    if (elevation != 0) {
      infoByte |= BITMAP_ELEVATION;
    }
    if (housenumber != null && housenumber.length() > 0) {
      infoByte |= BITMAP_HOUSENUMBER;
    }
    return infoByte;
  }

  private byte infoByteWay(String name, String ref, boolean labelPosition,
      boolean isMultiPolygon) {
    byte infoByte = 0;

    if (name != null && name.length() > 0) {
      infoByte |= BITMAP_NAME;
    }
    if (ref != null && ref.length() > 0) {
      infoByte |= BITMAP_REF;
    }
    if (labelPosition) {
      infoByte |= BITMAP_LABEL;
    }
    if (isMultiPolygon) {
      infoByte |= BITMAP_MULTIPOLYGON;
    }

    return infoByte;
  }

  private byte infoByteWayAmountRenderedTags(short[] tags) {

    byte infoByte = 0;
    short counter = 0;
    if (tags != null) {
      for (short tagID : tags) {
        if (MapFileWriterTask.TAG_MAPPING.getWayTag(tagID).isRenderable())
          counter++;
      }
      infoByte = (byte) (counter << 5);
    }

    return infoByte;
  }

  private byte infoByteTagBitmask(short[] tags) {
    if (tags == null)
      return 0;
    byte infoByte = 0;

    for (short tagID : tags) {
      switch (WayType.fromString(MapFileWriterTask.TAG_MAPPING.getWayTag(tagID).getKey())) {
        case HIGHWAY:
          infoByte |= BITMAP_HIGHWAY;
          break;
        case RAILWAY:
          infoByte |= BITMAP_RAILWAY;
          break;
        case BUILDING:
          infoByte |= BITMAP_BUILDING;
          break;
        case LANDUSE:
          infoByte |= BITMAP_LANDUSE;
          break;
        case LEISURE:
          infoByte |= BITMAP_LEISURE;
          break;
        case AMENITY:
          infoByte |= BITMAP_AMENITY;
          break;
        case NATURAL:
          infoByte |= BITMAP_NATURAL;
          break;
        case WATERWAY:
          infoByte |= BITMAP_WATERWAY;
          break;
        case UNCLASSIFIED:
          break;
        default:
          break;
      }
    }
    return infoByte;
  }

  private void writeWayNodes(List<Integer> waynodes, int currentTileLat, int currentTileLon,
      ByteBuffer buffer) {
    if (!waynodes.isEmpty()
        && waynodes.size() % 2 == 0) {
      Iterator<Integer> waynodeIterator = waynodes.iterator();
      buffer.put(Serializer.getVariableByteSigned(waynodeIterator.next() - currentTileLat));
      buffer.put(Serializer.getVariableByteSigned(waynodeIterator.next() - currentTileLon));

      while (waynodeIterator.hasNext()) {
        buffer.put(Serializer.getVariableByteSigned(waynodeIterator.next()));
      }
    }
  }

  private void writeInnerWayNodes(int latRef, int lonRef, List<Integer> innerWay,
      ByteBuffer buffer) {

    if (!innerWay.isEmpty()
        && innerWay.size() % 2 == 0) {
      Iterator<Integer> waynodeIterator = innerWay.iterator();
      buffer.put(Serializer.getVariableByteSigned(waynodeIterator.next() - latRef));
      buffer.put(Serializer.getVariableByteSigned(waynodeIterator.next() - lonRef));

      while (waynodeIterator.hasNext()) {
        buffer.put(Serializer.getVariableByteSigned(waynodeIterator.next()));
      }
    }
  }

  private short[] computeSubtileBitmasks(List<TDWay> ways,
      TileCoordinate currentTileCoordinate) {
    short[] bitmaskComputationResults = new short[ways.size()];
    Collection<TileBitmaskComputationTask> tasks = new ArrayList<MapFileWriter.TileBitmaskComputationTask>();
    for (TDWay tdWay : ways) {
      tasks.add(new TileBitmaskComputationTask(currentTileCoordinate,
          tdWay));
    }

    try {
      List<Future<Short>> results = executorService.invokeAll(tasks);
      int i = 0;
      for (Future<Short> future : results) {
        bitmaskComputationResults[i++] = future.get().shortValue();
      }
    } catch (InterruptedException e) {
      throw new RuntimeException(e);
    } catch (ExecutionException e) {
      throw new RuntimeException(e);
    }

    return bitmaskComputationResults;
  }

  private class WayNodePreprocessingResult {

    final List<Integer> coordinates;
    final GeoCoordinate labelPosition;

    public WayNodePreprocessingResult(List<Integer> coordinates, GeoCoordinate labelPosition) {
      super();
      this.coordinates = coordinates;
      this.labelPosition = labelPosition;
    }

    public List<Integer> getCoordinates() {
      return coordinates;
    }

    public GeoCoordinate getLabelPosition() {
      return labelPosition;
    }

  }

  private class TileBitmaskComputationTask implements Callable<Short> {

    private static final short COASTLINE_BITMASK = (short) 0xFFFF;
    private final TileCoordinate baseTile;
    private final TDWay way;

    TileBitmaskComputationTask(TileCoordinate baseTile, TDWay way) {
      super();
      this.baseTile = baseTile;
      this.way = way;
    }

    @Override
    public Short call() {
      if (way.isCoastline()) {
        return COASTLINE_BITMASK;
      }
      return GeoUtils.computeBitmask(way, baseTile, bboxEnlargement);
    }

  }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.