diff --git a/docs/Changelog.md b/docs/Changelog.md index febdc923..fd2ecd67 100644 --- a/docs/Changelog.md +++ b/docs/Changelog.md @@ -6,7 +6,8 @@ - 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 fix artifacts for zoom > 17 [#231](https://github.com/mapsforge/vtm/issues/231) +- 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) - Many other minor improvements and bug fixes - [Solved issues](https://github.com/mapsforge/vtm/issues?q=is%3Aclosed+milestone%3A0.9.0) diff --git a/vtm-tests/test/org/oscim/core/MercatorProjectionTest.java b/vtm-tests/test/org/oscim/core/MercatorProjectionTest.java new file mode 100644 index 00000000..1e1fe699 --- /dev/null +++ b/vtm-tests/test/org/oscim/core/MercatorProjectionTest.java @@ -0,0 +1,294 @@ +/* + * 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.core; + +import org.junit.Assert; +import org.junit.Test; + +public class MercatorProjectionTest { + private static final int[] TILE_SIZES = {256, 128, 376, 512, 100}; + private static final int ZOOM_LEVEL_MAX = 30; + private static final int ZOOM_LEVEL_MIN = 0; + + private static void verifyInvalidGetMapSize(byte zoomLevel) { + try { + MercatorProjection.getMapSize(zoomLevel); + Assert.fail("zoomLevel: " + zoomLevel); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + private static void verifyInvalidPixelXToLongitude(double pixelX, byte zoomLevel) { + try { + MercatorProjection.pixelXToLongitude(pixelX, MercatorProjection.getMapSize(zoomLevel)); + Assert.fail("pixelX: " + pixelX + ", zoomLevel: " + zoomLevel); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + private static void verifyInvalidPixelYToLatitude(double pixelY, byte zoomLevel) { + try { + MercatorProjection.pixelYToLatitude(pixelY, MercatorProjection.getMapSize(zoomLevel)); + Assert.fail("pixelY: " + pixelY + ", zoomLevel: " + zoomLevel); + } catch (IllegalArgumentException e) { + Assert.assertTrue(true); + } + } + + @Test + public void getMapSizeTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long factor = Math.round(MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(Tile.SIZE * factor, MercatorProjection.getMapSize(zoomLevel)); + Assert.assertEquals(MercatorProjection.getMapSizeWithScale(MercatorProjection.zoomLevelToScale(zoomLevel)), + MercatorProjection.getMapSize(zoomLevel)); + } + verifyInvalidGetMapSize((byte) -1); + } + } + + @Test + public void latitudeToPixelYTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long mapSize = MercatorProjection.getMapSize(zoomLevel); + double pixelY = MercatorProjection.latitudeToPixelY(MercatorProjection.LATITUDE_MAX, mapSize); + Assert.assertEquals(0, pixelY, 0); + + pixelY = MercatorProjection.latitudeToPixelYWithScale(MercatorProjection.LATITUDE_MAX, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(0, pixelY, 0); + + pixelY = MercatorProjection.latitudeToPixelY(0, mapSize); + Assert.assertEquals((float) mapSize / 2, pixelY, 0); + pixelY = MercatorProjection.latitudeToPixelYWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals((float) mapSize / 2, pixelY, 0); + + pixelY = MercatorProjection.latitudeToPixelY(MercatorProjection.LATITUDE_MIN, mapSize); + Assert.assertEquals(mapSize, pixelY, 0); + pixelY = MercatorProjection.latitudeToPixelYWithScale(MercatorProjection.LATITUDE_MIN, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(mapSize, pixelY, 0); + } + } + } + + @Test + public void latitudeToTileYTest() { + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long tileY = MercatorProjection.latitudeToTileY(MercatorProjection.LATITUDE_MAX, zoomLevel); + Assert.assertEquals(0, tileY); + tileY = MercatorProjection.latitudeToTileYWithScale(MercatorProjection.LATITUDE_MAX, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(0, tileY); + + tileY = MercatorProjection.latitudeToTileY(MercatorProjection.LATITUDE_MIN, zoomLevel); + Assert.assertEquals(Tile.getMaxTileNumber(zoomLevel), tileY); + tileY = MercatorProjection.latitudeToTileYWithScale(MercatorProjection.LATITUDE_MIN, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(Tile.getMaxTileNumber(zoomLevel), tileY); + } + } + + @Test + public void longitudeToPixelXTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long mapSize = MercatorProjection.getMapSize(zoomLevel); + double pixelX = MercatorProjection.longitudeToPixelX(MercatorProjection.LONGITUDE_MIN, mapSize); + Assert.assertEquals(0, pixelX, 0); + pixelX = MercatorProjection.longitudeToPixelXWithScale(MercatorProjection.LONGITUDE_MIN, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(0, pixelX, 0); + + pixelX = MercatorProjection.longitudeToPixelX(0, mapSize); + Assert.assertEquals((float) mapSize / 2, pixelX, 0); + mapSize = MercatorProjection.getMapSizeWithScale(MercatorProjection.zoomLevelToScale(zoomLevel)); + pixelX = MercatorProjection.longitudeToPixelXWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals((float) mapSize / 2, pixelX, 0); + + pixelX = MercatorProjection.longitudeToPixelX(MercatorProjection.LONGITUDE_MAX, mapSize); + Assert.assertEquals(mapSize, pixelX, 0); + pixelX = MercatorProjection.longitudeToPixelXWithScale(MercatorProjection.LONGITUDE_MAX, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(mapSize, pixelX, 0); + } + } + } + + @Test + public void longitudeToTileXTest() { + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long tileX = MercatorProjection.longitudeToTileX(MercatorProjection.LONGITUDE_MIN, zoomLevel); + Assert.assertEquals(0, tileX); + tileX = MercatorProjection.longitudeToTileXWithScale(MercatorProjection.LONGITUDE_MIN, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(0, tileX); + + tileX = MercatorProjection.longitudeToTileX(MercatorProjection.LONGITUDE_MAX, zoomLevel); + Assert.assertEquals(Tile.getMaxTileNumber(zoomLevel), tileX); + tileX = MercatorProjection.longitudeToTileXWithScale(MercatorProjection.LONGITUDE_MAX, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(Tile.getMaxTileNumber(zoomLevel), tileX); + } + } + + @Test + public void metersToPixelTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + Assert.assertTrue(MercatorProjection.metersToPixels(10, 10.0, MercatorProjection.getMapSize((byte) 1)) < 1); + Assert.assertTrue(MercatorProjection.metersToPixels((int) (40 * 10e7), 10.0, MercatorProjection.getMapSize((byte) 1)) > 1); + Assert.assertTrue(MercatorProjection.metersToPixels(10, 10.0, MercatorProjection.getMapSize((byte) 20)) > 1); + Assert.assertTrue(MercatorProjection.metersToPixels(10, 89.0, MercatorProjection.getMapSize((byte) 1)) < 1); + Assert.assertTrue(MercatorProjection.metersToPixels((int) (40 * 10e3), 50, MercatorProjection.getMapSize((byte) 10)) > 1); + } + } + + @Test + public void pixelXToLongitudeTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long mapSize = MercatorProjection.getMapSize(zoomLevel); + double longitude = MercatorProjection.pixelXToLongitude(0, mapSize); + Assert.assertEquals(MercatorProjection.LONGITUDE_MIN, longitude, 0); + longitude = MercatorProjection.pixelXToLongitudeWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LONGITUDE_MIN, longitude, 0); + + longitude = MercatorProjection.pixelXToLongitude((float) mapSize / 2, mapSize); + Assert.assertEquals(0, longitude, 0); + mapSize = MercatorProjection.getMapSizeWithScale(MercatorProjection.zoomLevelToScale(zoomLevel)); + longitude = MercatorProjection.pixelXToLongitudeWithScale((float) mapSize / 2, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(0, longitude, 0); + + longitude = MercatorProjection.pixelXToLongitude(mapSize, mapSize); + Assert.assertEquals(MercatorProjection.LONGITUDE_MAX, longitude, 0); + longitude = MercatorProjection.pixelXToLongitudeWithScale(mapSize, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LONGITUDE_MAX, longitude, 0); + } + + verifyInvalidPixelXToLongitude(-1, (byte) 0); + verifyInvalidPixelXToLongitude(Tile.SIZE + 1, (byte) 0); + } + } + + @Test + public void pixelXToTileXTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + Assert.assertEquals(0, MercatorProjection.pixelXToTileX(0, zoomLevel)); + Assert.assertEquals(0, MercatorProjection.pixelXToTileXWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel))); + } + } + } + + @Test + public void pixelYToLatitudeTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + long mapSize = MercatorProjection.getMapSize(zoomLevel); + double latitude = MercatorProjection.pixelYToLatitude(0, mapSize); + Assert.assertEquals(MercatorProjection.LATITUDE_MAX, latitude, 0); + latitude = MercatorProjection.pixelYToLatitudeWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LATITUDE_MAX, latitude, 0); + + latitude = MercatorProjection.pixelYToLatitude((float) mapSize / 2, mapSize); + Assert.assertEquals(0, latitude, 0); + mapSize = MercatorProjection.getMapSizeWithScale(MercatorProjection.zoomLevelToScale(zoomLevel)); + latitude = MercatorProjection.pixelYToLatitudeWithScale((float) mapSize / 2, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(0, latitude, 0); + + latitude = MercatorProjection.pixelYToLatitude(mapSize, mapSize); + Assert.assertEquals(MercatorProjection.LATITUDE_MIN, latitude, 0); + latitude = MercatorProjection.pixelYToLatitudeWithScale(mapSize, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LATITUDE_MIN, latitude, 0); + } + + verifyInvalidPixelYToLatitude(-1, (byte) 0); + verifyInvalidPixelYToLatitude(Tile.SIZE + 1, (byte) 0); + } + } + + @Test + public void pixelYToTileYTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + Assert.assertEquals(0, MercatorProjection.pixelYToTileY(0, zoomLevel)); + Assert.assertEquals(0, MercatorProjection.pixelYToTileYWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel))); + } + } + } + + @Test + public void tileToPixelTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + Assert.assertEquals(0, MercatorProjection.tileToPixel(0)); + Assert.assertEquals(Tile.SIZE, MercatorProjection.tileToPixel(1)); + Assert.assertEquals(Tile.SIZE * 2, MercatorProjection.tileToPixel(2)); + } + } + + @Test + public void tileXToLongitudeTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + double longitude = MercatorProjection.tileXToLongitude(0, zoomLevel); + Assert.assertEquals(MercatorProjection.LONGITUDE_MIN, longitude, 0); + longitude = MercatorProjection.tileXToLongitudeWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LONGITUDE_MIN, longitude, 0); + + long tileX = MercatorProjection.getMapSize(zoomLevel) / Tile.SIZE; + longitude = MercatorProjection.tileXToLongitude(tileX, zoomLevel); + Assert.assertEquals(MercatorProjection.LONGITUDE_MAX, longitude, 0); + tileX = MercatorProjection.getMapSizeWithScale(MercatorProjection.zoomLevelToScale(zoomLevel)) / Tile.SIZE; + longitude = MercatorProjection.tileXToLongitudeWithScale(tileX, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LONGITUDE_MAX, longitude, 0); + } + } + } + + @Test + public void tileYToLatitudeTest() { + for (int tileSize : TILE_SIZES) { + Tile.SIZE = tileSize; + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + double latitude = MercatorProjection.tileYToLatitude(0, zoomLevel); + Assert.assertEquals(MercatorProjection.LATITUDE_MAX, latitude, 0); + latitude = MercatorProjection.tileYToLatitudeWithScale(0, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LATITUDE_MAX, latitude, 0); + + long tileY = MercatorProjection.getMapSize(zoomLevel) / Tile.SIZE; + latitude = MercatorProjection.tileYToLatitude(tileY, zoomLevel); + Assert.assertEquals(MercatorProjection.LATITUDE_MIN, latitude, 0); + tileY = MercatorProjection.getMapSizeWithScale(MercatorProjection.zoomLevelToScale(zoomLevel)) / Tile.SIZE; + latitude = MercatorProjection.tileYToLatitudeWithScale(tileY, MercatorProjection.zoomLevelToScale(zoomLevel)); + Assert.assertEquals(MercatorProjection.LATITUDE_MIN, latitude, 0); + } + } + } + + @Test + public void zoomLevelToScaleTest() { + for (byte zoomLevel = ZOOM_LEVEL_MIN; zoomLevel <= ZOOM_LEVEL_MAX; ++zoomLevel) { + double scale = MercatorProjection.zoomLevelToScale(zoomLevel); + Assert.assertEquals(zoomLevel, MercatorProjection.scaleToZoomLevel(scale), 0.0001f); + } + } +} diff --git a/vtm/src/org/oscim/core/BoundingBox.java b/vtm/src/org/oscim/core/BoundingBox.java index fd183daa..4de0b816 100644 --- a/vtm/src/org/oscim/core/BoundingBox.java +++ b/vtm/src/org/oscim/core/BoundingBox.java @@ -1,7 +1,7 @@ /* * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2014 Hannes Janetzek - * Copyright 2016 devemux86 + * Copyright 2016-2017 devemux86 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -321,6 +321,46 @@ public class BoundingBox { && getMinLatitude() <= boundingBox.getMaxLatitude() && getMinLongitude() <= boundingBox.getMaxLongitude(); } + /** + * Returns if an area built from the geoPoints intersects with a bias towards + * returning true. + * The method returns fast if any of the points lie within the bbox. If none of the points + * lie inside the box, it constructs the outer bbox for all the points and tests for intersection + * (so it is possible that the area defined by the points does not actually intersect) + * + * @param geoPoints the points that define an area + * @return false if there is no intersection, true if there could be an intersection + */ + public boolean intersectsArea(GeoPoint[][] geoPoints) { + if (geoPoints.length == 0 || geoPoints[0].length == 0) { + return false; + } + for (GeoPoint[] outer : geoPoints) { + for (GeoPoint geoPoint : outer) { + if (this.contains(geoPoint)) { + // if any of the points is inside the bbox return early + return true; + } + } + } + + // no fast solution, so accumulate boundary points + double tmpMinLat = geoPoints[0][0].getLatitude(); + double tmpMinLon = geoPoints[0][0].getLongitude(); + double tmpMaxLat = geoPoints[0][0].getLatitude(); + double tmpMaxLon = geoPoints[0][0].getLongitude(); + + for (GeoPoint[] outer : geoPoints) { + for (GeoPoint geoPoint : outer) { + tmpMinLat = Math.min(tmpMinLat, geoPoint.getLatitude()); + tmpMaxLat = Math.max(tmpMaxLat, geoPoint.getLatitude()); + tmpMinLon = Math.min(tmpMinLon, geoPoint.getLongitude()); + tmpMaxLon = Math.max(tmpMaxLon, geoPoint.getLongitude()); + } + } + return this.intersects(new BoundingBox(tmpMinLat, tmpMinLon, tmpMaxLat, tmpMaxLon)); + } + @Override public String toString() { return new StringBuilder() diff --git a/vtm/src/org/oscim/core/MercatorProjection.java b/vtm/src/org/oscim/core/MercatorProjection.java index ba497847..e4c81020 100644 --- a/vtm/src/org/oscim/core/MercatorProjection.java +++ b/vtm/src/org/oscim/core/MercatorProjection.java @@ -1,7 +1,8 @@ /* * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2012 Hannes Janetzek - * Copyright 2016 devemux86 + * Copyright 2014 Ludwig M Brinckmann + * Copyright 2016-2017 devemux86 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -22,6 +23,13 @@ import org.oscim.utils.FastMath; /** * An implementation of the spherical Mercator projection. + *

+ * There are generally two methods for each operation: one taking a byte zoom level and + * a parallel one taking a double scale. The scale is Math.pow(2, zoomLevel) + * for a given zoom level, but the operations take intermediate values as well. + * The zoom level operation is a little faster as it can make use of shift operations, + * the scale operation offers greater flexibility in computing the values for + * intermediate zoom levels. */ public final class MercatorProjection { /** @@ -49,6 +57,106 @@ public final class MercatorProjection { */ public static final double LONGITUDE_MIN = -LONGITUDE_MAX; + /** + * Get GeoPoint from Pixels. + */ + public static GeoPoint fromPixelsWithScale(double pixelX, double pixelY, double scale) { + return new GeoPoint(pixelYToLatitudeWithScale(pixelY, scale), + pixelXToLongitudeWithScale(pixelX, scale)); + } + + /** + * Get GeoPoint from Pixels. + */ + public static GeoPoint fromPixels(double pixelX, double pixelY, long mapSize) { + return new GeoPoint(pixelYToLatitude(pixelY, mapSize), + pixelXToLongitude(pixelX, mapSize)); + } + + /** + * @param scale the scale factor for which the size of the world map should be returned. + * @return the horizontal and vertical size of the map in pixel at the given scale. + * @throws IllegalArgumentException if the given scale factor is < 1 + */ + public static long getMapSizeWithScale(double scale) { + if (scale < 1) { + throw new IllegalArgumentException("scale factor must not < 1 " + scale); + } + return (long) (Tile.SIZE * (Math.pow(2, scaleToZoomLevel(scale)))); + } + + /** + * @param zoomLevel the zoom level for which the size of the world map should be returned. + * @return the horizontal and vertical size of the map in pixel at the given zoom level. + * @throws IllegalArgumentException if the given zoom level is negative. + */ + public static long getMapSize(byte zoomLevel) { + if (zoomLevel < 0) { + throw new IllegalArgumentException("zoom level must not be negative: " + zoomLevel); + } + return (long) Tile.SIZE << zoomLevel; + } + + public static Point getPixelWithScale(GeoPoint geoPoint, double scale) { + double pixelX = MercatorProjection.longitudeToPixelXWithScale(geoPoint.getLongitude(), scale); + double pixelY = MercatorProjection.latitudeToPixelYWithScale(geoPoint.getLatitude(), scale); + return new Point(pixelX, pixelY); + } + + public static Point getPixel(GeoPoint geoPoint, long mapSize) { + double pixelX = MercatorProjection.longitudeToPixelX(geoPoint.getLongitude(), mapSize); + double pixelY = MercatorProjection.latitudeToPixelY(geoPoint.getLatitude(), mapSize); + return new Point(pixelX, pixelY); + } + + /** + * Calculates the absolute pixel position for a map size and tile size + * + * @param geoPoint the geographic position. + * @param mapSize precomputed size of map. + * @return the absolute pixel coordinates (for world) + */ + + public static Point getPixelAbsolute(GeoPoint geoPoint, long mapSize) { + return getPixelRelative(geoPoint, mapSize, 0, 0); + } + + /** + * Calculates the absolute pixel position for a map size and tile size relative to origin + * + * @param geoPoint the geographic position. + * @param mapSize precomputed size of map. + * @return the relative pixel position to the origin values (e.g. for a tile) + */ + public static Point getPixelRelative(GeoPoint geoPoint, long mapSize, double x, double y) { + double pixelX = MercatorProjection.longitudeToPixelX(geoPoint.getLongitude(), mapSize) - x; + double pixelY = MercatorProjection.latitudeToPixelY(geoPoint.getLatitude(), mapSize) - y; + return new Point(pixelX, pixelY); + } + + + /** + * Calculates the absolute pixel position for a map size and tile size relative to origin + * + * @param geoPoint the geographic position. + * @param mapSize precomputed size of map. + * @return the relative pixel position to the origin values (e.g. for a tile) + */ + public static Point getPixelRelative(GeoPoint geoPoint, long mapSize, Point origin) { + return getPixelRelative(geoPoint, mapSize, origin.x, origin.y); + } + + /** + * Calculates the absolute pixel position for a tile and tile size relative to origin + * + * @param geoPoint the geographic position. + * @param tile tile + * @return the relative pixel position to the origin values (e.g. for a tile) + */ + public static Point getPixelRelativeToTile(GeoPoint geoPoint, Tile tile) { + return getPixelRelative(geoPoint, tile.mapSize, tile.getOrigin()); + } + /** * Calculates the distance on the ground that is represented by a single * pixel on the map. @@ -56,9 +164,9 @@ public final class MercatorProjection { * @param latitude the latitude coordinate at which the resolution should be * calculated. * @param scale the map scale at which the resolution should be calculated. - * @return the ground resolution at the given latitude and zoom level. + * @return the ground resolution at the given latitude and scale. */ - public static double groundResolution(double latitude, double scale) { + public static double groundResolutionWithScale(double latitude, double scale) { return Math.cos(latitude * (Math.PI / 180)) * EARTH_CIRCUMFERENCE / (Tile.SIZE * scale); } @@ -70,6 +178,83 @@ public final class MercatorProjection { / (Tile.SIZE * pos.scale)); } + /** + * Calculates the distance on the ground that is represented by a single pixel on the map. + * + * @param latitude the latitude coordinate at which the resolution should be calculated. + * @param mapSize precomputed size of map. + * @return the ground resolution at the given latitude and map size. + */ + public static double groundResolution(double latitude, long mapSize) { + return Math.cos(latitude * (Math.PI / 180)) * EARTH_CIRCUMFERENCE / mapSize; + } + + /** + * Converts a latitude coordinate (in degrees) to a pixel Y coordinate at a certain scale. + * + * @param latitude the latitude coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the pixel Y coordinate of the latitude value. + */ + public static double latitudeToPixelYWithScale(double latitude, double scale) { + double sinLatitude = Math.sin(latitude * (Math.PI / 180)); + long mapSize = getMapSizeWithScale(scale); + // FIXME improve this formula so that it works correctly without the clipping + double pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * mapSize; + return Math.min(Math.max(0, pixelY), mapSize); + } + + /** + * Converts a latitude coordinate (in degrees) to a pixel Y coordinate at a certain zoom level. + * + * @param latitude the latitude coordinate that should be converted. + * @param zoomLevel the zoom level at which the coordinate should be converted. + * @return the pixel Y coordinate of the latitude value. + */ + public static double latitudeToPixelY(double latitude, byte zoomLevel) { + double sinLatitude = Math.sin(latitude * (Math.PI / 180)); + long mapSize = getMapSize(zoomLevel); + // FIXME improve this formula so that it works correctly without the clipping + double pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * mapSize; + return Math.min(Math.max(0, pixelY), mapSize); + } + + /** + * Converts a latitude coordinate (in degrees) to a pixel Y coordinate at a certain map size. + * + * @param latitude the latitude coordinate that should be converted. + * @param mapSize precomputed size of map. + * @return the pixel Y coordinate of the latitude value. + */ + public static double latitudeToPixelY(double latitude, long mapSize) { + double sinLatitude = Math.sin(latitude * (Math.PI / 180)); + // FIXME improve this formula so that it works correctly without the clipping + double pixelY = (0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI)) * mapSize; + return Math.min(Math.max(0, pixelY), mapSize); + } + + /** + * Converts a latitude coordinate (in degrees) to a tile Y number at a certain scale. + * + * @param latitude the latitude coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the tile Y number of the latitude value. + */ + public static int latitudeToTileYWithScale(double latitude, double scale) { + return pixelYToTileYWithScale(latitudeToPixelYWithScale(latitude, scale), scale); + } + + /** + * Converts a latitude coordinate (in degrees) to a tile Y number at a certain zoom level. + * + * @param latitude the latitude coordinate that should be converted. + * @param zoomLevel the zoom level at which the coordinate should be converted. + * @return the tile Y number of the latitude value. + */ + public static int latitudeToTileY(double latitude, byte zoomLevel) { + return pixelYToTileY(latitudeToPixelY(latitude, zoomLevel), zoomLevel); + } + /** * Projects a latitude coordinate (in degrees) to the range [0.0,1.0] * @@ -81,8 +266,78 @@ public final class MercatorProjection { return FastMath.clamp(0.5 - Math.log((1 + sinLatitude) / (1 - sinLatitude)) / (4 * Math.PI), 0.0, 1.0); } - public static double toLatitude(double y) { - return 90 - 360 * Math.atan(Math.exp((y - 0.5) * (2 * Math.PI))) / Math.PI; + /** + * @param latitude the latitude value which should be checked. + * @return the given latitude value, limited to the possible latitude range. + */ + public static double limitLatitude(double latitude) { + return Math.max(Math.min(latitude, LATITUDE_MAX), LATITUDE_MIN); + } + + /** + * @param longitude the longitude value which should be checked. + * @return the given longitude value, limited to the possible longitude + * range. + */ + public static double limitLongitude(double longitude) { + return Math.max(Math.min(longitude, LONGITUDE_MAX), LONGITUDE_MIN); + } + + /** + * Converts a longitude coordinate (in degrees) to a pixel X coordinate at a certain scale factor. + * + * @param longitude the longitude coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the pixel X coordinate of the longitude value. + */ + public static double longitudeToPixelXWithScale(double longitude, double scale) { + long mapSize = getMapSizeWithScale(scale); + return (longitude + 180) / 360 * mapSize; + } + + /** + * Converts a longitude coordinate (in degrees) to a pixel X coordinate at a certain zoom level. + * + * @param longitude the longitude coordinate that should be converted. + * @param zoomLevel the zoom level at which the coordinate should be converted. + * @return the pixel X coordinate of the longitude value. + */ + public static double longitudeToPixelX(double longitude, byte zoomLevel) { + long mapSize = getMapSize(zoomLevel); + return (longitude + 180) / 360 * mapSize; + } + + /** + * Converts a longitude coordinate (in degrees) to a pixel X coordinate at a certain map size. + * + * @param longitude the longitude coordinate that should be converted. + * @param mapSize precomputed size of map. + * @return the pixel X coordinate of the longitude value. + */ + public static double longitudeToPixelX(double longitude, long mapSize) { + return (longitude + 180) / 360 * mapSize; + } + + /** + * Converts a longitude coordinate (in degrees) to the tile X number at a certain scale factor. + * + * @param longitude the longitude coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the tile X number of the longitude value. + */ + public static int longitudeToTileXWithScale(double longitude, double scale) { + return pixelXToTileXWithScale(longitudeToPixelXWithScale(longitude, scale), scale); + } + + /** + * Converts a longitude coordinate (in degrees) to the tile X number at a certain zoom level. + * + * @param longitude the longitude coordinate that should be converted. + * @param zoomLevel the zoom level at which the coordinate should be converted. + * @return the tile X number of the longitude value. + */ + public static int longitudeToTileX(double longitude, byte zoomLevel) { + return pixelXToTileX(longitudeToPixelX(longitude, zoomLevel), zoomLevel); } /** @@ -95,8 +350,137 @@ public final class MercatorProjection { return (longitude + 180.0) / 360.0; } - public static double toLongitude(double x) { - return 360.0 * (x - 0.5); + /** + * Converts meters to pixels at latitude for zoom-level. + * + * @param meters the meters to convert + * @param latitude the latitude for the conversion. + * @param scale the scale factor for the conversion. + * @return pixels that represent the meters at the given zoom-level and latitude. + */ + public static double metersToPixelsWithScale(float meters, double latitude, double scale) { + return meters / MercatorProjection.groundResolutionWithScale(latitude, scale); + } + + /** + * Converts meters to pixels at latitude for zoom-level. + * + * @param meters the meters to convert + * @param latitude the latitude for the conversion. + * @param mapSize precomputed size of map. + * @return pixels that represent the meters at the given zoom-level and latitude. + */ + public static double metersToPixels(float meters, double latitude, long mapSize) { + return meters / MercatorProjection.groundResolution(latitude, mapSize); + } + + /** + * Converts a pixel X coordinate at a certain scale to a longitude coordinate. + * + * @param pixelX the pixel X coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the longitude value of the pixel X coordinate. + * @throws IllegalArgumentException if the given pixelX coordinate is invalid. + */ + public static double pixelXToLongitudeWithScale(double pixelX, double scale) { + long mapSize = getMapSizeWithScale(scale); + if (pixelX < 0 || pixelX > mapSize) { + throw new IllegalArgumentException("invalid pixelX coordinate at scale " + scale + ": " + pixelX); + } + return 360 * ((pixelX / mapSize) - 0.5); + } + + /** + * Converts a pixel X coordinate at a certain map size to a longitude coordinate. + * + * @param pixelX the pixel X coordinate that should be converted. + * @param mapSize precomputed size of map. + * @return the longitude value of the pixel X coordinate. + * @throws IllegalArgumentException if the given pixelX coordinate is invalid. + */ + + public static double pixelXToLongitude(double pixelX, long mapSize) { + if (pixelX < 0 || pixelX > mapSize) { + throw new IllegalArgumentException("invalid pixelX coordinate " + mapSize + ": " + pixelX); + } + return 360 * ((pixelX / mapSize) - 0.5); + } + + /** + * Converts a pixel X coordinate to the tile X number. + * + * @param pixelX the pixel X coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the tile X number. + */ + public static int pixelXToTileXWithScale(double pixelX, double scale) { + return (int) Math.min(Math.max(pixelX / Tile.SIZE, 0), scale - 1); + } + + /** + * Converts a pixel X coordinate to the tile X number. + * + * @param pixelX the pixel X coordinate that should be converted. + * @param zoomLevel the zoom level at which the coordinate should be converted. + * @return the tile X number. + */ + public static int pixelXToTileX(double pixelX, byte zoomLevel) { + return (int) Math.min(Math.max(pixelX / Tile.SIZE, 0), Math.pow(2, zoomLevel) - 1); + } + + /** + * Converts a pixel Y coordinate at a certain scale to a latitude coordinate. + * + * @param pixelY the pixel Y coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the latitude value of the pixel Y coordinate. + * @throws IllegalArgumentException if the given pixelY coordinate is invalid. + */ + public static double pixelYToLatitudeWithScale(double pixelY, double scale) { + long mapSize = getMapSizeWithScale(scale); + if (pixelY < 0 || pixelY > mapSize) { + throw new IllegalArgumentException("invalid pixelY coordinate at scale " + scale + ": " + pixelY); + } + double y = 0.5 - (pixelY / mapSize); + return 90 - 360 * Math.atan(Math.exp(-y * (2 * Math.PI))) / Math.PI; + } + + /** + * Converts a pixel Y coordinate at a certain map size to a latitude coordinate. + * + * @param pixelY the pixel Y coordinate that should be converted. + * @param mapSize precomputed size of map. + * @return the latitude value of the pixel Y coordinate. + * @throws IllegalArgumentException if the given pixelY coordinate is invalid. + */ + public static double pixelYToLatitude(double pixelY, long mapSize) { + if (pixelY < 0 || pixelY > mapSize) { + throw new IllegalArgumentException("invalid pixelY coordinate " + mapSize + ": " + pixelY); + } + double y = 0.5 - (pixelY / mapSize); + return 90 - 360 * Math.atan(Math.exp(-y * (2 * Math.PI))) / Math.PI; + } + + /** + * Converts a pixel Y coordinate to the tile Y number. + * + * @param pixelY the pixel Y coordinate that should be converted. + * @param scale the scale factor at which the coordinate should be converted. + * @return the tile Y number. + */ + public static int pixelYToTileYWithScale(double pixelY, double scale) { + return (int) Math.min(Math.max(pixelY / Tile.SIZE, 0), scale - 1); + } + + /** + * Converts a pixel Y coordinate to the tile Y number. + * + * @param pixelY the pixel Y coordinate that should be converted. + * @param zoomLevel the zoom level at which the coordinate should be converted. + * @return the tile Y number. + */ + public static int pixelYToTileY(double pixelY, byte zoomLevel) { + return (int) Math.min(Math.max(pixelY / Tile.SIZE, 0), Math.pow(2, zoomLevel) - 1); } public static Point project(GeoPoint p, Point reuse) { @@ -130,20 +514,75 @@ public final class MercatorProjection { } /** - * @param latitude the latitude value which should be checked. - * @return the given latitude value, limited to the possible latitude range. + * Converts a scale factor to a zoomLevel. + * Note that this will return a double, as the scale factors cover the + * intermediate zoom levels as well. + * + * @param scale the scale factor to convert to a zoom level. + * @return the zoom level. */ - public static double limitLatitude(double latitude) { - return Math.max(Math.min(latitude, LATITUDE_MAX), LATITUDE_MIN); + public static double scaleToZoomLevel(double scale) { + return FastMath.log2((int) scale); } /** - * @param longitude the longitude value which should be checked. - * @return the given longitude value, limited to the possible longitude - * range. + * @param tileNumber the tile number that should be converted. + * @return the pixel coordinate for the given tile number. */ - public static double limitLongitude(double longitude) { - return Math.max(Math.min(longitude, LONGITUDE_MAX), LONGITUDE_MIN); + public static long tileToPixel(long tileNumber) { + return tileNumber * Tile.SIZE; + } + + /** + * Converts a tile X number at a certain scale to a longitude coordinate. + * + * @param tileX the tile X number that should be converted. + * @param scale the scale factor at which the number should be converted. + * @return the longitude value of the tile X number. + */ + public static double tileXToLongitudeWithScale(long tileX, double scale) { + return pixelXToLongitudeWithScale(tileX * Tile.SIZE, scale); + } + + /** + * Converts a tile X number at a certain zoom level to a longitude coordinate. + * + * @param tileX the tile X number that should be converted. + * @param zoomLevel the zoom level at which the number should be converted. + * @return the longitude value of the tile X number. + */ + public static double tileXToLongitude(long tileX, byte zoomLevel) { + return pixelXToLongitude(tileX * Tile.SIZE, getMapSize(zoomLevel)); + } + + /** + * Converts a tile Y number at a certain scale to a latitude coordinate. + * + * @param tileY the tile Y number that should be converted. + * @param scale the scale factor at which the number should be converted. + * @return the latitude value of the tile Y number. + */ + public static double tileYToLatitudeWithScale(long tileY, double scale) { + return pixelYToLatitudeWithScale(tileY * Tile.SIZE, scale); + } + + /** + * Converts a tile Y number at a certain zoom level to a latitude coordinate. + * + * @param tileY the tile Y number that should be converted. + * @param zoomLevel the zoom level at which the number should be converted. + * @return the latitude value of the tile Y number. + */ + public static double tileYToLatitude(long tileY, byte zoomLevel) { + return pixelYToLatitude(tileY * Tile.SIZE, getMapSize(zoomLevel)); + } + + public static double toLatitude(double y) { + return 90 - 360 * Math.atan(Math.exp((y - 0.5) * (2 * Math.PI))) / Math.PI; + } + + public static double toLongitude(double x) { + return 360.0 * (x - 0.5); } public static double wrapLongitude(double longitude) { @@ -155,6 +594,16 @@ public final class MercatorProjection { return longitude; } + /** + * Converts a zoom level to a scale factor. + * + * @param zoomLevel the zoom level to convert. + * @return the corresponding scale factor. + */ + public static double zoomLevelToScale(byte zoomLevel) { + return 1 << zoomLevel; + } + private MercatorProjection() { } } diff --git a/vtm/src/org/oscim/core/Tile.java b/vtm/src/org/oscim/core/Tile.java index b1097490..51ae0ece 100644 --- a/vtm/src/org/oscim/core/Tile.java +++ b/vtm/src/org/oscim/core/Tile.java @@ -1,7 +1,7 @@ /* * Copyright 2010, 2011, 2012 mapsforge.org * Copyright 2013 Hannes Janetzek - * Copyright 2016 devemux86 + * Copyright 2016-2017 devemux86 * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * @@ -40,6 +40,11 @@ public class Tile { */ public static int TILE_SIZE_MULTIPLE = 64; + /** + * the map size implied by zoom level and tileSize, to avoid multiple computations. + */ + public final long mapSize; + /** * The X number of this tile. */ @@ -55,6 +60,9 @@ public class Tile { */ public final byte zoomLevel; + private BoundingBox boundingBox; + private Point origin; + /** * @param tileX the X number of the tile. * @param tileY the Y number of the tile. @@ -64,6 +72,7 @@ public class Tile { this.tileX = tileX; this.tileY = tileY; this.zoomLevel = zoomLevel; + this.mapSize = MercatorProjection.getMapSize(zoomLevel); } @Override @@ -119,4 +128,63 @@ public class Tile { return Math.max(TILE_SIZE_MULTIPLE, Math.round(scaled / TILE_SIZE_MULTIPLE) * TILE_SIZE_MULTIPLE); } + + /** + * Gets the geographic extend of this Tile as a BoundingBox. + * + * @return boundaries of this tile. + */ + public BoundingBox getBoundingBox() { + if (this.boundingBox == null) { + double minLatitude = Math.max(MercatorProjection.LATITUDE_MIN, MercatorProjection.tileYToLatitude(tileY + 1, zoomLevel)); + double minLongitude = Math.max(-180, MercatorProjection.tileXToLongitude(this.tileX, zoomLevel)); + double maxLatitude = Math.min(MercatorProjection.LATITUDE_MAX, MercatorProjection.tileYToLatitude(this.tileY, zoomLevel)); + double maxLongitude = Math.min(180, MercatorProjection.tileXToLongitude(tileX + 1, zoomLevel)); + if (maxLongitude == -180) { + // fix for dateline crossing, where the right tile starts at -180 and causes an invalid bbox + maxLongitude = 180; + } + this.boundingBox = new BoundingBox(minLatitude, minLongitude, maxLatitude, maxLongitude); + } + return this.boundingBox; + } + + /** + * Return the BoundingBox of a rectangle of tiles defined by upper left and lower right tile. + * + * @param upperLeft tile in upper left corner. + * @param lowerRight tile in lower right corner. + * @return BoundingBox defined by the area around upperLeft and lowerRight Tile. + */ + public static BoundingBox getBoundingBox(Tile upperLeft, Tile lowerRight) { + BoundingBox ul = upperLeft.getBoundingBox(); + BoundingBox lr = lowerRight.getBoundingBox(); + return ul.extendBoundingBox(lr); + } + + /** + * @return the maximum valid tile number for the given zoom level, 2zoomLevel -1. + */ + public static int getMaxTileNumber(byte zoomLevel) { + if (zoomLevel < 0) { + throw new IllegalArgumentException("zoomLevel must not be negative: " + zoomLevel); + } else if (zoomLevel == 0) { + return 0; + } + return (2 << zoomLevel - 1) - 1; + } + + /** + * Returns the top-left point of this tile in absolute coordinates. + * + * @return the top-left point + */ + public Point getOrigin() { + if (this.origin == null) { + double x = MercatorProjection.tileToPixel(this.tileX); + double y = MercatorProjection.tileToPixel(this.tileY); + this.origin = new Point(x, y); + } + return this.origin; + } } diff --git a/vtm/src/org/oscim/layers/LocationLayer.java b/vtm/src/org/oscim/layers/LocationLayer.java index 35bf6fa4..05f9ed46 100644 --- a/vtm/src/org/oscim/layers/LocationLayer.java +++ b/vtm/src/org/oscim/layers/LocationLayer.java @@ -48,7 +48,7 @@ public class LocationLayer extends Layer { public void setPosition(double latitude, double longitude, double accuracy) { double x = MercatorProjection.longitudeToX(longitude); double y = MercatorProjection.latitudeToY(latitude); - double radius = accuracy / MercatorProjection.groundResolution(latitude, 1); + double radius = accuracy / MercatorProjection.groundResolutionWithScale(latitude, 1); locationRenderer.setLocation(x, y, radius); locationRenderer.animate(true); } diff --git a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java index d660f84f..49eed862 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java +++ b/vtm/src/org/oscim/layers/tile/buildings/BuildingLayer.java @@ -1,6 +1,6 @@ /* * Copyright 2013 Hannes Janetzek - * Copyright 2016 devemux86 + * Copyright 2016-2017 devemux86 * Copyright 2016 Robin Boldt * * This file is part of the OpenScienceMap project (http://www.opensciencemap.org). @@ -101,7 +101,7 @@ public class BuildingLayer extends Layer implements TileLoaderThemeHook { double lat = MercatorProjection.toLatitude(tile.y); float groundScale = (float) MercatorProjection - .groundResolution(lat, 1 << tile.zoomLevel); + .groundResolutionWithScale(lat, 1 << tile.zoomLevel); ebs.buckets = Inlist.push(ebs.buckets, new ExtrusionBucket(0, groundScale, diff --git a/vtm/src/org/oscim/layers/tile/buildings/S3DBTileLoader.java b/vtm/src/org/oscim/layers/tile/buildings/S3DBTileLoader.java index 3656479b..7b4f1621 100644 --- a/vtm/src/org/oscim/layers/tile/buildings/S3DBTileLoader.java +++ b/vtm/src/org/oscim/layers/tile/buildings/S3DBTileLoader.java @@ -80,7 +80,7 @@ class S3DBTileLoader extends TileLoader { private void initTile(MapTile tile) { double lat = MercatorProjection.toLatitude(tile.y); mGroundScale = (float) MercatorProjection - .groundResolution(lat, 1 << mTile.zoomLevel); + .groundResolutionWithScale(lat, 1 << mTile.zoomLevel); mRoofs = new ExtrusionBucket(0, mGroundScale, Color.get(247, 249, 250)); diff --git a/vtm/src/org/oscim/utils/GeoPointUtils.java b/vtm/src/org/oscim/utils/GeoPointUtils.java new file mode 100644 index 00000000..853288e1 --- /dev/null +++ b/vtm/src/org/oscim/utils/GeoPointUtils.java @@ -0,0 +1,51 @@ +/* + * 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.utils; + +import org.oscim.core.GeoPoint; + +public final class GeoPointUtils { + + /** + * Find if the given point lies within this polygon.
+ * See http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html + * + * @return true if this polygon contains the given point, false otherwise. + */ + public static boolean contains(GeoPoint[] geoPoints, GeoPoint geoPoint) { + boolean result = false; + for (int i = 0, j = geoPoints.length - 1; i < geoPoints.length; j = i++) { + if ((geoPoints[i].getLatitude() > geoPoint.getLatitude()) != (geoPoints[j].getLatitude() > geoPoint.getLatitude()) + && (geoPoint.getLongitude() < (geoPoints[j].getLongitude() - geoPoints[i].getLongitude()) * (geoPoint.getLatitude() - geoPoints[i].getLatitude()) + / (geoPoints[j].getLatitude() - geoPoints[i].getLatitude()) + geoPoints[i].getLongitude())) { + result = !result; + } + } + return result; + } + + /** + * Find if this way is closed. + * + * @return true if this way is closed, false otherwise. + */ + public static boolean isClosedWay(GeoPoint[] geoPoints) { + return geoPoints[0].distance(geoPoints[geoPoints.length - 1]) < 0.000000001; + } + + private GeoPointUtils() { + throw new IllegalStateException(); + } +}