diff --git a/docs/Changelog.md b/docs/Changelog.md
index fd2ecd67..e2e2d8fa 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -6,6 +6,7 @@
- Render themes: line symbol [#124](https://github.com/mapsforge/vtm/issues/124)
- Render themes: stroke dash array [#131](https://github.com/mapsforge/vtm/issues/131)
- POI Search example [#394](https://github.com/mapsforge/vtm/issues/394)
+- Mapsforge Reverse Geocoding [#383](https://github.com/mapsforge/vtm/issues/383)
- Core utilities [#396](https://github.com/mapsforge/vtm/issues/396)
- Mapsforge fix artifacts zoom > 17 [#231](https://github.com/mapsforge/vtm/issues/231)
- vtm-theme-comparator module [#387](https://github.com/mapsforge/vtm/issues/387)
diff --git a/vtm-android-example/AndroidManifest.xml b/vtm-android-example/AndroidManifest.xml
index dc87e92b..27e3093b 100644
--- a/vtm-android-example/AndroidManifest.xml
+++ b/vtm-android-example/AndroidManifest.xml
@@ -97,6 +97,9 @@
+
diff --git a/vtm-android-example/res/values/strings.xml b/vtm-android-example/res/values/strings.xml
index be9f0460..434e8d22 100644
--- a/vtm-android-example/res/values/strings.xml
+++ b/vtm-android-example/res/values/strings.xml
@@ -18,5 +18,6 @@
Show nature
Hide nature
Grid
+ Reverse Geocoding
diff --git a/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java b/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java
index a8d5d039..03365bd6 100644
--- a/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java
+++ b/vtm-android-example/src/org/oscim/android/test/MapsforgeMapActivity.java
@@ -53,6 +53,7 @@ public class MapsforgeMapActivity extends MapActivity {
private TileGridLayer mGridLayer;
private DefaultMapScaleBar mMapScaleBar;
private Menu mMenu;
+ MapFileTileSource mTileSource;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -152,12 +153,12 @@ public class MapsforgeMapActivity extends MapActivity {
return;
}
- MapFileTileSource tileSource = new MapFileTileSource();
- tileSource.setPreferredLanguage("en");
+ mTileSource = new MapFileTileSource();
+ mTileSource.setPreferredLanguage("en");
String file = intent.getStringExtra(FilePicker.SELECTED_FILE);
- if (tileSource.setMapFile(file)) {
+ if (mTileSource.setMapFile(file)) {
- VectorTileLayer l = mMap.setBaseMap(tileSource);
+ VectorTileLayer l = mMap.setBaseMap(mTileSource);
loadTheme(null);
mMap.layers().add(new BuildingLayer(mMap, l));
@@ -175,7 +176,7 @@ public class MapsforgeMapActivity extends MapActivity {
renderer.setOffset(5 * getResources().getDisplayMetrics().density, 0);
mMap.layers().add(mapScaleBarLayer);
- MapInfo info = tileSource.getMapInfo();
+ MapInfo info = mTileSource.getMapInfo();
MapPosition pos = new MapPosition();
pos.setByBoundingBox(info.boundingBox, Tile.SIZE * 4, Tile.SIZE * 4);
mMap.setMapPosition(pos);
diff --git a/vtm-android-example/src/org/oscim/android/test/PoiSearchActivity.java b/vtm-android-example/src/org/oscim/android/test/PoiSearchActivity.java
index 5968f998..f7f9ba86 100644
--- a/vtm-android-example/src/org/oscim/android/test/PoiSearchActivity.java
+++ b/vtm-android-example/src/org/oscim/android/test/PoiSearchActivity.java
@@ -86,8 +86,11 @@ public class PoiSearchActivity extends MapsforgeMapActivity implements ItemizedL
super.onActivityResult(requestCode, resultCode, intent);
if (requestCode == SELECT_MAP_FILE) {
- startActivityForResult(new Intent(this, PoiFilePicker.class),
- SELECT_POI_FILE);
+ if (mTileSource != null)
+ startActivityForResult(new Intent(this, PoiFilePicker.class),
+ SELECT_POI_FILE);
+ else
+ finish();
} else if (requestCode == SELECT_POI_FILE) {
if (resultCode != RESULT_OK || intent == null || intent.getStringExtra(FilePicker.SELECTED_FILE) == null) {
finish();
diff --git a/vtm-android-example/src/org/oscim/android/test/ReverseGeocodeActivity.java b/vtm-android-example/src/org/oscim/android/test/ReverseGeocodeActivity.java
new file mode 100644
index 00000000..184f3fb8
--- /dev/null
+++ b/vtm-android-example/src/org/oscim/android/test/ReverseGeocodeActivity.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright 2017 devemux86
+ *
+ * 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 .
+ */
+package org.oscim.android.test;
+
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.Menu;
+
+import org.oscim.backend.CanvasAdapter;
+import org.oscim.core.GeoPoint;
+import org.oscim.core.GeometryBuffer;
+import org.oscim.core.MercatorProjection;
+import org.oscim.core.Point;
+import org.oscim.core.Tag;
+import org.oscim.core.Tile;
+import org.oscim.event.Gesture;
+import org.oscim.event.GestureListener;
+import org.oscim.event.MotionEvent;
+import org.oscim.layers.Layer;
+import org.oscim.layers.TileGridLayer;
+import org.oscim.map.Map;
+import org.oscim.tiling.source.mapfile.MapDatabase;
+import org.oscim.tiling.source.mapfile.MapReadResult;
+import org.oscim.tiling.source.mapfile.PointOfInterest;
+import org.oscim.tiling.source.mapfile.Way;
+import org.oscim.utils.GeoPointUtils;
+
+import java.util.List;
+
+/**
+ * Reverse Geocoding with long press.
+ *
+ * - POI in specified radius.
+ * - Ways containing touch point.
+ */
+public class ReverseGeocodeActivity extends MapsforgeMapActivity {
+
+ private static final int TOUCH_RADIUS = 32 / 2;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ // Map events receiver
+ mMap.layers().add(new MapEventsReceiver(mMap));
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ return false;
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ super.onActivityResult(requestCode, resultCode, intent);
+
+ if (requestCode == SELECT_MAP_FILE) {
+ // For debug
+ TileGridLayer gridLayer = new TileGridLayer(mMap, getResources().getDisplayMetrics().density);
+ mMap.layers().add(gridLayer);
+ }
+ }
+
+ private class MapEventsReceiver extends Layer implements GestureListener {
+
+ MapEventsReceiver(Map map) {
+ super(map);
+ }
+
+ @Override
+ public boolean onGesture(Gesture g, MotionEvent e) {
+ if (g instanceof Gesture.LongPress) {
+ GeoPoint p = mMap.viewport().fromScreenPoint(e.getX(), e.getY());
+
+ // Read all labeled POI and ways for the area covered by the tiles under touch
+ float touchRadius = TOUCH_RADIUS * CanvasAdapter.dpi / CanvasAdapter.DEFAULT_DPI;
+ long mapSize = MercatorProjection.getMapSize((byte) mMap.getMapPosition().getZoomLevel());
+ double pixelX = MercatorProjection.longitudeToPixelX(p.getLongitude(), mapSize);
+ double pixelY = MercatorProjection.latitudeToPixelY(p.getLatitude(), mapSize);
+ int tileXMin = MercatorProjection.pixelXToTileX(pixelX - touchRadius, (byte) mMap.getMapPosition().getZoomLevel());
+ int tileXMax = MercatorProjection.pixelXToTileX(pixelX + touchRadius, (byte) mMap.getMapPosition().getZoomLevel());
+ int tileYMin = MercatorProjection.pixelYToTileY(pixelY - touchRadius, (byte) mMap.getMapPosition().getZoomLevel());
+ int tileYMax = MercatorProjection.pixelYToTileY(pixelY + touchRadius, (byte) mMap.getMapPosition().getZoomLevel());
+ Tile upperLeft = new Tile(tileXMin, tileYMin, (byte) mMap.getMapPosition().getZoomLevel());
+ Tile lowerRight = new Tile(tileXMax, tileYMax, (byte) mMap.getMapPosition().getZoomLevel());
+ MapReadResult mapReadResult = ((MapDatabase) mTileSource.getDataSource()).readLabels(upperLeft, lowerRight);
+
+ StringBuilder sb = new StringBuilder();
+
+ // Filter POI
+ sb.append("*** POI ***");
+ for (PointOfInterest pointOfInterest : mapReadResult.pointOfInterests) {
+ Point layerXY = new Point();
+ mMap.viewport().toScreenPoint(pointOfInterest.position, false, layerXY);
+ Point tapXY = new Point(e.getX(), e.getY());
+ if (layerXY.distance(tapXY) > touchRadius) {
+ continue;
+ }
+ sb.append("\n");
+ List tags = pointOfInterest.tags;
+ for (Tag tag : tags) {
+ sb.append("\n").append(tag.key).append("=").append(tag.value);
+ }
+ }
+
+ // Filter ways
+ sb.append("\n\n").append("*** WAYS ***");
+ for (Way way : mapReadResult.ways) {
+ if (way.geometryType != GeometryBuffer.GeometryType.POLY
+ || !GeoPointUtils.contains(way.geoPoints[0], p)) {
+ continue;
+ }
+ sb.append("\n");
+ List tags = way.tags;
+ for (Tag tag : tags) {
+ sb.append("\n").append(tag.key).append("=").append(tag.value);
+ }
+ }
+
+ AlertDialog.Builder builder = new AlertDialog.Builder(ReverseGeocodeActivity.this);
+ builder.setIcon(android.R.drawable.ic_menu_search);
+ builder.setTitle(R.string.dialog_reverse_geocoding_title);
+ builder.setMessage(sb);
+ builder.setPositiveButton(R.string.ok, null);
+ builder.show();
+
+ return true;
+ }
+ return false;
+ }
+ }
+}
diff --git a/vtm-android-example/src/org/oscim/android/test/Samples.java b/vtm-android-example/src/org/oscim/android/test/Samples.java
index 5289c016..4198bd8b 100644
--- a/vtm-android-example/src/org/oscim/android/test/Samples.java
+++ b/vtm-android-example/src/org/oscim/android/test/Samples.java
@@ -122,6 +122,7 @@ public class Samples extends Activity {
linearLayout.addView(createButton(MultiMapActivity.class));
linearLayout.addView(createLabel("Experiments"));
+ linearLayout.addView(createButton(ReverseGeocodeActivity.class));
linearLayout.addView(createButton(MapPositionActivity.class));
linearLayout.addView(createButton(S3DBMapActivity.class));
linearLayout.addView(createButton(ThemeStylerActivity.class));
diff --git a/vtm/src/org/oscim/layers/tile/MapTile.java b/vtm/src/org/oscim/layers/tile/MapTile.java
index f3df7ebd..a4a7380b 100644
--- a/vtm/src/org/oscim/layers/tile/MapTile.java
+++ b/vtm/src/org/oscim/layers/tile/MapTile.java
@@ -1,5 +1,6 @@
/*
* Copyright 2012, 2013 Hannes Janetzek
+ * Copyright 2017 devemux86
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -158,6 +159,10 @@ public class MapTile extends Tile {
}
}
+ public MapTile(int tileX, int tileY, int zoomLevel) {
+ this(null, tileX, tileY, zoomLevel);
+ }
+
public MapTile(TileNode node, int tileX, int tileY, int zoomLevel) {
super(tileX, tileY, (byte) zoomLevel);
this.x = (double) tileX / (1 << zoomLevel);
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java b/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java
index e97b923a..11da4e4c 100644
--- a/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java
+++ b/vtm/src/org/oscim/tiling/source/mapfile/MapDatabase.java
@@ -1,6 +1,7 @@
/*
* Copyright 2010, 2011, 2012 mapsforge.org
* Copyright 2013, 2014 Hannes Janetzek
+ * Copyright 2014-2015 Ludwig M Brinckmann
* Copyright 2016-2017 devemux86
* Copyright 2016 Andrey Novikov
*
@@ -20,6 +21,8 @@
package org.oscim.tiling.source.mapfile;
import org.oscim.backend.CanvasAdapter;
+import org.oscim.core.BoundingBox;
+import org.oscim.core.GeoPoint;
import org.oscim.core.GeometryBuffer.GeometryType;
import org.oscim.core.MapElement;
import org.oscim.core.MercatorProjection;
@@ -35,6 +38,9 @@ import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.RandomAccessFile;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
import static org.oscim.core.GeometryBuffer.GeometryType.LINE;
import static org.oscim.core.GeometryBuffer.GeometryType.POLY;
@@ -167,6 +173,18 @@ public class MapDatabase implements ITileDataSource {
*/
private static final int WAY_NUMBER_OF_TAGS_BITMASK = 0x0f;
+ /**
+ * Way filtering reduces the number of ways returned to only those that are
+ * relevant for the tile requested, leading to performance gains, but can
+ * cause line clipping artifacts (particularly at higher zoom levels). The
+ * risk of clipping can be reduced by either turning way filtering off or by
+ * increasing the wayFilterDistance which governs how large an area surrounding
+ * the requested tile will be returned.
+ * For most use cases the standard settings should be sufficient.
+ */
+ public static boolean wayFilterEnabled = true;
+ public static int wayFilterDistance = 20;
+
private long mFileSize;
private boolean mDebugFile;
private RandomAccessFile mInputFile;
@@ -187,6 +205,9 @@ public class MapDatabase implements ITileDataSource {
private final MapFileTileSource mTileSource;
+ private int zoomLevelMin = 0;
+ private int zoomLevelMax = Byte.MAX_VALUE;
+
public MapDatabase(MapFileTileSource tileSource) throws IOException {
mTileSource = tileSource;
try {
@@ -305,7 +326,9 @@ public class MapDatabase implements ITileDataSource {
* @param mapDataSink the callback which handles the extracted map elements.
*/
private void processBlock(QueryParameters queryParameters,
- SubFileParameter subFileParameter, ITileDataSink mapDataSink) {
+ SubFileParameter subFileParameter, ITileDataSink mapDataSink,
+ BoundingBox boundingBox, Selector selector,
+ MapReadResult mapReadResult) {
if (!processBlockSignature()) {
return;
@@ -339,7 +362,13 @@ public class MapDatabase implements ITileDataSource {
return;
}
- if (!processPOIs(mapDataSink, poisOnQueryZoomLevel)) {
+ boolean filterRequired = queryParameters.queryZoomLevel > subFileParameter.baseZoomLevel;
+
+ List pois = null;
+ if (mapReadResult != null)
+ pois = new ArrayList<>();
+
+ if (!processPOIs(mapDataSink, poisOnQueryZoomLevel, boundingBox, filterRequired, pois)) {
return;
}
@@ -355,10 +384,19 @@ public class MapDatabase implements ITileDataSource {
/* move the pointer to the first way */
mReadBuffer.setBufferPosition(firstWayOffset);
- if (!processWays(queryParameters, mapDataSink, waysOnQueryZoomLevel)) {
+ List ways = null;
+ if (mapReadResult != null && Selector.POIS != selector)
+ ways = new ArrayList<>();
+
+ if (!processWays(queryParameters, mapDataSink, waysOnQueryZoomLevel, boundingBox, filterRequired, selector, ways)) {
return;
}
+ if (mapReadResult != null) {
+ if (Selector.POIS == selector)
+ ways = Collections.emptyList();
+ mapReadResult.add(new PoiWayBundle(pois, ways));
+ }
}
// private long mCurrentRow;
@@ -405,8 +443,26 @@ public class MapDatabase implements ITileDataSource {
//private final static Tag mWaterTag = new Tag("natural", "water");
+ /**
+ * Map rendering.
+ */
private void processBlocks(ITileDataSink mapDataSink, QueryParameters queryParams,
SubFileParameter subFileParameter) throws IOException {
+ processBlocks(mapDataSink, queryParams, subFileParameter, null, null, null);
+ }
+
+ /**
+ * Map data reading.
+ */
+ private void processBlocks(QueryParameters queryParams,
+ SubFileParameter subFileParameter, BoundingBox boundingBox,
+ Selector selector, MapReadResult mapReadResult) throws IOException {
+ processBlocks(null, queryParams, subFileParameter, boundingBox, selector, mapReadResult);
+ }
+
+ private void processBlocks(ITileDataSink mapDataSink, QueryParameters queryParams,
+ SubFileParameter subFileParameter, BoundingBox boundingBox,
+ Selector selector, MapReadResult mapReadResult) 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++) {
@@ -508,7 +564,7 @@ public class MapDatabase implements ITileDataSource {
mTileLatitude = (int) (tileLatitudeDeg * 1E6);
mTileLongitude = (int) (tileLongitudeDeg * 1E6);
- processBlock(queryParams, subFileParameter, mapDataSink);
+ processBlock(queryParams, subFileParameter, mapDataSink, boundingBox, selector, mapReadResult);
}
}
}
@@ -539,7 +595,8 @@ public class MapDatabase implements ITileDataSource {
* @return true if the POIs could be processed successfully, false
* otherwise.
*/
- private boolean processPOIs(ITileDataSink mapDataSink, int numberOfPois) {
+ private boolean processPOIs(ITileDataSink mapDataSink, int numberOfPois, BoundingBox boundingBox,
+ boolean filterRequired, List pois) {
Tag[] poiTags = mTileSource.fileInfo.poiTags;
MapElement e = mElem;
@@ -605,13 +662,26 @@ public class MapDatabase implements ITileDataSource {
e.setLayer(layer);
- mapDataSink.process(e);
+ if (pois != null) {
+ List tags = new ArrayList<>();
+ for (int i = 0; i < e.tags.numTags; i++)
+ tags.add(e.tags.tags[i]);
+ GeoPoint position = new GeoPoint(latitude, longitude);
+ // depending on the zoom level configuration the poi can lie outside
+ // the tile requested, we filter them out here
+ if (!filterRequired || boundingBox.contains(position)) {
+ pois.add(new PointOfInterest(layer, tags, position));
+ }
+ }
+
+ if (mapDataSink != null)
+ mapDataSink.process(e);
}
return true;
}
- private boolean processWayDataBlock(MapElement e, boolean doubleDeltaEncoding, boolean isLine) {
+ private boolean processWayDataBlock(MapElement e, boolean doubleDeltaEncoding, boolean isLine, List wayCoordinates) {
/* get and check the number of way coordinate blocks (VBE-U) */
int numBlocks = mReadBuffer.readUnsignedInt();
if (numBlocks < 1 || numBlocks > Short.MAX_VALUE) {
@@ -638,6 +708,14 @@ public class MapDatabase implements ITileDataSource {
wayLengths[coordinateBlock] = decodeWayNodes(doubleDeltaEncoding,
e, len, isLine);
+
+ if (wayCoordinates != null) {
+ // create the array which will store the current way segment
+ GeoPoint[] waySegment = new GeoPoint[e.getNumPoints()];
+ for (int i = 0; i < e.getNumPoints(); i++)
+ waySegment[i] = new GeoPoint(e.getPointY(i) / 1E6, e.getPointX(i) / 1E6);
+ wayCoordinates.add(waySegment);
+ }
}
return true;
@@ -712,8 +790,9 @@ public class MapDatabase implements ITileDataSource {
* @return true if the ways could be processed successfully, false
* otherwise.
*/
- private boolean processWays(QueryParameters queryParameters,
- ITileDataSink mapDataSink, int numberOfWays) {
+ private boolean processWays(QueryParameters queryParameters, ITileDataSink mapDataSink,
+ int numberOfWays, BoundingBox boundingBox, boolean filterRequired,
+ Selector selector, List ways) {
Tag[] wayTags = mTileSource.fileInfo.wayTags;
MapElement e = mElem;
@@ -866,7 +945,11 @@ public class MapDatabase implements ITileDataSource {
for (int wayDataBlock = 0; wayDataBlock < wayDataBlocks; wayDataBlock++) {
e.clear();
- if (!processWayDataBlock(e, featureWayDoubleDeltaEncoding, linearFeature))
+ List wayNodes = null;
+ if (ways != null)
+ wayNodes = new ArrayList<>();
+
+ if (!processWayDataBlock(e, featureWayDoubleDeltaEncoding, linearFeature, wayNodes))
return false;
/* drop invalid outer ring */
@@ -889,13 +972,112 @@ public class MapDatabase implements ITileDataSource {
e.setLayer(layer);
- mapDataSink.process(e);
+ if (ways != null) {
+ BoundingBox wayFilterBbox = boundingBox.extendMeters(wayFilterDistance);
+ GeoPoint[][] wayNodesArray = wayNodes.toArray(new GeoPoint[wayNodes.size()][]);
+ if (!filterRequired || !wayFilterEnabled || wayFilterBbox.intersectsArea(wayNodesArray)) {
+ List tags = new ArrayList<>();
+ for (int i = 0; i < e.tags.numTags; i++)
+ tags.add(e.tags.tags[i]);
+ if (Selector.ALL == selector || hasName || hasHouseNr || hasRef || wayAsLabelTagFilter(tags)) {
+ GeoPoint labelPos = e.labelPosition != null ? new GeoPoint(e.labelPosition.y / 1E6, e.labelPosition.x / 1E6) : null;
+ ways.add(new Way(layer, tags, wayNodesArray, labelPos, e.type));
+ }
+ }
+ }
+
+ if (mapDataSink != null)
+ mapDataSink.process(e);
}
}
return true;
}
+ /**
+ * Reads only labels for tile.
+ *
+ * @param tile tile for which data is requested.
+ * @return label data for the tile.
+ */
+ public MapReadResult readLabels(Tile tile) {
+ return readMapData(tile, tile, Selector.LABELS);
+ }
+
+ /**
+ * Reads data for an area defined by the tile in the upper left and the tile in
+ * the lower right corner.
+ * Precondition: upperLeft.tileX <= lowerRight.tileX && upperLeft.tileY <= lowerRight.tileY
+ *
+ * @param upperLeft tile that defines the upper left corner of the requested area.
+ * @param lowerRight tile that defines the lower right corner of the requested area.
+ * @return map data for the tile.
+ */
+ public MapReadResult readLabels(Tile upperLeft, Tile lowerRight) {
+ return readMapData(upperLeft, lowerRight, Selector.LABELS);
+ }
+
+ /**
+ * Reads all map data for the area covered by the given tile at the tile zoom level.
+ *
+ * @param tile defines area and zoom level of read map data.
+ * @return the read map data.
+ */
+ public MapReadResult readMapData(Tile tile) {
+ return readMapData(tile, tile, Selector.ALL);
+ }
+
+ /**
+ * Reads data for an area defined by the tile in the upper left and the tile in
+ * the lower right corner.
+ * Precondition: upperLeft.tileX <= lowerRight.tileX && upperLeft.tileY <= lowerRight.tileY
+ *
+ * @param upperLeft tile that defines the upper left corner of the requested area.
+ * @param lowerRight tile that defines the lower right corner of the requested area.
+ * @return map data for the tile.
+ */
+ public MapReadResult readMapData(Tile upperLeft, Tile lowerRight) {
+ return readMapData(upperLeft, lowerRight, Selector.ALL);
+ }
+
+ private MapReadResult readMapData(Tile upperLeft, Tile lowerRight, Selector selector) {
+ if (mTileSource.fileHeader == null)
+ return null;
+
+ MapReadResult mapReadResult = new MapReadResult();
+
+ if (mIntBuffer == null)
+ mIntBuffer = new int[Short.MAX_VALUE * 2];
+
+ try {
+ mTileProjection.setTile(upperLeft);
+
+ QueryParameters queryParameters = new QueryParameters();
+ queryParameters.queryZoomLevel =
+ mTileSource.fileHeader.getQueryZoomLevel(upperLeft.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);
+
+ return null;
+ }
+
+ QueryCalculations.calculateBaseTiles(queryParameters, upperLeft, lowerRight, subFileParameter);
+ QueryCalculations.calculateBlocks(queryParameters, subFileParameter);
+ processBlocks(queryParameters, subFileParameter, Tile.getBoundingBox(upperLeft, lowerRight), selector, mapReadResult);
+ } catch (IOException e) {
+ log.error(e.getMessage());
+ return null;
+ }
+
+ return mapReadResult;
+ }
+
private int[] readOptionalLabelPosition() {
int[] labelPosition = new int[2];
@@ -908,6 +1090,29 @@ public class MapDatabase implements ITileDataSource {
return labelPosition;
}
+ /**
+ * Reads only POI data for tile.
+ *
+ * @param tile tile for which data is requested.
+ * @return POI data for the tile.
+ */
+ public MapReadResult readPoiData(Tile tile) {
+ return readMapData(tile, tile, Selector.POIS);
+ }
+
+ /**
+ * Reads POI data for an area defined by the tile in the upper left and the tile in
+ * the lower right corner.
+ * This implementation takes the data storage of a MapFile into account for greater efficiency.
+ *
+ * @param upperLeft tile that defines the upper left corner of the requested area.
+ * @param lowerRight tile that defines the lower right corner of the requested area.
+ * @return map data for the tile.
+ */
+ public MapReadResult readPoiData(Tile upperLeft, Tile lowerRight) {
+ return readMapData(upperLeft, lowerRight, Selector.POIS);
+ }
+
private int[][] readZoomTable(SubFileParameter subFileParameter) {
int rows = subFileParameter.zoomLevelMax - subFileParameter.zoomLevelMin + 1;
int[][] zoomTable = new int[rows][2];
@@ -926,6 +1131,51 @@ public class MapDatabase implements ITileDataSource {
return zoomTable;
}
+ /**
+ * Restricts returns of data to zoom level range specified. This can be used to restrict
+ * the use of this map data base when used in MultiMapDatabase settings.
+ *
+ * @param minZoom minimum zoom level supported
+ * @param maxZoom maximum zoom level supported
+ */
+ public void restrictToZoomRange(int minZoom, int maxZoom) {
+ this.zoomLevelMax = maxZoom;
+ this.zoomLevelMin = minZoom;
+ }
+
+ /**
+ * Returns true if MapDatabase contains tile.
+ *
+ * @param tile tile to be rendered.
+ * @return true if tile is part of database.
+ */
+ public boolean supportsTile(Tile tile) {
+ return tile.getBoundingBox().intersects(mTileSource.getMapInfo().boundingBox)
+ && (tile.zoomLevel >= this.zoomLevelMin && tile.zoomLevel <= this.zoomLevelMax);
+ }
+
+ /**
+ * Returns true if a way should be included in the result set for readLabels()
+ * By default only ways with names, house numbers or a ref are included in the result set
+ * of readLabels(). This is to reduce the set of ways as much as possible to save memory.
+ *
+ * @param tags the tags associated with the way
+ * @return true if the way should be included in the result set
+ */
+ public boolean wayAsLabelTagFilter(List tags) {
+ return false;
+ }
+
+ /**
+ * The Selector enum is used to specify which data subset is to be retrieved from a MapFile:
+ * ALL: all data (as in version 0.6.0)
+ * POIS: only poi data, no ways (new after 0.6.0)
+ * LABELS: poi data and ways that have a name (new after 0.6.0)
+ */
+ private enum Selector {
+ ALL, POIS, LABELS
+ }
+
static class TileProjection {
private static final double COORD_SCALE = 1000000.0;
@@ -947,7 +1197,7 @@ public class MapDatabase implements ITileDataSource {
/* scales longitude(1e6) to map-pixel */
divx = (180.0 * COORD_SCALE) / (mapExtents >> 1);
- /* scale latidute to map-pixel */
+ /* scale latitude to map-pixel */
divy = (Math.PI * 2.0) / (mapExtents >> 1);
}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MapReadResult.java b/vtm/src/org/oscim/tiling/source/mapfile/MapReadResult.java
new file mode 100644
index 00000000..06bd183f
--- /dev/null
+++ b/vtm/src/org/oscim/tiling/source/mapfile/MapReadResult.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2010, 2011, 2012, 2013 mapsforge.org
+ * Copyright 2014-2015 Ludwig M Brinckmann
+ * Copyright 2017 devemux86
+ *
+ * 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 .
+ */
+package org.oscim.tiling.source.mapfile;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An immutable container for the data returned from a MapDataStore.
+ */
+public class MapReadResult {
+
+ /**
+ * True if the read area is completely covered by water, false otherwise.
+ */
+ public boolean isWater;
+
+ /**
+ * The read POIs.
+ */
+ public List pointOfInterests;
+
+ /**
+ * The read ways.
+ */
+ public List ways;
+
+ public MapReadResult() {
+ this.pointOfInterests = new ArrayList<>();
+ this.ways = new ArrayList<>();
+ }
+
+ public void add(PoiWayBundle poiWayBundle) {
+ this.pointOfInterests.addAll(poiWayBundle.pois);
+ this.ways.addAll(poiWayBundle.ways);
+ }
+
+ /**
+ * Adds other MapReadResult by combining pois and ways. Optionally, deduplication can
+ * be requested (much more expensive).
+ *
+ * @param other the MapReadResult to add to this.
+ * @param deduplicate true if check for duplicates is required.
+ */
+ public void add(MapReadResult other, boolean deduplicate) {
+ if (deduplicate) {
+ for (PointOfInterest poi : other.pointOfInterests) {
+ if (!this.pointOfInterests.contains(poi)) {
+ this.pointOfInterests.add(poi);
+ }
+ }
+ for (Way way : other.ways) {
+ if (!this.ways.contains(way)) {
+ this.ways.add(way);
+ }
+ }
+ } else {
+ this.pointOfInterests.addAll(other.pointOfInterests);
+ this.ways.addAll(other.ways);
+ }
+ }
+
+}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java
index b65ffb17..468785a7 100644
--- a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java
+++ b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapDatabase.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 devemux86
+ * Copyright 2016-2017 devemux86
*
* 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
@@ -14,6 +14,7 @@
*/
package org.oscim.tiling.source.mapfile;
+import org.oscim.core.Tile;
import org.oscim.layers.tile.MapTile;
import org.oscim.tiling.ITileDataSink;
import org.oscim.tiling.ITileDataSource;
@@ -41,8 +42,7 @@ public class MultiMapDatabase implements ITileDataSource {
public void query(MapTile tile, ITileDataSink mapDataSink) {
MultiMapDataSink multiMapDataSink = new MultiMapDataSink(mapDataSink);
for (MapDatabase mapDatabase : mapDatabases) {
- int[] zoomLevels = tileSource.getZoomsByTileSource().get(mapDatabase.getTileSource());
- if (zoomLevels == null || (zoomLevels[0] <= tile.zoomLevel && tile.zoomLevel <= zoomLevels[1]))
+ if (mapDatabase.supportsTile(tile))
mapDatabase.query(tile, multiMapDataSink);
}
mapDataSink.completed(multiMapDataSink.getResult());
@@ -61,4 +61,109 @@ public class MultiMapDatabase implements ITileDataSource {
mapDatabase.cancel();
}
}
+
+ public MapReadResult readLabels(Tile tile) {
+ MapReadResult mapReadResult = new MapReadResult();
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(tile)) {
+ MapReadResult result = mdb.readLabels(tile);
+ if (result == null) {
+ continue;
+ }
+ boolean isWater = mapReadResult.isWater & result.isWater;
+ mapReadResult.isWater = isWater;
+ mapReadResult.add(result, false);
+ }
+ }
+ return mapReadResult;
+ }
+
+ public MapReadResult readLabels(Tile upperLeft, Tile lowerRight) {
+ MapReadResult mapReadResult = new MapReadResult();
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(upperLeft)) {
+ MapReadResult result = mdb.readLabels(upperLeft, lowerRight);
+ if (result == null) {
+ continue;
+ }
+ boolean isWater = mapReadResult.isWater & result.isWater;
+ mapReadResult.isWater = isWater;
+ mapReadResult.add(result, false);
+ }
+ }
+ return mapReadResult;
+ }
+
+ public MapReadResult readMapData(Tile tile) {
+ MapReadResult mapReadResult = new MapReadResult();
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(tile)) {
+ MapReadResult result = mdb.readMapData(tile);
+ if (result == null) {
+ continue;
+ }
+ boolean isWater = mapReadResult.isWater & result.isWater;
+ mapReadResult.isWater = isWater;
+ mapReadResult.add(result, false);
+ }
+ }
+ return mapReadResult;
+ }
+
+ public MapReadResult readMapData(Tile upperLeft, Tile lowerRight) {
+ MapReadResult mapReadResult = new MapReadResult();
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(upperLeft)) {
+ MapReadResult result = mdb.readMapData(upperLeft, lowerRight);
+ if (result == null) {
+ continue;
+ }
+ boolean isWater = mapReadResult.isWater & result.isWater;
+ mapReadResult.isWater = isWater;
+ mapReadResult.add(result, false);
+ }
+ }
+ return mapReadResult;
+ }
+
+ public MapReadResult readPoiData(Tile tile) {
+ MapReadResult mapReadResult = new MapReadResult();
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(tile)) {
+ MapReadResult result = mdb.readPoiData(tile);
+ if (result == null) {
+ continue;
+ }
+ boolean isWater = mapReadResult.isWater & result.isWater;
+ mapReadResult.isWater = isWater;
+ mapReadResult.add(result, false);
+ }
+ }
+ return mapReadResult;
+ }
+
+ public MapReadResult readPoiData(Tile upperLeft, Tile lowerRight) {
+ MapReadResult mapReadResult = new MapReadResult();
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(upperLeft)) {
+ MapReadResult result = mdb.readPoiData(upperLeft, lowerRight);
+ if (result == null) {
+ continue;
+ }
+ boolean isWater = mapReadResult.isWater & result.isWater;
+ mapReadResult.isWater = isWater;
+ mapReadResult.add(result, false);
+ }
+ }
+ return mapReadResult;
+ }
+
+ public boolean supportsTile(Tile tile) {
+ for (MapDatabase mdb : mapDatabases) {
+ if (mdb.supportsTile(tile)) {
+ return true;
+ }
+ }
+ return false;
+ }
}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java
index 93bed95f..96fbf24e 100644
--- a/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java
+++ b/vtm/src/org/oscim/tiling/source/mapfile/MultiMapFileTileSource.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2016 devemux86
+ * Copyright 2016-2017 devemux86
*
* 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
@@ -63,16 +63,16 @@ public class MultiMapFileTileSource extends TileSource implements IMapFileTileSo
return boundingBox;
}
- Map getZoomsByTileSource() {
- return zoomsByTileSource;
- }
-
@Override
public ITileDataSource getDataSource() {
MultiMapDatabase multiMapDatabase = new MultiMapDatabase(this);
for (MapFileTileSource mapFileTileSource : mapFileTileSources) {
try {
- multiMapDatabase.add(new MapDatabase(mapFileTileSource));
+ MapDatabase mapDatabase = new MapDatabase(mapFileTileSource);
+ int[] zoomLevels = zoomsByTileSource.get(mapFileTileSource);
+ if (zoomLevels != null)
+ mapDatabase.restrictToZoomRange(zoomLevels[0], zoomLevels[1]);
+ multiMapDatabase.add(mapDatabase);
} catch (IOException e) {
log.debug(e.getMessage());
}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/PoiWayBundle.java b/vtm/src/org/oscim/tiling/source/mapfile/PoiWayBundle.java
new file mode 100644
index 00000000..75722202
--- /dev/null
+++ b/vtm/src/org/oscim/tiling/source/mapfile/PoiWayBundle.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2010, 2011, 2012, 2013 mapsforge.org
+ * Copyright 2017 devemux86
+ *
+ * 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 .
+ */
+package org.oscim.tiling.source.mapfile;
+
+import java.util.List;
+
+public class PoiWayBundle {
+ final List pois;
+ final List ways;
+
+ public PoiWayBundle(List pois, List ways) {
+ this.pois = pois;
+ this.ways = ways;
+ }
+}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/PointOfInterest.java b/vtm/src/org/oscim/tiling/source/mapfile/PointOfInterest.java
new file mode 100644
index 00000000..e2a212cf
--- /dev/null
+++ b/vtm/src/org/oscim/tiling/source/mapfile/PointOfInterest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2010, 2011, 2012, 2013 mapsforge.org
+ * Copyright 2014-2015 Ludwig M Brinckmann
+ * Copyright 2017 devemux86
+ *
+ * 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 .
+ */
+package org.oscim.tiling.source.mapfile;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.core.Tag;
+
+import java.util.List;
+
+/**
+ * An immutable container for all data associated with a single point of interest node (POI).
+ */
+public class PointOfInterest {
+ /**
+ * The layer of this POI + 5 (to avoid negative values).
+ */
+ public final byte layer;
+
+ /**
+ * The position of this POI.
+ */
+ public final GeoPoint position;
+
+ /**
+ * The tags of this POI.
+ */
+ public final List tags;
+
+ public PointOfInterest(byte layer, List tags, GeoPoint position) {
+ this.layer = layer;
+ this.tags = tags;
+ this.position = position;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (!(obj instanceof PointOfInterest)) {
+ return false;
+ }
+ PointOfInterest other = (PointOfInterest) obj;
+ if (this.layer != other.layer) {
+ return false;
+ } else if (!this.tags.equals(other.tags)) {
+ return false;
+ } else if (!this.position.equals(other.position)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + layer;
+ result = prime * result + tags.hashCode();
+ result = prime * result + position.hashCode();
+ return result;
+ }
+
+}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/QueryCalculations.java b/vtm/src/org/oscim/tiling/source/mapfile/QueryCalculations.java
index a3d4dd52..2f3805e6 100644
--- a/vtm/src/org/oscim/tiling/source/mapfile/QueryCalculations.java
+++ b/vtm/src/org/oscim/tiling/source/mapfile/QueryCalculations.java
@@ -1,5 +1,7 @@
/*
* Copyright 2010, 2011, 2012 mapsforge.org
+ * Copyright 2014-2015 Ludwig M Brinckmann
+ * Copyright 2017 devemux86
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
@@ -100,18 +102,14 @@ final class QueryCalculations {
}
}
- static void calculateBaseTiles(QueryParameters queryParameters, Tile tile,
- SubFileParameter subFileParameter) {
+ static void calculateBaseTiles(QueryParameters queryParameters, Tile tile, SubFileParameter subFileParameter) {
if (tile.zoomLevel < subFileParameter.baseZoomLevel) {
- // calculate the XY numbers of the upper left and lower right
- // sub-tiles
+ // calculate the XY numbers of the upper left and lower right sub-tiles
int zoomLevelDifference = subFileParameter.baseZoomLevel - tile.zoomLevel;
queryParameters.fromBaseTileX = tile.tileX << zoomLevelDifference;
queryParameters.fromBaseTileY = tile.tileY << zoomLevelDifference;
- queryParameters.toBaseTileX = queryParameters.fromBaseTileX
- + (1 << zoomLevelDifference) - 1;
- queryParameters.toBaseTileY = queryParameters.fromBaseTileY
- + (1 << zoomLevelDifference) - 1;
+ queryParameters.toBaseTileX = queryParameters.fromBaseTileX + (1 << zoomLevelDifference) - 1;
+ queryParameters.toBaseTileY = queryParameters.fromBaseTileY + (1 << zoomLevelDifference) - 1;
queryParameters.useTileBitmask = false;
} else if (tile.zoomLevel > subFileParameter.baseZoomLevel) {
// calculate the XY numbers of the parent base tile
@@ -132,6 +130,37 @@ final class QueryCalculations {
}
}
+ static void calculateBaseTiles(QueryParameters queryParameters, Tile upperLeft, Tile lowerRight, SubFileParameter subFileParameter) {
+ if (upperLeft.zoomLevel < subFileParameter.baseZoomLevel) {
+ // here we need to combine multiple base tiles
+ int zoomLevelDifference = subFileParameter.baseZoomLevel - upperLeft.zoomLevel;
+ queryParameters.fromBaseTileX = upperLeft.tileX << zoomLevelDifference;
+ queryParameters.fromBaseTileY = upperLeft.tileY << zoomLevelDifference;
+ queryParameters.toBaseTileX = (lowerRight.tileX << zoomLevelDifference) + (1 << zoomLevelDifference) - 1;
+ queryParameters.toBaseTileY = (lowerRight.tileY << zoomLevelDifference) + (1 << zoomLevelDifference) - 1;
+ queryParameters.useTileBitmask = false;
+ } else if (upperLeft.zoomLevel > subFileParameter.baseZoomLevel) {
+ // we might have more than just one base tile as we might span boundaries
+ int zoomLevelDifference = upperLeft.zoomLevel - subFileParameter.baseZoomLevel;
+ queryParameters.fromBaseTileX = upperLeft.tileX >>> zoomLevelDifference;
+ queryParameters.fromBaseTileY = upperLeft.tileY >>> zoomLevelDifference;
+ queryParameters.toBaseTileX = lowerRight.tileX >>> zoomLevelDifference;
+ queryParameters.toBaseTileY = lowerRight.tileY >>> zoomLevelDifference;
+ // TODO understand what is going on here. The tileBitmask is used to extract just
+ // the data from the base tiles that is relevant for the area, but how can this work
+ // for a set of tiles, so not using tileBitmask for the moment.
+ queryParameters.useTileBitmask = true;
+ queryParameters.queryTileBitmask = QueryCalculations.calculateTileBitmask(upperLeft, lowerRight, zoomLevelDifference);
+ } else {
+ // we are on the base zoom level, so we just need all tiles in range
+ queryParameters.fromBaseTileX = upperLeft.tileX;
+ queryParameters.fromBaseTileY = upperLeft.tileY;
+ queryParameters.toBaseTileX = lowerRight.tileX;
+ queryParameters.toBaseTileY = lowerRight.tileY;
+ queryParameters.useTileBitmask = false;
+ }
+ }
+
static void calculateBlocks(QueryParameters queryParameters, SubFileParameter subFileParameter) {
// calculate the blocks in the file which need to be read
queryParameters.fromBlockX = Math.max(queryParameters.fromBaseTileX
@@ -171,6 +200,17 @@ final class QueryCalculations {
}
}
+ static int calculateTileBitmask(Tile upperLeft, Tile lowerRight, int zoomLevelDifference) {
+ int bitmask = 0;
+ for (int x = upperLeft.tileX; x <= lowerRight.tileX; x++) {
+ for (int y = upperLeft.tileY; y <= lowerRight.tileY; y++) {
+ Tile current = new Tile(x, y, upperLeft.zoomLevel);
+ bitmask |= calculateTileBitmask(current, zoomLevelDifference);
+ }
+ }
+ return bitmask;
+ }
+
private QueryCalculations() {
throw new IllegalStateException();
}
diff --git a/vtm/src/org/oscim/tiling/source/mapfile/Way.java b/vtm/src/org/oscim/tiling/source/mapfile/Way.java
new file mode 100644
index 00000000..2ab54c6c
--- /dev/null
+++ b/vtm/src/org/oscim/tiling/source/mapfile/Way.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2010, 2011, 2012, 2013 mapsforge.org
+ * Copyright 2014-2015 Ludwig M Brinckmann
+ * Copyright 2017 devemux86
+ *
+ * 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 .
+ */
+package org.oscim.tiling.source.mapfile;
+
+import org.oscim.core.GeoPoint;
+import org.oscim.core.GeometryBuffer;
+import org.oscim.core.Tag;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * An immutable container for all data associated with a single way or area (closed way).
+ */
+public class Way {
+ /**
+ * The position of the area label (may be null).
+ */
+ public final GeoPoint labelPosition;
+
+ /**
+ * The geometry type.
+ */
+ public GeometryBuffer.GeometryType geometryType = GeometryBuffer.GeometryType.NONE;
+
+ /**
+ * The geographical coordinates of the way nodes.
+ */
+ public final GeoPoint[][] geoPoints;
+
+ /**
+ * The layer of this way + 5 (to avoid negative values).
+ */
+ public final byte layer;
+
+ /**
+ * The tags of this way.
+ */
+ public final List tags;
+
+ public Way(byte layer, List tags, GeoPoint[][] geoPoints, GeoPoint labelPosition) {
+ this.layer = layer;
+ this.tags = tags;
+ this.geoPoints = geoPoints;
+ this.labelPosition = labelPosition;
+ }
+
+ public Way(byte layer, List tags, GeoPoint[][] geoPoints, GeoPoint labelPosition, final GeometryBuffer.GeometryType geometryType) {
+ this(layer, tags, geoPoints, labelPosition);
+ this.geometryType = geometryType;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ } else if (!(obj instanceof Way)) {
+ return false;
+ }
+ Way other = (Way) obj;
+ if (this.layer != other.layer) {
+ return false;
+ } else if (!this.tags.equals(other.tags)) {
+ return false;
+ } else if (this.labelPosition == null && other.labelPosition != null) {
+ return false;
+ } else if (this.labelPosition != null && !this.labelPosition.equals(other.labelPosition)) {
+ return false;
+ } else if (this.geoPoints.length != other.geoPoints.length) {
+ return false;
+ } else {
+ for (int i = 0; i < this.geoPoints.length; i++) {
+ if (this.geoPoints[i].length != other.geoPoints[i].length) {
+ return false;
+ } else {
+ for (int j = 0; j < this.geoPoints[i].length; j++) {
+ if (!geoPoints[i][j].equals(other.geoPoints[i][j])) {
+ return false;
+ }
+ }
+ }
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + layer;
+ result = prime * result + tags.hashCode();
+ result = prime * result + Arrays.deepHashCode(geoPoints);
+ if (labelPosition != null) {
+ result = prime * result + labelPosition.hashCode();
+ }
+ return result;
+ }
+
+}