From c85c99f8e8a58a273e0ba7463d74bb1c762fc61e Mon Sep 17 00:00:00 2001 From: Tobrun Date: Fri, 6 Dec 2019 15:30:36 +0100 Subject: [PATCH] [projection] add bulk conversion of geographic coordinates to screen coordinates with reusable objects (#91) --- CHANGELOG.md | 1 + .../com/mapbox/mapboxsdk/maps/NativeMap.java | 4 + .../mapbox/mapboxsdk/maps/NativeMapView.java | 21 ++++ .../com/mapbox/mapboxsdk/maps/Projection.java | 25 ++++ .../maps/ScreenCoordinateConversion.kt | 112 ++++++++++++++++++ vendor/mapbox-gl-native | 2 +- 6 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ScreenCoordinateConversion.kt diff --git a/CHANGELOG.md b/CHANGELOG.md index d7bc7564c..dd7ddbd3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Mapbox welcomes participation and contributions from everyone. If you'd like to ### Features - Introduce OfflineManager.runPackDatabaseAutomatically(boolean) and remove the redundant OfflineRegion.deleteAndSkipPackDatabase() method [#78](https://github.com/mapbox/mapbox-gl-native-android/pull/78) - Expose getter for the view used for rendering OpenGL content on [#87](https://github.com/mapbox/mapbox-gl-native-android/pull/87) +- Bulk conversion of geographic coordinates to screen coordinate using reusable double arrays. [#91](https://github.com/mapbox/mapbox-gl-native-android/pull/91) ### Performance improvements - Make network requests for expired resources lower priority than requests for new resources. ([#15950](https://github.com/mapbox/mapbox-gl-native/pull/15950)) diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java index c805dcddb..2c6a49305 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMap.java @@ -193,8 +193,12 @@ interface NativeMap { @NonNull PointF pixelForLatLng(@NonNull LatLng latLng); + void pixelsForLatLngs(@NonNull double[] input, @NonNull double[] output); + LatLng latLngForPixel(@NonNull PointF pixel); + void latLngsForPixels(@NonNull double[] input, @NonNull double[] output); + // // Utils API // diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index 75ca5edfc..2c69cb322 100755 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -651,6 +651,13 @@ final class NativeMapView implements NativeMap { return pointF; } + @Override + public void pixelsForLatLngs(@NonNull double[] input, @NonNull double[] output) { + if (!checkState("pixelsForLatLngs")) { + nativePixelsForLatLngs(input, output, pixelRatio); + } + } + @Override public LatLng latLngForPixel(@NonNull PointF pixel) { if (checkState("latLngForPixel")) { @@ -659,6 +666,14 @@ final class NativeMapView implements NativeMap { return nativeLatLngForPixel(pixel.x / pixelRatio, pixel.y / pixelRatio); } + @Override + public void latLngsForPixels(@NonNull double[] input, @NonNull double[] output) { + if (!checkState("latLngsForPixels")) { + nativeLatLngsForPixels(input, output, pixelRatio); + } + } + + @Override public double getTopOffsetPixelsForAnnotationSymbol(String symbolName) { if (checkState("getTopOffsetPixelsForAnnotationSymbol")) { @@ -1275,10 +1290,16 @@ final class NativeMapView implements NativeMap { @Keep private native PointF nativePixelForLatLng(double lat, double lon); + @Keep + private native void nativePixelsForLatLngs(double[] input, double[] output, float pixelRatio); + @NonNull @Keep private native LatLng nativeLatLngForPixel(float x, float y); + @Keep + private native void nativeLatLngsForPixels(double[] input, double[] output, float pixelRatio); + @Keep private native double nativeGetTopOffsetPixelsForAnnotationSymbol(String symbolName); diff --git a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java index 76395f956..ae28576fa 100644 --- a/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java +++ b/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/Projection.java @@ -95,6 +95,19 @@ public class Projection { return nativeMapView.latLngForPixel(point); } + + /** + * Returns the geographic locations that corresponds to screen locations. + * The screen locations are specified in screen pixels (not display pixels) relative to the + * top left of the map (not the top left of the whole screen). + * + * @param input an array of input values representing screen coordinates + * @param output an array of output values representing geographic locations + */ + public void fromScreenLocations(@NonNull double[] input, @NonNull double[] output) { + nativeMapView.latLngsForPixels(input, output); + } + /** * Gets a projection of the viewing frustum for converting between screen coordinates and * geo-latitude/longitude coordinates. @@ -263,6 +276,18 @@ public class Projection { return nativeMapView.pixelForLatLng(location); } + /** + * Returns a screen locations that corresponds to a geographical coordinates. + * The screen locations are in screen pixels (not display pixels) relative to the top left + * of the map (not of the whole screen). + * + * @param input an array of input values representing geographic locations + * @param output an array of output values representing screen coordinates + */ + public void toScreenLocations(@NonNull double[] input, @NonNull double[] output) { + nativeMapView.pixelsForLatLngs(input, output); + } + float getHeight() { return mapView.getHeight(); } diff --git a/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ScreenCoordinateConversion.kt b/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ScreenCoordinateConversion.kt new file mode 100644 index 000000000..3894bf46f --- /dev/null +++ b/MapboxGLAndroidSDKTestApp/src/androidTest/java/com/mapbox/mapboxsdk/testapp/maps/ScreenCoordinateConversion.kt @@ -0,0 +1,112 @@ +package com.mapbox.mapboxsdk.testapp.maps + +import android.graphics.PointF +import android.support.test.rule.ActivityTestRule +import android.support.test.runner.AndroidJUnit4 +import com.mapbox.mapboxsdk.AppCenter +import com.mapbox.mapboxsdk.geometry.LatLng +import com.mapbox.mapboxsdk.maps.MapView +import com.mapbox.mapboxsdk.testapp.R +import com.mapbox.mapboxsdk.testapp.activity.espresso.DeviceIndependentTestActivity +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ScreenCoordinateConversion : AppCenter() { + + @Rule + @JvmField + var rule = ActivityTestRule(DeviceIndependentTestActivity::class.java) + + private lateinit var mapView: MapView + private lateinit var latch: CountDownLatch + + @Before + fun setup() { + latch = CountDownLatch(1) + } + + @Test + fun testToScreenCoordinates() { + rule.runOnUiThread { + mapView = rule.activity.findViewById(R.id.mapView) + val expected = doubleArrayOf(261.6888889445351, 253.15541110551123, 267.37777788907033, 247.4627648056528) + val actual = doubleArrayOf(0.0, 0.0, 0.0, 0.0) + mapView.getMapAsync { + val projection = it.projection + projection.toScreenLocations(doubleArrayOf(1.0, 2.0, 3.0, 4.0), actual) + // check all values + assertTrue(expected.contentEquals(actual)) + // check first coordinate value + val pointFirst = PointF(expected[0].toFloat(), expected[1].toFloat()) + val convertedFirst = projection.toScreenLocation(LatLng(1.0, 2.0)) + assertEquals( + pointFirst.x, convertedFirst.x, DELTA_FLOAT + ) + assertEquals( + pointFirst.y, convertedFirst.y, DELTA_FLOAT + ) + + // check second latlng value + val pointSecond = PointF(expected[2].toFloat(), expected[3].toFloat()) + val convertedSecond = projection.toScreenLocation(LatLng(3.0, 4.0)) + assertEquals( + pointSecond.x, convertedSecond.x, DELTA_FLOAT + ) + assertEquals( + pointSecond.y, convertedSecond.y, DELTA_FLOAT + ) + latch.countDown() + } + } + latch.await(10, TimeUnit.SECONDS) + } + + @Test + fun testFromScreenCoordinates() { + rule.runOnUiThread { + mapView = rule.activity.findViewById(R.id.mapView) + val expected = doubleArrayOf(66.2314571265913, -89.64843662311537, 65.94647142295494, -88.94531162999371) + val actual = doubleArrayOf(0.0, 0.0, 0.0, 0.0) + mapView.getMapAsync { + val projection = it.projection + projection.fromScreenLocations(doubleArrayOf(1.0, 2.0, 3.0, 4.0), actual) + // check all values at once + assertTrue(expected.contentEquals(actual)) + + // check first latlng value + val latLngFirst = LatLng(expected[0], expected[1]) + val convertedFirst = projection.fromScreenLocation(PointF(1.0f, 2.0f)) + assertEquals( + latLngFirst.latitude, convertedFirst.latitude, DELTA_DOUBLE + ) + assertEquals( + latLngFirst.longitude, convertedFirst.longitude, DELTA_DOUBLE + ) + + // check second latlng value + val latLngSecond = LatLng(expected[2], expected[3]) + val convertedSecond = projection.fromScreenLocation(PointF(3.0f, 4.0f)) + assertEquals( + latLngSecond.latitude, convertedSecond.latitude, DELTA_DOUBLE + ) + assertEquals( + latLngSecond.longitude, convertedSecond.longitude, DELTA_DOUBLE + ) + latch.countDown() + } + } + latch.await(10, TimeUnit.SECONDS) + } + + companion object { + const val DELTA_DOUBLE = 0.000001 + const val DELTA_FLOAT = 0.000001f + } +} \ No newline at end of file diff --git a/vendor/mapbox-gl-native b/vendor/mapbox-gl-native index e373d8a59..1ac3e9e9c 160000 --- a/vendor/mapbox-gl-native +++ b/vendor/mapbox-gl-native @@ -1 +1 @@ -Subproject commit e373d8a5924e4f4cf3904ecacbf1d1cf86a5d60f +Subproject commit 1ac3e9e9c2a8bfc731e3bba978a28562c3f083e5