Skip to content
This repository has been archived by the owner on Aug 8, 2023. It is now read-only.

VisibleRegion on rotated map #12255

Merged
merged 1 commit into from
Jul 27, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
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;
Expand Down Expand Up @@ -124,54 +126,114 @@ public VisibleRegion getVisibleRegion(boolean ignorePadding) {
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<LatLng> 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<LatLng> 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 <a href="http://turfjs.org/docs/#bearing">Turf Bearing documentation</a>
*/
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;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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))
}
Expand Down