/*
* Copyright 2010, 2011, 2012 mapsforge.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.mapsforge.map.writer;
import gnu.trove.list.array.TLongArrayList;
import gnu.trove.map.hash.TLongObjectHashMap;
import gnu.trove.procedure.TLongProcedure;
import gnu.trove.set.hash.TLongHashSet;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.mapsforge.core.model.BoundingBox;
import org.mapsforge.map.writer.model.MapWriterConfiguration;
import org.mapsforge.map.writer.model.TDNode;
import org.mapsforge.map.writer.model.TDRelation;
import org.mapsforge.map.writer.model.TDWay;
import org.mapsforge.map.writer.model.TileCoordinate;
import org.mapsforge.map.writer.model.TileData;
import org.mapsforge.map.writer.model.TileInfo;
import org.mapsforge.map.writer.model.ZoomIntervalConfiguration;
import org.mapsforge.map.writer.util.GeoUtils;
import org.openstreetmap.osmosis.core.domain.v0_6.Node;
import org.openstreetmap.osmosis.core.domain.v0_6.Relation;
import org.openstreetmap.osmosis.core.domain.v0_6.Way;
/**
* A TileBasedDataStore that uses the RAM as storage device for temporary data structures.
*
* @author bross
*/
public final class RAMTileBasedDataProcessor extends BaseTileBasedDataProcessor {
private final TLongObjectHashMap<TDNode> nodes;
final TLongObjectHashMap<TDWay> ways;
private final TLongObjectHashMap<TDRelation> multipolygons;
private final RAMTileData[][][] tileData;
private RAMTileBasedDataProcessor(MapWriterConfiguration configuration) {
super(configuration);
this.nodes = new TLongObjectHashMap<TDNode>();
this.ways = new TLongObjectHashMap<TDWay>();
this.multipolygons = new TLongObjectHashMap<TDRelation>();
this.tileData = new RAMTileData[this.zoomIntervalConfiguration.getNumberOfZoomIntervals()][][];
// compute number of tiles needed on each base zoom level
for (int i = 0; i < this.zoomIntervalConfiguration.getNumberOfZoomIntervals(); i++) {
this.tileData[i] = new RAMTileData[this.tileGridLayouts[i].getAmountTilesHorizontal()][this.tileGridLayouts[i]
.getAmountTilesVertical()];
}
}
/**
* Creates a new instance of a {@link RAMTileBasedDataProcessor}.
*
* @param configuration
* the configuration
* @return a new instance of a {@link RAMTileBasedDataProcessor}
*/
public static RAMTileBasedDataProcessor newInstance(MapWriterConfiguration configuration) {
return new RAMTileBasedDataProcessor(configuration);
}
@Override
public TDNode getNode(long id) {
return this.nodes.get(id);
}
@Override
public TDWay getWay(long id) {
return this.ways.get(id);
}
@Override
public BoundingBox getBoundingBox() {
return this.boundingbox;
}
@Override
public ZoomIntervalConfiguration getZoomIntervalConfiguration() {
return this.zoomIntervalConfiguration;
}
@Override
public void addNode(Node node) {
TDNode tdNode = TDNode.fromNode(node, this.preferredLanguage);
this.nodes.put(tdNode.getId(), tdNode);
addPOI(tdNode);
}
@Override
public void addWay(Way way) {
TDWay tdWay = TDWay.fromWay(way, this, this.preferredLanguage);
if (tdWay == null) {
return;
}
this.ways.put(tdWay.getId(), tdWay);
this.maxWayID = Math.max(this.maxWayID, way.getId());
if (tdWay.isCoastline()) {
// find matching tiles on zoom level 12
Set<TileCoordinate> coastLineTiles = GeoUtils.mapWayToTiles(tdWay, TileInfo.TILE_INFO_ZOOMLEVEL, 0);
for (TileCoordinate tileCoordinate : coastLineTiles) {
TLongHashSet coastlines = this.tilesToCoastlines.get(tileCoordinate);
if (coastlines == null) {
coastlines = new TLongHashSet();
this.tilesToCoastlines.put(tileCoordinate, coastlines);
}
coastlines.add(tdWay.getId());
}
}
}
@Override
public void addRelation(Relation relation) {
TDRelation tdRelation = TDRelation.fromRelation(relation, this, this.preferredLanguage);
if (tdRelation != null) {
this.multipolygons.put(relation.getId(), tdRelation);
}
}
@Override
public void complete() {
// Polygonize multipolygon
RelationHandler relationHandler = new RelationHandler();
this.multipolygons.forEachValue(relationHandler);
WayHandler wayHandler = new WayHandler();
this.ways.forEachValue(wayHandler);
OSMTagMapping.getInstance().optimizePoiOrdering(this.histogramPoiTags);
OSMTagMapping.getInstance().optimizeWayOrdering(this.histogramWayTags);
}
@Override
public TileData getTile(int zoom, int tileX, int tileY) {
return getTileImpl(zoom, tileX, tileY);
}
@Override
protected RAMTileData getTileImpl(int zoom, int tileX, int tileY) {
int tileCoordinateXIndex = tileX - this.tileGridLayouts[zoom].getUpperLeft().getX();
int tileCoordinateYIndex = tileY - this.tileGridLayouts[zoom].getUpperLeft().getY();
// check for valid range
if (tileCoordinateXIndex < 0 || tileCoordinateYIndex < 0 || this.tileData[zoom].length <= tileCoordinateXIndex
|| this.tileData[zoom][tileCoordinateXIndex].length <= tileCoordinateYIndex) {
return null;
}
RAMTileData td = this.tileData[zoom][tileCoordinateXIndex][tileCoordinateYIndex];
if (td == null) {
td = new RAMTileData();
this.tileData[zoom][tileCoordinateXIndex][tileCoordinateYIndex] = td;
}
return td;
}
@Override
public Set<TDWay> getCoastLines(TileCoordinate tc) {
if (tc.getZoomlevel() <= TileInfo.TILE_INFO_ZOOMLEVEL) {
return Collections.emptySet();
}
TileCoordinate correspondingOceanTile = tc.translateToZoomLevel(TileInfo.TILE_INFO_ZOOMLEVEL).get(0);
TLongHashSet coastlines = this.tilesToCoastlines.get(correspondingOceanTile);
if (coastlines == null) {
return Collections.emptySet();
}
final Set<TDWay> res = new HashSet<TDWay>();
coastlines.forEach(new TLongProcedure() {
@Override
public boolean execute(long id) {
TDWay way = RAMTileBasedDataProcessor.this.ways.get(id);
if (way != null) {
res.add(way);
return true;
}
return false;
}
});
return res;
}
@Override
public void release() {
// nothing to do here
}
@Override
public List<TDWay> getInnerWaysOfMultipolygon(long outerWayID) {
TLongArrayList innerwayIDs = this.outerToInnerMapping.get(outerWayID);
if (innerwayIDs == null) {
return null;
}
return getInnerWaysOfMultipolygon(innerwayIDs.toArray());
}
private List<TDWay> getInnerWaysOfMultipolygon(long[] innerWayIDs) {
if (innerWayIDs == null) {
return Collections.emptyList();
}
List<TDWay> res = new ArrayList<TDWay>();
for (long id : innerWayIDs) {
TDWay current = this.ways.get(id);
if (current == null) {
continue;
}
res.add(current);
}
return res;
}
@Override
protected void handleVirtualOuterWay(TDWay virtualWay) {
// nothing to do here
}
@Override
protected void handleAdditionalRelationTags(TDWay virtualWay, TDRelation relation) {
// nothing to do here
}
@Override
protected void handleVirtualInnerWay(TDWay virtualWay) {
this.ways.put(virtualWay.getId(), virtualWay);
}
}