From ac7706eb7aa0abc5cd9d8edc615832a628b84719 Mon Sep 17 00:00:00 2001 From: Emux Date: Sat, 8 Oct 2016 15:48:00 +0300 Subject: [PATCH] BoundingBox improvements, closes #200 --- docs/Applications.md | 1 - vtm/src/org/oscim/core/BoundingBox.java | 198 ++++++++++++++++++++---- 2 files changed, 167 insertions(+), 32 deletions(-) diff --git a/docs/Applications.md b/docs/Applications.md index d65b9810..c11cdedd 100644 --- a/docs/Applications.md +++ b/docs/Applications.md @@ -5,5 +5,4 @@ | [Cachebox 3.0](https://github.com/Longri/cachebox3.0) | Android / Desktop / iOS geocaching application | GPL/Free | Open | | [Cruiser](http://wiki.openstreetmap.org/wiki/Cruiser) | Android / Desktop map and navigation application | Proprietary/Free | Closed | - You know an application that is missing here? Please inform us by sending a message via our public [mailing list](https://groups.google.com/group/mapsforge-dev). diff --git a/vtm/src/org/oscim/core/BoundingBox.java b/vtm/src/org/oscim/core/BoundingBox.java index 2bc46347..38f10193 100644 --- a/vtm/src/org/oscim/core/BoundingBox.java +++ b/vtm/src/org/oscim/core/BoundingBox.java @@ -67,14 +67,40 @@ public class BoundingBox { this.maxLongitudeE6 = maxLongitudeE6; } - public BoundingBox(double minLatitude, double minLongitude, double maxLatitude, - double maxLongitude) { + /** + * @param minLatitude the minimum latitude coordinate in degrees. + * @param minLongitude the minimum longitude coordinate in degrees. + * @param maxLatitude the maximum latitude coordinate in degrees. + * @param maxLongitude the maximum longitude coordinate in degrees. + */ + public BoundingBox(double minLatitude, double minLongitude, double maxLatitude, double maxLongitude) { this.minLatitudeE6 = (int) (minLatitude * 1E6); this.minLongitudeE6 = (int) (minLongitude * 1E6); this.maxLatitudeE6 = (int) (maxLatitude * 1E6); this.maxLongitudeE6 = (int) (maxLongitude * 1E6); } + /** + * @param geoPoints the coordinates list. + */ + public BoundingBox(List geoPoints) { + int minLat = Integer.MAX_VALUE; + int minLon = Integer.MAX_VALUE; + int maxLat = Integer.MIN_VALUE; + int maxLon = Integer.MIN_VALUE; + for (GeoPoint geoPoint : geoPoints) { + minLat = Math.min(minLat, geoPoint.latitudeE6); + minLon = Math.min(minLon, geoPoint.longitudeE6); + maxLat = Math.max(maxLat, geoPoint.latitudeE6); + maxLon = Math.max(maxLon, geoPoint.longitudeE6); + } + + this.minLatitudeE6 = minLat; + this.minLongitudeE6 = minLon; + this.maxLatitudeE6 = maxLat; + this.maxLongitudeE6 = maxLon; + } + /** * @param geoPoint the point whose coordinates should be checked. * @return true if this BoundingBox contains the given GeoPoint, false @@ -107,6 +133,118 @@ public class BoundingBox { return true; } + /** + * @param boundingBox the BoundingBox which this BoundingBox should be extended if it is larger + * @return a BoundingBox that covers this BoundingBox and the given BoundingBox. + */ + public BoundingBox extendBoundingBox(BoundingBox boundingBox) { + return new BoundingBox(Math.min(this.minLatitudeE6, boundingBox.minLatitudeE6), + Math.min(this.minLongitudeE6, boundingBox.minLongitudeE6), + Math.max(this.maxLatitudeE6, boundingBox.maxLatitudeE6), + Math.max(this.maxLongitudeE6, boundingBox.maxLongitudeE6)); + } + + /** + * Creates a BoundingBox extended up to GeoPoint (but does not cross date line/poles). + * + * @param geoPoint coordinates up to the extension + * @return an extended BoundingBox or this (if contains coordinates) + */ + public BoundingBox extendCoordinates(GeoPoint geoPoint) { + if (contains(geoPoint)) { + return this; + } + + double minLat = Math.max(MercatorProjection.LATITUDE_MIN, Math.min(getMinLatitude(), geoPoint.getLatitude())); + double minLon = Math.max(MercatorProjection.LONGITUDE_MIN, Math.min(getMinLongitude(), geoPoint.getLongitude())); + double maxLat = Math.min(MercatorProjection.LATITUDE_MAX, Math.max(getMaxLatitude(), geoPoint.getLatitude())); + double maxLon = Math.min(MercatorProjection.LONGITUDE_MAX, Math.max(getMaxLongitude(), geoPoint.getLongitude())); + + return new BoundingBox(minLat, minLon, maxLat, maxLon); + } + + /** + * Creates a BoundingBox that is a fixed degree amount larger on all sides (but does not cross date line/poles). + * + * @param verticalExpansion degree extension (must be >= 0) + * @param horizontalExpansion degree extension (must be >= 0) + * @return an extended BoundingBox or this (if degrees == 0) + */ + public BoundingBox extendDegrees(double verticalExpansion, double horizontalExpansion) { + if (verticalExpansion == 0 && horizontalExpansion == 0) { + return this; + } else if (verticalExpansion < 0 || horizontalExpansion < 0) { + throw new IllegalArgumentException("BoundingBox extend operation does not accept negative values"); + } + + double minLat = Math.max(MercatorProjection.LATITUDE_MIN, getMinLatitude() - verticalExpansion); + double minLon = Math.max(MercatorProjection.LONGITUDE_MIN, getMinLongitude() - horizontalExpansion); + double maxLat = Math.min(MercatorProjection.LATITUDE_MAX, getMaxLatitude() + verticalExpansion); + double maxLon = Math.min(MercatorProjection.LONGITUDE_MAX, getMaxLongitude() + horizontalExpansion); + + return new BoundingBox(minLat, minLon, maxLat, maxLon); + } + + /** + * Creates a BoundingBox that is a fixed margin factor larger on all sides (but does not cross date line/poles). + * + * @param margin extension (must be > 0) + * @return an extended BoundingBox or this (if margin == 1) + */ + public BoundingBox extendMargin(float margin) { + if (margin == 1) { + return this; + } else if (margin <= 0) { + throw new IllegalArgumentException("BoundingBox extend operation does not accept negative or zero values"); + } + + double verticalExpansion = (getLatitudeSpan() * margin - getLatitudeSpan()) * 0.5; + double horizontalExpansion = (getLongitudeSpan() * margin - getLongitudeSpan()) * 0.5; + + double minLat = Math.max(MercatorProjection.LATITUDE_MIN, getMinLatitude() - verticalExpansion); + double minLon = Math.max(MercatorProjection.LONGITUDE_MIN, getMinLongitude() - horizontalExpansion); + double maxLat = Math.min(MercatorProjection.LATITUDE_MAX, getMaxLatitude() + verticalExpansion); + double maxLon = Math.min(MercatorProjection.LONGITUDE_MAX, getMaxLongitude() + horizontalExpansion); + + return new BoundingBox(minLat, minLon, maxLat, maxLon); + } + + /** + * Creates a BoundingBox that is a fixed meter amount larger on all sides (but does not cross date line/poles). + * + * @param meters extension (must be >= 0) + * @return an extended BoundingBox or this (if meters == 0) + */ + public BoundingBox extendMeters(int meters) { + if (meters == 0) { + return this; + } else if (meters < 0) { + throw new IllegalArgumentException("BoundingBox extend operation does not accept negative values"); + } + + double verticalExpansion = GeoPoint.latitudeDistance(meters); + double horizontalExpansion = GeoPoint.longitudeDistance(meters, Math.max(Math.abs(getMinLatitude()), Math.abs(getMaxLatitude()))); + + double minLat = Math.max(MercatorProjection.LATITUDE_MIN, getMinLatitude() - verticalExpansion); + double minLon = Math.max(MercatorProjection.LONGITUDE_MIN, getMinLongitude() - horizontalExpansion); + double maxLat = Math.min(MercatorProjection.LATITUDE_MAX, getMaxLatitude() + verticalExpansion); + double maxLon = Math.min(MercatorProjection.LONGITUDE_MAX, getMaxLongitude() + horizontalExpansion); + + return new BoundingBox(minLat, minLon, maxLat, maxLon); + } + + public String format() { + return new StringBuilder() + .append(minLatitudeE6 / CONVERSION_FACTOR) + .append(',') + .append(minLongitudeE6 / CONVERSION_FACTOR) + .append(',') + .append(maxLatitudeE6 / CONVERSION_FACTOR) + .append(',') + .append(maxLongitudeE6 / CONVERSION_FACTOR) + .toString(); + } + /** * @return the GeoPoint at the horizontal and vertical center of this * BoundingBox. @@ -118,6 +256,20 @@ public class BoundingBox { + longitudeOffset); } + /** + * @return the latitude span of this BoundingBox in degrees. + */ + public double getLatitudeSpan() { + return getMaxLatitude() - getMinLatitude(); + } + + /** + * @return the longitude span of this BoundingBox in degrees. + */ + public double getLongitudeSpan() { + return getMaxLongitude() - getMinLatitude(); + } + /** * @return the maximum latitude value of this BoundingBox in degrees. */ @@ -156,6 +308,19 @@ public class BoundingBox { return result; } + /** + * @param boundingBox the BoundingBox which should be checked for intersection with this BoundingBox. + * @return true if this BoundingBox intersects with the given BoundingBox, false otherwise. + */ + public boolean intersects(BoundingBox boundingBox) { + if (this == boundingBox) { + return true; + } + + return getMaxLatitude() >= boundingBox.getMinLatitude() && getMaxLongitude() >= boundingBox.getMinLongitude() + && getMinLatitude() <= boundingBox.getMaxLatitude() && getMinLongitude() <= boundingBox.getMaxLongitude(); + } + @Override public String toString() { return new StringBuilder() @@ -170,33 +335,4 @@ public class BoundingBox { .append("]") .toString(); } - - public String format() { - return new StringBuilder() - .append(minLatitudeE6 / CONVERSION_FACTOR) - .append(',') - .append(minLongitudeE6 / CONVERSION_FACTOR) - .append(',') - .append(maxLatitudeE6 / CONVERSION_FACTOR) - .append(',') - .append(maxLongitudeE6 / CONVERSION_FACTOR) - .toString(); - } - - /* code below is from osdmroid, @author Nicolas Gramlich */ - public static BoundingBox fromGeoPoints(final List partialPolyLine) { - int minLat = Integer.MAX_VALUE; - int minLon = Integer.MAX_VALUE; - int maxLat = Integer.MIN_VALUE; - int maxLon = Integer.MIN_VALUE; - for (final GeoPoint gp : partialPolyLine) { - - minLat = Math.min(minLat, gp.latitudeE6); - minLon = Math.min(minLon, gp.longitudeE6); - maxLat = Math.max(maxLat, gp.latitudeE6); - maxLon = Math.max(maxLon, gp.longitudeE6); - } - - return new BoundingBox(minLat, minLon, maxLat, maxLon); - } }