/* * 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.map.hash.TShortIntHashMap; import gnu.trove.procedure.TObjectProcedure; import gnu.trove.set.TLongSet; import gnu.trove.set.hash.TLongHashSet; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import org.mapsforge.core.model.BoundingBox; import org.mapsforge.core.model.Coordinates; import org.mapsforge.core.util.MercatorProjection; import org.mapsforge.map.writer.model.MapWriterConfiguration; import org.mapsforge.map.writer.model.NodeResolver; import org.mapsforge.map.writer.model.TDNode; import org.mapsforge.map.writer.model.TDWay; import org.mapsforge.map.writer.model.TileBasedDataProcessor; import org.mapsforge.map.writer.model.TileCoordinate; import org.mapsforge.map.writer.model.TileData; import org.mapsforge.map.writer.model.TileGridLayout; import org.mapsforge.map.writer.model.WayResolver; import org.mapsforge.map.writer.model.ZoomIntervalConfiguration; import org.mapsforge.map.writer.util.GeoUtils; abstract class BaseTileBasedDataProcessor implements TileBasedDataProcessor, NodeResolver, WayResolver { protected static final Logger LOGGER = Logger.getLogger(BaseTileBasedDataProcessor.class.getName()); protected final org.mapsforge.core.model.BoundingBox boundingbox; protected TileGridLayout[] tileGridLayouts; protected final ZoomIntervalConfiguration zoomIntervalConfiguration; protected final int bboxEnlargement; protected final String preferredLanguage; protected final boolean skipInvalidRelations; protected final TLongObjectHashMap<TLongArrayList> outerToInnerMapping; protected final TLongSet innerWaysWithoutAdditionalTags; protected final Map<TileCoordinate, TLongHashSet> tilesToCoastlines; // accounting protected float[] countWays; protected float[] countWayTileFactor; protected final TShortIntHashMap histogramPoiTags; protected final TShortIntHashMap histogramWayTags; protected long maxWayID = Long.MIN_VALUE; // public BaseTileBasedDataProcessor(double minLat, double maxLat, double minLon, double maxLon, // ZoomIntervalConfiguration zoomIntervalConfiguration, int bboxEnlargement, String preferredLanguage) { // this(new Rect(minLon, maxLon, minLat, maxLat), zoomIntervalConfiguration, bboxEnlargement, // preferredLanguage); // // } public BaseTileBasedDataProcessor(MapWriterConfiguration configuration) { super(); this.boundingbox = configuration.getBboxConfiguration(); this.zoomIntervalConfiguration = configuration.getZoomIntervalConfiguration(); this.tileGridLayouts = new TileGridLayout[this.zoomIntervalConfiguration.getNumberOfZoomIntervals()]; this.bboxEnlargement = configuration.getBboxEnlargement(); this.preferredLanguage = configuration.getPreferredLanguage(); this.skipInvalidRelations = configuration.isSkipInvalidRelations(); this.outerToInnerMapping = new TLongObjectHashMap<TLongArrayList>(); this.innerWaysWithoutAdditionalTags = new TLongHashSet(); this.tilesToCoastlines = new HashMap<TileCoordinate, TLongHashSet>(); this.countWays = new float[this.zoomIntervalConfiguration.getNumberOfZoomIntervals()]; this.countWayTileFactor = new float[this.zoomIntervalConfiguration.getNumberOfZoomIntervals()]; this.histogramPoiTags = new TShortIntHashMap(); this.histogramWayTags = new TShortIntHashMap(); // compute horizontal and vertical tile coordinate offsets for all // base zoom levels for (int i = 0; i < this.zoomIntervalConfiguration.getNumberOfZoomIntervals(); i++) { TileCoordinate upperLeft = new TileCoordinate((int) MercatorProjection.longitudeToTileX( Coordinates.microdegreesToDegrees(this.boundingbox.minLongitudeE6), this.zoomIntervalConfiguration.getBaseZoom(i)), (int) MercatorProjection.latitudeToTileY( Coordinates.microdegreesToDegrees(this.boundingbox.maxLatitudeE6), this.zoomIntervalConfiguration.getBaseZoom(i)), this.zoomIntervalConfiguration.getBaseZoom(i)); this.tileGridLayouts[i] = new TileGridLayout(upperLeft, computeNumberOfHorizontalTiles(i), computeNumberOfVerticalTiles(i)); } } @Override public BoundingBox getBoundingBox() { return this.boundingbox; } @Override public TileGridLayout getTileGridLayout(int zoomIntervalIndex) { return this.tileGridLayouts[zoomIntervalIndex]; } @Override public ZoomIntervalConfiguration getZoomIntervalConfiguration() { return this.zoomIntervalConfiguration; } @Override public long cumulatedNumberOfTiles() { long cumulated = 0; for (int i = 0; i < this.zoomIntervalConfiguration.getNumberOfZoomIntervals(); i++) { cumulated += this.tileGridLayouts[i].getAmountTilesHorizontal() * this.tileGridLayouts[i].getAmountTilesVertical(); } return cumulated; } protected void countPoiTags(TDNode poi) { if (poi == null || poi.getTags() == null) { return; } for (short tag : poi.getTags()) { // NOPMD by bross on 25.12.11 13:48 this.histogramPoiTags.adjustOrPutValue(tag, 1, 1); } } protected void countWayTags(TDWay way) { if (way == null || way.getTags() == null) { return; } for (short tag : way.getTags()) { // NOPMD by bross on 25.12.11 13:48 this.histogramWayTags.adjustOrPutValue(tag, 1, 1); } } protected void countWayTags(short[] tags) { // NOPMD by bross on 25.12.11 13:49 if (tags == null) { return; } for (short tag : tags) { // NOPMD by bross on 25.12.11 13:49 this.histogramWayTags.adjustOrPutValue(tag, 1, 1); } } protected void addPOI(TDNode poi) { if (!poi.isPOI()) { return; } byte minZoomLevel = poi.getZoomAppear(); for (int i = 0; i < this.zoomIntervalConfiguration.getNumberOfZoomIntervals(); i++) { // is POI seen in a zoom interval? if (minZoomLevel <= this.zoomIntervalConfiguration.getMaxZoom(i)) { long tileCoordinateX = MercatorProjection.longitudeToTileX( Coordinates.microdegreesToDegrees(poi.getLongitude()), this.zoomIntervalConfiguration.getBaseZoom(i)); long tileCoordinateY = MercatorProjection.latitudeToTileY( Coordinates.microdegreesToDegrees(poi.getLatitude()), this.zoomIntervalConfiguration.getBaseZoom(i)); TileData tileData = getTileImpl(i, (int) tileCoordinateX, (int) tileCoordinateY); if (tileData != null) { tileData.addPOI(poi); countPoiTags(poi); } } } } protected void addWayToTiles(TDWay way, int enlargement) { int bboxEnlargementLocal = enlargement; byte minZoomLevel = way.getMinimumZoomLevel(); for (int i = 0; i < this.zoomIntervalConfiguration.getNumberOfZoomIntervals(); i++) { // is way seen in a zoom interval? if (minZoomLevel <= this.zoomIntervalConfiguration.getMaxZoom(i)) { Set<TileCoordinate> matchedTiles = GeoUtils.mapWayToTiles(way, this.zoomIntervalConfiguration.getBaseZoom(i), bboxEnlargementLocal); boolean added = false; for (TileCoordinate matchedTile : matchedTiles) { TileData td = getTileImpl(i, matchedTile.getX(), matchedTile.getY()); if (td != null) { countWayTags(way); this.countWayTileFactor[i]++; added = true; td.addWay(way); } } if (added) { this.countWays[i]++; } } } } protected abstract TileData getTileImpl(int zoom, int tileX, int tileY); protected abstract void handleVirtualOuterWay(TDWay virtualWay); // protected abstract void handleAdditionalRelationTags(TDWay virtualWay, TDRelation relation); protected abstract void handleVirtualInnerWay(TDWay virtualWay); private int computeNumberOfHorizontalTiles(int zoomIntervalIndex) { long tileCoordinateLeft = MercatorProjection.longitudeToTileX(this.boundingbox.getMinLongitude(), this.zoomIntervalConfiguration.getBaseZoom(zoomIntervalIndex)); long tileCoordinateRight = MercatorProjection.longitudeToTileX(this.boundingbox.getMaxLongitude(), this.zoomIntervalConfiguration.getBaseZoom(zoomIntervalIndex)); assert tileCoordinateLeft <= tileCoordinateRight; assert tileCoordinateLeft - tileCoordinateRight + 1 < Integer.MAX_VALUE; LOGGER.finer("basezoom: " + this.zoomIntervalConfiguration.getBaseZoom(zoomIntervalIndex) + "\t+n_horizontal: " + (tileCoordinateRight - tileCoordinateLeft + 1)); return (int) (tileCoordinateRight - tileCoordinateLeft + 1); } private int computeNumberOfVerticalTiles(int zoomIntervalIndex) { long tileCoordinateBottom = MercatorProjection.latitudeToTileY(this.boundingbox.getMinLatitude(), this.zoomIntervalConfiguration.getBaseZoom(zoomIntervalIndex)); long tileCoordinateTop = MercatorProjection.latitudeToTileY(this.boundingbox.getMaxLatitude(), this.zoomIntervalConfiguration.getBaseZoom(zoomIntervalIndex)); assert tileCoordinateBottom >= tileCoordinateTop; assert tileCoordinateBottom - tileCoordinateTop + 1 <= Integer.MAX_VALUE; LOGGER.finer("basezoom: " + this.zoomIntervalConfiguration.getBaseZoom(zoomIntervalIndex) + "\t+n_vertical: " + (tileCoordinateBottom - tileCoordinateTop + 1)); return (int) (tileCoordinateBottom - tileCoordinateTop + 1); } // protected class RelationHandler implements TObjectProcedure<TDRelation> { // // private final WayPolygonizer polygonizer = new WayPolygonizer(); // // private List<Integer> inner; // private List<Deque<TDWay>> extractedPolygons; // private Map<Integer, List<Integer>> outerToInner; // // @Override // public boolean execute(TDRelation relation) { // if (relation == null) { // return false; // } // // this.extractedPolygons = null; // this.outerToInner = null; // // TDWay[] members = relation.getMemberWays(); // try { // this.polygonizer.polygonizeAndRelate(members); // } catch (TopologyException e) { // LOGGER.log(Level.FINE, // "cannot relate extracted polygons to each other for relation: " + relation.getId(), e); // } // // // skip invalid relations // if (!this.polygonizer.getDangling().isEmpty()) { // if (BaseTileBasedDataProcessor.this.skipInvalidRelations) { // LOGGER.fine("skipping relation that contains dangling ways which could not be merged to polygons: " // + relation.getId()); // return true; // } // LOGGER.fine("relation contains dangling ways which could not be merged to polygons: " // + relation.getId()); // // } else if (!this.polygonizer.getIllegal().isEmpty()) { // if (BaseTileBasedDataProcessor.this.skipInvalidRelations) { // LOGGER.fine("skipping relation contains illegal closed ways with fewer than 4 nodes: " // + relation.getId()); // return true; // } // LOGGER.fine("relation contains illegal closed ways with fewer than 4 nodes: " + relation.getId()); // } // // this.extractedPolygons = this.polygonizer.getPolygons(); // this.outerToInner = this.polygonizer.getOuterToInner(); // // for (Entry<Integer, List<Integer>> entry : this.outerToInner.entrySet()) { // Deque<TDWay> outerPolygon = this.extractedPolygons.get(entry.getKey().intValue()); // this.inner = null; // this.inner = entry.getValue(); // byte shape = TDWay.SIMPLE_POLYGON; // // does it contain inner ways? // if (this.inner != null && !this.inner.isEmpty()) { // shape = TDWay.MULTI_POLYGON; // } // // TDWay outerWay = null; // if (outerPolygon.size() > 1) { // // we need to create a new way from a set of ways // // collect the way nodes and use the tags of the relation // // if one of the ways has its own tags, we should ignore them, // // ways with relevant tags will be added separately later // if (!relation.isRenderRelevant()) { // LOGGER.fine("constructed outer polygon in relation has no known tags: " + relation.getId()); // continue; // } // // merge way nodes from outer way segments // List<TDNode> waynodeList = new ArrayList<TDNode>(); // for (TDWay outerSegment : outerPolygon) { // if (outerSegment.isReversedInRelation()) { // for (int i = outerSegment.getWayNodes().length - 1; i >= 0; i--) { // waynodeList.add(outerSegment.getWayNodes()[i]); // } // } else { // for (TDNode tdNode : outerSegment.getWayNodes()) { // waynodeList.add(tdNode); // } // } // } // TDNode[] waynodes = waynodeList.toArray(new TDNode[waynodeList.size()]); // // // create new virtual way which represents the outer way // // use maxWayID counter to create unique id // outerWay = new TDWay(++BaseTileBasedDataProcessor.this.maxWayID, relation.getLayer(), // relation.getName(), relation.getHouseNumber(), relation.getRef(), relation.getTags(), // shape, waynodes); // // // add the newly created way to matching tiles // addWayToTiles(outerWay, BaseTileBasedDataProcessor.this.bboxEnlargement); // handleVirtualOuterWay(outerWay); // // adjust tag statistics, cannot be omitted!!! // countWayTags(relation.getTags()); // } // // // the outer way consists of only one segment // else { // outerWay = outerPolygon.getFirst(); // // // is it a polygon that we have seen already and which was // // identified as a polgyon containing inner ways? // if (BaseTileBasedDataProcessor.this.outerToInnerMapping.contains(outerWay.getId())) { // shape = TDWay.MULTI_POLYGON; // } // outerWay.setShape(shape); // // // we merge the name, ref, tag information of the relation to the outer way // // TODO is this true? // // a relation that addresses an already closed way, is normally used to add // // additional information to the way // outerWay.mergeRelationInformation(relation); // // only consider the way, if it has tags, otherwise the renderer cannot interpret // // the way // if (outerWay.isRenderRelevant()) { // // handle relation tags // handleAdditionalRelationTags(outerWay, relation); // addWayToTiles(outerWay, BaseTileBasedDataProcessor.this.bboxEnlargement); // countWayTags(outerWay.getTags()); // } // } // // // relate inner ways to outer way // addInnerWays(outerWay); // } // return true; // } // // private void addInnerWays(TDWay outer) { // if (this.inner != null && !this.inner.isEmpty()) { // // TLongArrayList innerList = BaseTileBasedDataProcessor.this.outerToInnerMapping.get(outer.getId()); // if (innerList == null) { // innerList = new TLongArrayList(); // BaseTileBasedDataProcessor.this.outerToInnerMapping.put(outer.getId(), innerList); // } // // for (Integer innerIndex : this.inner) { // Deque<TDWay> innerSegments = this.extractedPolygons.get(innerIndex.intValue()); // TDWay innerWay = null; // // if (innerSegments.size() == 1) { // innerWay = innerSegments.getFirst(); // if (innerWay.hasTags() && outer.hasTags()) { // short[] iTags = innerWay.getTags(); // short[] oTags = outer.getTags(); // int contained = 0; // for (short iTagID : iTags) { // for (short oTagID : oTags) { // if (iTagID == oTagID) { // contained++; // } // } // } // if (contained == iTags.length) { // BaseTileBasedDataProcessor.this.innerWaysWithoutAdditionalTags.add(innerWay.getId()); // } // } // } else { // List<TDNode> waynodeList = new ArrayList<TDNode>(); // for (TDWay innerSegment : innerSegments) { // if (innerSegment.isReversedInRelation()) { // for (int i = innerSegment.getWayNodes().length - 1; i >= 0; i--) { // waynodeList.add(innerSegment.getWayNodes()[i]); // } // } else { // for (TDNode tdNode : innerSegment.getWayNodes()) { // waynodeList.add(tdNode); // } // } // } // TDNode[] waynodes = waynodeList.toArray(new TDNode[waynodeList.size()]); // // TODO which layer? // innerWay = new TDWay(++BaseTileBasedDataProcessor.this.maxWayID, (byte) 0, null, null, null, // waynodes); // handleVirtualInnerWay(innerWay); // // does not need to be added to corresponding tiles // // virtual inner ways do not have any tags, they are holes in the outer polygon // } // innerList.add(innerWay.getId()); // } // } // } // // } protected class WayHandler implements TObjectProcedure<TDWay> { @Override public boolean execute(TDWay way) { if (way == null) { return true; } // we only consider ways that have tags and which have not already // added as outer way of a relation // inner ways without additional tags are also not considered as they are processed as part of a // multi polygon if (way.isRenderRelevant() && !BaseTileBasedDataProcessor.this.outerToInnerMapping.contains(way.getId()) && !BaseTileBasedDataProcessor.this.innerWaysWithoutAdditionalTags.contains(way.getId())) { addWayToTiles(way, BaseTileBasedDataProcessor.this.bboxEnlargement); } return true; } } }