From f05199e2d94a085c615aea96c867280e88a6ef6e Mon Sep 17 00:00:00 2001 From: Osana Babayan <32496536+osana@users.noreply.github.com> Date: Fri, 27 Jul 2018 09:31:33 -0700 Subject: VisibleRegion on rotated bounds (#12135) (#12255) --- .../java/com/mapbox/mapboxsdk/maps/Projection.java | 138 +++++++++++++++------ .../mapboxsdk/testapp/maps/VisibleRegionTest.kt | 48 +++++++ 2 files changed, 148 insertions(+), 38 deletions(-) diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java index 5580dcf99e..3ecf7354c4 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java @@ -4,6 +4,8 @@ import android.graphics.PointF; import android.support.annotation.FloatRange; import android.support.annotation.NonNull; +import com.mapbox.geojson.Point; +import com.mapbox.mapboxsdk.constants.GeometryConstants; import com.mapbox.mapboxsdk.geometry.LatLng; import com.mapbox.mapboxsdk.geometry.LatLngBounds; import com.mapbox.mapboxsdk.geometry.ProjectedMeters; @@ -124,54 +126,114 @@ public class Projection { bottom = nativeMapView.getHeight() - contentPadding[3]; } + LatLng center = fromScreenLocation(new PointF(left + (right - left) / 2, top + (bottom - top) / 2)); + LatLng topLeft = fromScreenLocation(new PointF(left, top)); LatLng topRight = fromScreenLocation(new PointF(right, top)); LatLng bottomRight = fromScreenLocation(new PointF(right, bottom)); LatLng bottomLeft = fromScreenLocation(new PointF(left, bottom)); - // Map can be rotated, find correct LatLngBounds that encompasses the visible region (that might be rotated) - List boundsPoints = new ArrayList<>(); - boundsPoints.add(topLeft); - boundsPoints.add(topRight); - boundsPoints.add(bottomRight); - boundsPoints.add(bottomLeft); - - // order so that two most northern point are put first - while ((boundsPoints.get(0).getLatitude() < boundsPoints.get(3).getLatitude()) - || (boundsPoints.get(1).getLatitude() < boundsPoints.get(2).getLatitude())) { - LatLng first = boundsPoints.remove(0); - boundsPoints.add(first); - } + List latLngs = new ArrayList<>(); + latLngs.add(topRight); + latLngs.add(bottomRight); + latLngs.add(bottomLeft); + latLngs.add(topLeft); - double north = boundsPoints.get(0).getLatitude(); - if (north < boundsPoints.get(1).getLatitude()) { - north = boundsPoints.get(1).getLatitude(); - } + double maxEastLonSpan = 0; + double maxWestLonSpan = 0; + + double east = 0; + double west = 0; + double north = GeometryConstants.MIN_LATITUDE; + double south = GeometryConstants.MAX_LATITUDE; - double south = boundsPoints.get(2).getLatitude(); - if (south > boundsPoints.get(3).getLatitude()) { - south = boundsPoints.get(3).getLatitude(); + for (LatLng latLng : latLngs) { + double bearing = bearing(center, latLng); + + if (bearing >= 0) { + double span = getLongitudeSpan(latLng.getLongitude(), center.getLongitude()); + if (span > maxEastLonSpan) { + maxEastLonSpan = span; + east = latLng.getLongitude(); + } + } else { + double span = getLongitudeSpan(center.getLongitude(), latLng.getLongitude()); + if (span > maxWestLonSpan) { + maxWestLonSpan = span; + west = latLng.getLongitude(); + } + } + + if (north < latLng.getLatitude()) { + north = latLng.getLatitude(); + } + if (south > latLng.getLatitude()) { + south = latLng.getLatitude(); + } } - double firstLon = boundsPoints.get(0).getLongitude(); - double secondLon = boundsPoints.get(1).getLongitude(); - double thridLon = boundsPoints.get(2).getLongitude(); - double fourthLon = boundsPoints.get(3).getLongitude(); - - // if it does not go over the date line - if (secondLon > fourthLon && firstLon < thridLon) { - return new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight, - LatLngBounds.from(north, - secondLon > thridLon ? secondLon : thridLon, - south, - firstLon < fourthLon ? firstLon : fourthLon)); - } else { - return new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight, - LatLngBounds.from(north, - secondLon < thridLon ? secondLon : thridLon, - south, - firstLon > fourthLon ? firstLon : fourthLon)); + return new VisibleRegion(topLeft, topRight, bottomLeft, bottomRight, + LatLngBounds.from(north, east, south, west)); + } + + /** + * Takes two {@link Point}s and finds the geographic bearing between them. + * + * @param latLng1 the first point used for calculating the bearing + * @param latLng2 the second point used for calculating the bearing + * @return bearing in decimal degrees + * @see Turf Bearing documentation + */ + static double bearing(@NonNull LatLng latLng1, @NonNull LatLng latLng2) { + + double lon1 = degreesToRadians(latLng1.getLongitude()); + double lon2 = degreesToRadians(latLng2.getLongitude()); + double lat1 = degreesToRadians(latLng1.getLatitude()); + double lat2 = degreesToRadians(latLng2.getLatitude()); + + double value1 = Math.sin(lon2 - lon1) * Math.cos(lat2); + double value2 = Math.cos(lat1) * Math.sin(lat2) - Math.sin(lat1) + * Math.cos(lat2) * Math.cos(lon2 - lon1); + + return radiansToDegrees(Math.atan2(value1, value2)); + } + + /** + * Converts an angle in degrees to radians. + * + * @param degrees angle between 0 and 360 degrees + * @return angle in radians + */ + static double degreesToRadians(double degrees) { + double radians = degrees % 360; + return radians * Math.PI / 180; + } + + /** + * Converts an angle in radians to degrees. + * + * @param radians angle in radians + * @return degrees between 0 and 360 degrees + */ + static double radiansToDegrees(double radians) { + double degrees = radians % (2 * Math.PI); + return degrees * 180 / Math.PI; + } + + /** + * Get the absolute distance, in degrees, between the west and + * east boundaries of this LatLngBounds + * + * @return Span distance + */ + static double getLongitudeSpan(double east, double west) { + double longSpan = Math.abs(east - west); + if (east > west) { + return longSpan; } + + // shortest span contains antimeridian + return GeometryConstants.LONGITUDE_SPAN - longSpan; } /** diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/VisibleRegionTest.kt b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/VisibleRegionTest.kt index f09a5eff00..f0d62dd370 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/VisibleRegionTest.kt +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/VisibleRegionTest.kt @@ -2,6 +2,8 @@ package com.mapbox.mapboxsdk.testapp.maps import android.graphics.PointF import android.support.test.espresso.UiController +import com.mapbox.mapboxsdk.camera.CameraPosition +import com.mapbox.mapboxsdk.camera.CameraUpdate import com.mapbox.mapboxsdk.camera.CameraUpdateFactory import com.mapbox.mapboxsdk.geometry.LatLng import com.mapbox.mapboxsdk.maps.MapView @@ -336,6 +338,52 @@ class VisibleRegionTest : BaseActivityTest() { } } + @Test + fun visibleRotatedRegionTest() { + validateTestSetup() + invoke(mapboxMap) { _: UiController, mapboxMap: MapboxMap -> + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 0.0), 8.0)) + val d = Math.min(mapboxMap.width, mapboxMap.height) / 4; + val latLngs = listOf( + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f, mapView.height / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f - d / 2f, mapView.height / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f + d / 2f, mapView.height / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f, mapView.height / 2f - d / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f, mapView.height / 2f + d / 2f) + ) + + + for (bearing in 45 until 360 step 45) { + mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(bearing.toDouble())); + val visibleRegion = mapboxMap.projection.visibleRegion + assertTrue(latLngs.all { visibleRegion.latLngBounds.contains(it) }) + } + } + } + + @Test + fun visibleRotatedRegionOverDatelineTest() { + validateTestSetup() + invoke(mapboxMap) { _: UiController, mapboxMap: MapboxMap -> + mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(LatLng(0.0, 180.0), 8.0)) + val d = Math.min(mapboxMap.width, mapboxMap.height) / 4; + val latLngs = listOf( + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f, mapView.height / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f - d / 2f, mapView.height / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f + d / 2f, mapView.height / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f, mapView.height / 2f - d / 2f), + mapboxMap.getLatLngFromScreenCoords(mapView.width / 2f, mapView.height / 2f + d / 2f) + ) + + + for (bearing in 45 until 360 step 45) { + mapboxMap.moveCamera(CameraUpdateFactory.bearingTo(bearing.toDouble())); + val visibleRegion = mapboxMap.projection.visibleRegion + assertTrue(latLngs.all { visibleRegion.latLngBounds.contains(it) }) + } + } + } + private fun MapboxMap.getLatLngFromScreenCoords(x: Float, y: Float): LatLng { return this.projection.fromScreenLocation(PointF(x, y)) } -- cgit v1.2.1