/* * 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.iterator.TLongIterator; import gnu.trove.list.array.TLongArrayList; import gnu.trove.map.TLongObjectMap; import gnu.trove.map.hash.TLongObjectHashMap; 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.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.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; import org.openstreetmap.osmosis.core.lifecycle.ReleasableIterator; import org.openstreetmap.osmosis.core.store.IndexedObjectStore; import org.openstreetmap.osmosis.core.store.IndexedObjectStoreReader; import org.openstreetmap.osmosis.core.store.NoSuchIndexElementException; import org.openstreetmap.osmosis.core.store.SimpleObjectStore; import org.openstreetmap.osmosis.core.store.SingleClassObjectSerializationFactory; /** * A TileBasedDataStore that uses the hard disk as storage device for temporary data structures. * * @author bross */ public final class HDTileBasedDataProcessor extends BaseTileBasedDataProcessor { private final IndexedObjectStore<Node> indexedNodeStore; private final IndexedObjectStore<Way> indexedWayStore; private final SimpleObjectStore<Way> wayStore; private final SimpleObjectStore<Relation> relationStore; private final HDTileData[][][] tileData; final TLongObjectMap<TDWay> virtualWays; final TLongObjectMap<List<TDRelation>> additionalRelationTags; private IndexedObjectStoreReader<Node> nodeIndexReader; private IndexedObjectStoreReader<Way> wayIndexReader; private HDTileBasedDataProcessor(MapWriterConfiguration configuration) { super(configuration); this.indexedNodeStore = new IndexedObjectStore<Node>(new SingleClassObjectSerializationFactory(Node.class), "idxNodes"); this.indexedWayStore = new IndexedObjectStore<Way>(new SingleClassObjectSerializationFactory(Way.class), "idxWays"); // indexedRelationStore = new IndexedObjectStore<Relation>( // new SingleClassObjectSerializationFactory( // Relation.class), "idxWays"); this.wayStore = new SimpleObjectStore<Way>(new SingleClassObjectSerializationFactory(Way.class), "heapWays", true); this.relationStore = new SimpleObjectStore<Relation>(new SingleClassObjectSerializationFactory(Relation.class), "heapRelations", true); this.tileData = new HDTileData[this.zoomIntervalConfiguration.getNumberOfZoomIntervals()][][]; for (int i = 0; i < this.zoomIntervalConfiguration.getNumberOfZoomIntervals(); i++) { this.tileData[i] = new HDTileData[this.tileGridLayouts[i].getAmountTilesHorizontal()][this.tileGridLayouts[i] .getAmountTilesVertical()]; } this.virtualWays = new TLongObjectHashMap<TDWay>(); this.additionalRelationTags = new TLongObjectHashMap<List<TDRelation>>(); } /** * Creates a new {@link HDTileBasedDataProcessor}. * * @param configuration * the configuration * @return a new instance of a {@link HDTileBasedDataProcessor} */ public static HDTileBasedDataProcessor newInstance(MapWriterConfiguration configuration) { return new HDTileBasedDataProcessor(configuration); } @Override public void addNode(Node node) { this.indexedNodeStore.add(node.getId(), node); TDNode tdNode = TDNode.fromNode(node, this.preferredLanguage); addPOI(tdNode); } @Override public void addWay(Way way) { this.wayStore.add(way); this.indexedWayStore.add(way.getId(), way); this.maxWayID = Math.max(way.getId(), this.maxWayID); } @Override public void addRelation(Relation relation) { this.relationStore.add(relation); } @Override public synchronized 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 = null; try { current = TDWay.fromWay(this.wayIndexReader.get(id), this, this.preferredLanguage); } catch (NoSuchIndexElementException e) { current = this.virtualWays.get(id); if (current == null) { LOGGER.fine("multipolygon with outer way id " + id + " references non-existing inner way " + id); continue; } } res.add(current); } return res; } @Override public TileData getTile(int baseZoomIndex, int tileCoordinateX, int tileCoordinateY) { HDTileData hdt = getTileImpl(baseZoomIndex, tileCoordinateX, tileCoordinateY); if (hdt == null) { return null; } return fromHDTileData(hdt); } @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); if (this.wayIndexReader == null) { throw new IllegalStateException("way store not accessible, call complete() first"); } TLongHashSet coastlines = this.tilesToCoastlines.get(correspondingOceanTile); if (coastlines == null) { return Collections.emptySet(); } TLongIterator it = coastlines.iterator(); HashSet<TDWay> coastlinesAsTDWay = new HashSet<TDWay>(coastlines.size()); while (it.hasNext()) { long id = it.next(); TDWay tdWay = null; try { tdWay = TDWay.fromWay(this.wayIndexReader.get(id), this, this.preferredLanguage); } catch (NoSuchIndexElementException e) { LOGGER.finer("coastline way non-existing" + id); } if (tdWay != null) { coastlinesAsTDWay.add(tdWay); } } return coastlinesAsTDWay; } // TODO add accounting of average number of tiles per way @Override public void complete() { this.indexedNodeStore.complete(); this.nodeIndexReader = this.indexedNodeStore.createReader(); this.indexedWayStore.complete(); this.wayIndexReader = this.indexedWayStore.createReader(); // handle relations ReleasableIterator<Relation> relationReader = this.relationStore.iterate(); RelationHandler relationHandler = new RelationHandler(); while (relationReader.hasNext()) { Relation entry = relationReader.next(); TDRelation tdRelation = TDRelation.fromRelation(entry, this, this.preferredLanguage); relationHandler.execute(tdRelation); } // handle ways ReleasableIterator<Way> wayReader = this.wayStore.iterate(); WayHandler wayHandler = new WayHandler(); while (wayReader.hasNext()) { Way way = wayReader.next(); TDWay tdWay = TDWay.fromWay(way, this, this.preferredLanguage); if (tdWay == null) { continue; } List<TDRelation> associatedRelations = this.additionalRelationTags.get(tdWay.getId()); if (associatedRelations != null) { for (TDRelation tileDataRelation : associatedRelations) { tdWay.mergeRelationInformation(tileDataRelation); } } wayHandler.execute(tdWay); } OSMTagMapping.getInstance().optimizePoiOrdering(this.histogramPoiTags); OSMTagMapping.getInstance().optimizeWayOrdering(this.histogramWayTags); } @Override protected void handleVirtualOuterWay(TDWay virtualWay) { this.virtualWays.put(virtualWay.getId(), virtualWay); } @Override protected void handleAdditionalRelationTags(TDWay way, TDRelation relation) { List<TDRelation> associatedRelations = this.additionalRelationTags.get(way.getId()); if (associatedRelations == null) { associatedRelations = new ArrayList<TDRelation>(); this.additionalRelationTags.put(way.getId(), associatedRelations); } associatedRelations.add(relation); } @Override protected void handleVirtualInnerWay(TDWay virtualWay) { this.virtualWays.put(virtualWay.getId(), virtualWay); } @Override public void release() { this.indexedNodeStore.release(); this.indexedWayStore.release(); this.wayStore.release(); this.relationStore.release(); } @Override public TDNode getNode(long id) { if (this.nodeIndexReader == null) { throw new IllegalStateException("node store not accessible, call complete() first"); } try { return TDNode.fromNode(this.nodeIndexReader.get(id), this.preferredLanguage); } catch (NoSuchIndexElementException e) { LOGGER.finer("node cannot be found in index: " + id); return null; } } @Override public TDWay getWay(long id) { if (this.wayIndexReader == null) { throw new IllegalStateException("way store not accessible, call complete() first"); } try { return TDWay.fromWay(this.wayIndexReader.get(id), this, this.preferredLanguage); } catch (NoSuchIndexElementException e) { LOGGER.finer("way cannot be found in index: " + id); return null; } } @Override protected HDTileData 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; } HDTileData td = this.tileData[zoom][tileCoordinateXIndex][tileCoordinateYIndex]; if (td == null) { td = new HDTileData(); this.tileData[zoom][tileCoordinateXIndex][tileCoordinateYIndex] = td; } return td; } private RAMTileData fromHDTileData(HDTileData hdt) { final RAMTileData td = new RAMTileData(); TLongIterator it = hdt.getPois().iterator(); while (it.hasNext()) { td.addPOI(TDNode.fromNode(this.nodeIndexReader.get(it.next()), this.preferredLanguage)); } it = hdt.getWays().iterator(); while (it.hasNext()) { TDWay way = null; long id = it.next(); try { way = TDWay.fromWay(this.wayIndexReader.get(id), this, this.preferredLanguage); td.addWay(way); } catch (NoSuchIndexElementException e) { // is it a virtual way? way = this.virtualWays.get(id); if (way != null) { td.addWay(way); } else { LOGGER.finer("referenced way non-existing" + id); } } if (way != null) { if (this.outerToInnerMapping.contains(way.getId())) { way.setShape(TDWay.MULTI_POLYGON); } List<TDRelation> associatedRelations = this.additionalRelationTags.get(id); if (associatedRelations != null) { for (TDRelation tileDataRelation : associatedRelations) { way.mergeRelationInformation(tileDataRelation); } } } } return td; } }