diff options
Diffstat (limited to 'platform')
17 files changed, 1340 insertions, 54 deletions
diff --git a/platform/android/MapboxGLAndroidSDK/build.gradle b/platform/android/MapboxGLAndroidSDK/build.gradle index e2042fa6da..09c5887752 100644 --- a/platform/android/MapboxGLAndroidSDK/build.gradle +++ b/platform/android/MapboxGLAndroidSDK/build.gradle @@ -27,6 +27,11 @@ dependencies { compile "com.android.support:design:${supportLibVersion}" compile 'com.squareup.okhttp3:okhttp:3.3.0' compile 'com.mapzen.android:lost:1.1.0' + + // Mapbox Android Services + compile('com.mapbox.mapboxsdk:mapbox-android-services:2.0.0-SNAPSHOT@aar') { + transitive = true + } } android { diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java index b2c48be69c..fbc3f2f461 100644 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/MapboxMap.java @@ -2,6 +2,8 @@ package com.mapbox.mapboxsdk.maps; import android.content.Context; import android.graphics.Bitmap; +import android.graphics.PointF; +import android.graphics.RectF; import android.location.Location; import android.os.SystemClock; import android.support.annotation.FloatRange; @@ -42,6 +44,7 @@ import com.mapbox.mapboxsdk.maps.widgets.MyLocationViewSettings; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.NoSuchLayerException; import com.mapbox.mapboxsdk.style.sources.Source; +import com.mapbox.services.commons.geojson.Feature; import java.lang.reflect.ParameterizedType; import java.util.ArrayList; @@ -1722,6 +1725,33 @@ public class MapboxMap { mMapView.snapshot(callback, null); } + /** + * Queries the map for rendered features + * + * @param coordinates the point to query + * @param layerIds optionally - only query these layers + * @return the list of feature + */ + @UiThread + @NonNull + public List<Feature> queryRenderedFeatures(@NonNull PointF coordinates, @Nullable String... layerIds) { + return mMapView.getNativeMapView().queryRenderedFeatures(coordinates, layerIds); + } + + /** + * Queries the map for rendered features + * + * @param coordinates the box to query + * @param layerIds optionally - only query these layers + * @return the list of feature + */ + @UiThread + @NonNull + public List<Feature> queryRenderedFeatures(@NonNull RectF coordinates, @Nullable String... layerIds) { + return mMapView.getNativeMapView().queryRenderedFeatures(coordinates, layerIds); + } + + // // Interfaces // diff --git a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java index c93ac9c145..1010e9baa6 100755 --- a/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java +++ b/platform/android/MapboxGLAndroidSDK/src/main/java/com/mapbox/mapboxsdk/maps/NativeMapView.java @@ -20,7 +20,10 @@ import com.mapbox.mapboxsdk.offline.OfflineManager; import com.mapbox.mapboxsdk.style.layers.Layer; import com.mapbox.mapboxsdk.style.layers.NoSuchLayerException; import com.mapbox.mapboxsdk.style.sources.Source; +import com.mapbox.services.commons.geojson.Feature; +import java.util.ArrayList; +import java.util.Arrays; import java.util.List; // Class that wraps the native methods for convenience @@ -42,6 +45,8 @@ final class NativeMapView { // Used for callbacks private MapView mMapView; + private final float pixelRatio; + // // Static methods // @@ -63,7 +68,7 @@ final class NativeMapView { // the system String cachePath = dataPath; - float pixelRatio = context.getResources().getDisplayMetrics().density; + pixelRatio = context.getResources().getDisplayMetrics().density; String apkPath = context.getPackageCodePath(); int availableProcessors = Runtime.getRuntime().availableProcessors(); ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo(); @@ -501,6 +506,26 @@ final class NativeMapView { nativeRemoveSource(mNativeMapViewPtr, sourceId); } + // Feature querying + + @NonNull + public List<Feature> queryRenderedFeatures(PointF coordinates, String... layerIds) { + Feature[] features = nativeQueryRenderedFeaturesForPoint(mNativeMapViewPtr, coordinates.x / pixelRatio, coordinates.y / pixelRatio, layerIds); + return features != null ? Arrays.asList(features) : new ArrayList<Feature>(); + } + + @NonNull + public List<Feature> queryRenderedFeatures(RectF coordinates, String... layerIds) { + Feature[] features = nativeQueryRenderedFeaturesForBox( + mNativeMapViewPtr, + coordinates.left / pixelRatio, + coordinates.top / pixelRatio, + coordinates.right / pixelRatio, + coordinates.bottom / pixelRatio, + layerIds); + return features != null ? Arrays.asList(features) : new ArrayList<Feature>(); + } + public void scheduleTakeSnapshot() { nativeScheduleTakeSnapshot(mNativeMapViewPtr); } @@ -696,4 +721,8 @@ final class NativeMapView { private native long nativeUpdatePolyline(long nativeMapviewPtr, long polylineId, Polyline polyline); private native void nativeScheduleTakeSnapshot(long nativeMapViewPtr); + + private native Feature[] nativeQueryRenderedFeaturesForPoint(long nativeMapViewPtr, float x, float y, String[] layerIds); + + private native Feature[] nativeQueryRenderedFeaturesForBox(long mNativeMapViewPtr, float left, float top, float right, float bottom, String[] layerIds); } diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml index db1fa025ae..1a46671ade 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/AndroidManifest.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> -<manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="com.mapbox.mapboxsdk.testapp"> +<manifest package="com.mapbox.mapboxsdk.testapp" + xmlns:android="http://schemas.android.com/apk/res/android"> - <uses-permission android:name="android.permission.INTERNET" /> - <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> - <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> - <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> - <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.INTERNET"/> + <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/> + <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/> + <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:name=".MapboxApplication" @@ -20,8 +20,8 @@ android:name=".activity.FeatureOverviewActivity" android:label="@string/app_name"> <intent-filter> - <action android:name="android.intent.action.MAIN" /> - <category android:name="android.intent.category.LAUNCHER" /> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity @@ -30,7 +30,7 @@ android:label="@string/activity_info_window"> <meta-data android:name="@string/category" - android:value="@string/category_infowindow" /> + android:value="@string/category_infowindow"/> </activity> <activity android:name=".activity.infowindow.InfoWindowAdapterActivity" @@ -38,7 +38,7 @@ android:label="@string/activity_infowindow_adapter"> <meta-data android:name="@string/category" - android:value="@string/category_infowindow" /> + android:value="@string/category_infowindow"/> </activity> <activity android:name=".activity.infowindow.DynamicInfoWindowAdapterActivity" @@ -46,7 +46,7 @@ android:label="@string/activity_dynamic_infowindow_adapter"> <meta-data android:name="@string/category" - android:value="@string/category_infowindow" /> + android:value="@string/category_infowindow"/> </activity> <activity android:name=".activity.annotation.BulkMarkerActivity" @@ -55,7 +55,7 @@ android:label="@string/activity_add_bulk_markers"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.annotation.AnimatedMarkerActivity" @@ -63,7 +63,7 @@ android:label="@string/activity_animated_marker"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.annotation.DynamicMarkerChangeActivity" @@ -71,7 +71,7 @@ android:label="@string/activity_dynamic_marker"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.annotation.PressForMarkerActivity" @@ -79,7 +79,7 @@ android:label="@string/activity_press_for_marker"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.camera.CameraAnimationTypeActivity" @@ -87,7 +87,7 @@ android:label="@string/activity_camera_animation_types"> <meta-data android:name="@string/category" - android:value="@string/category_camera" /> + android:value="@string/category_camera"/> </activity> <activity android:name=".activity.camera.CameraPositionActivity" @@ -95,7 +95,7 @@ android:label="@string/activity_camera_position"> <meta-data android:name="@string/category" - android:value="@string/category_camera" /> + android:value="@string/category_camera"/> </activity> <activity android:name=".activity.camera.LatLngBoundsActivity" @@ -104,7 +104,7 @@ android:screenOrientation="portrait"> <meta-data android:name="@string/category" - android:value="@string/category_camera" /> + android:value="@string/category_camera"/> </activity> <activity android:name=".activity.fragment.MapFragmentActivity" @@ -112,7 +112,7 @@ android:label="@string/activity_map_fragment"> <meta-data android:name="@string/category" - android:value="@string/category_fragment" /> + android:value="@string/category_fragment"/> </activity> <activity android:name=".activity.fragment.SupportMapFragmentActivity" @@ -120,7 +120,7 @@ android:label="@string/activity_map_fragment_suport"> <meta-data android:name="@string/category" - android:value="@string/category_fragment" /> + android:value="@string/category_fragment"/> </activity> <activity android:name=".activity.camera.ManualZoomActivity" @@ -128,7 +128,7 @@ android:label="@string/activity_camera_zoom"> <meta-data android:name="@string/category" - android:value="@string/category_camera" /> + android:value="@string/category_camera"/> </activity> <activity android:name=".activity.camera.MaxMinZoomActivity" @@ -136,7 +136,7 @@ android:label="@string/activity_minmax_zoom"> <meta-data android:name="@string/category" - android:value="@string/category_camera" /> + android:value="@string/category_camera"/> </activity> <activity android:name=".activity.customlayer.CustomLayerActivity" @@ -144,7 +144,7 @@ android:label="@string/activity_custom_layer"> <meta-data android:name="@string/category" - android:value="@string/category_custom_layer" /> + android:value="@string/category_custom_layer"/> </activity> <activity android:name=".activity.userlocation.MyLocationTrackingModeActivity" @@ -152,7 +152,7 @@ android:label="@string/activity_user_tracking_mode"> <meta-data android:name="@string/category" - android:value="@string/category_userlocation" /> + android:value="@string/category_userlocation"/> </activity> <activity android:name=".activity.userlocation.MyLocationDrawableActivity" @@ -160,7 +160,7 @@ android:label="@string/activity_user_tracking_customization"> <meta-data android:name="@string/category" - android:value="@string/category_userlocation" /> + android:value="@string/category_userlocation"/> </activity> <activity android:name=".activity.userlocation.MyLocationTintActivity" @@ -168,7 +168,7 @@ android:label="@string/activity_user_dot_color"> <meta-data android:name="@string/category" - android:value="@string/category_userlocation" /> + android:value="@string/category_userlocation"/> </activity> <activity android:name=".activity.userlocation.MyLocationToggleActivity" @@ -176,7 +176,7 @@ android:label="@string/activity_user_location_toggle"> <meta-data android:name="@string/category" - android:value="@string/category_userlocation" /> + android:value="@string/category_userlocation"/> </activity> <activity android:name=".activity.annotation.PolygonActivity" @@ -184,7 +184,7 @@ android:label="@string/activity_polygon"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.annotation.PolylineActivity" @@ -192,7 +192,7 @@ android:label="@string/activity_polyline"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.directions.DirectionsActivity" @@ -200,7 +200,7 @@ android:label="@string/activity_directions"> <meta-data android:name="@string/category" - android:value="@string/category_directions" /> + android:value="@string/category_directions"/> </activity> <activity android:name=".activity.geocoding.GeocoderActivity" @@ -208,7 +208,7 @@ android:label="@string/activity_geocoder"> <meta-data android:name="@string/category" - android:value="@string/category_geocoding" /> + android:value="@string/category_geocoding"/> </activity> <activity android:name=".activity.camera.ScrollByActivity" @@ -216,7 +216,7 @@ android:label="@string/activity_scroll_by"> <meta-data android:name="@string/category" - android:value="@string/category_camera" /> + android:value="@string/category_camera"/> </activity> <activity android:name=".activity.maplayout.MapPaddingActivity" @@ -225,7 +225,7 @@ android:screenOrientation="portrait"> <meta-data android:name="@string/category" - android:value="@string/category_maplayout" /> + android:value="@string/category_maplayout"/> </activity> <activity android:name=".activity.maplayout.DebugModeActivity" @@ -233,7 +233,7 @@ android:label="@string/activity_debug_mode"> <meta-data android:name="@string/category" - android:value="@string/category_maplayout" /> + android:value="@string/category_maplayout"/> </activity> <activity android:name=".activity.offline.OfflineActivity" @@ -241,7 +241,7 @@ android:label="@string/activity_offline"> <meta-data android:name="@string/category" - android:value="@string/category_offline" /> + android:value="@string/category_offline"/> </activity> <activity android:name=".activity.imagegenerator.SnapshotActivity" @@ -249,7 +249,7 @@ android:label="@string/activity_snapshot"> <meta-data android:name="@string/category" - android:value="@string/category_imagegenerator" /> + android:value="@string/category_imagegenerator"/> </activity> <activity android:name=".activity.maplayout.DoubleMapActivity" @@ -257,7 +257,7 @@ android:label="@string/activity_double_map"> <meta-data android:name="@string/category" - android:value="@string/category_maplayout" /> + android:value="@string/category_maplayout"/> </activity> <activity android:name=".activity.annotation.MarkerViewActivity" @@ -265,7 +265,7 @@ android:label="@string/activity_view_marker"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.annotation.MarkerViewScaleActivity" @@ -273,7 +273,7 @@ android:label="@string/activity_view_marker_scale"> <meta-data android:name="@string/category" - android:value="@string/category_annotation" /> + android:value="@string/category_annotation"/> </activity> <activity android:name=".activity.navigation.LocationPickerActivity" @@ -281,7 +281,7 @@ android:label="@string/activity_location_picker"> <meta-data android:name="@string/category" - android:value="@string/category_navigation" /> + android:value="@string/category_navigation"/> </activity> <activity android:name=".activity.fragment.ViewPagerActivity" @@ -289,7 +289,7 @@ android:label="@string/activity_viewpager"> <meta-data android:name="@string/category" - android:value="@string/category_fragment" /> + android:value="@string/category_fragment"/> </activity> <activity android:name=".activity.maplayout.NavigationDrawerActivity" @@ -298,7 +298,7 @@ android:theme="@style/AppTheme.ActionBar.Transparent"> <meta-data android:name="@string/category" - android:value="@string/category_fragment" /> + android:value="@string/category_fragment"/> </activity> <activity android:name=".activity.style.RuntimeStyleActivity" @@ -306,7 +306,7 @@ android:label="@string/activity_runtime_style"> <meta-data android:name="@string/category" - android:value="@string/category_style" /> + android:value="@string/category_style"/> </activity> <activity android:name=".activity.style.GeoJsonClusteringActivity" @@ -323,7 +323,7 @@ android:label="@string/activity_print"> <meta-data android:name="@string/category" - android:value="@string/category_imagegenerator" /> + android:value="@string/category_imagegenerator"/> </activity> <activity android:name=".activity.maplayout.SurfaceViewMediaControlActivity" @@ -331,27 +331,53 @@ android:label="@string/activity_surfaceview_overlay"> <meta-data android:name="@string/category" - android:value="@string/category_maplayout" /> + android:value="@string/category_maplayout"/> + </activity> + + <!-- Features --> + <activity + android:name=".activity.feature.QueryRenderedFeaturesPropertiesActivity" + android:description="@string/description_query_rendered_feature_properties_point" + android:label="@string/activity_query_rendered_feature_properties"> + <meta-data + android:name="@string/category" + android:value="@string/category_features" /> + </activity> + <activity + android:name=".activity.feature.QueryRenderedFeaturesBoxCountActivity" + android:description="@string/description_query_rendered_features_box_count" + android:label="@string/activity_query_rendered_features_box_count"> + <meta-data + android:name="@string/category" + android:value="@string/category_features" /> + </activity> + <activity + android:name=".activity.feature.QueryRenderedFeaturesBoxHighlightActivity" + android:description="@string/description_query_rendered_features_box_highlight" + android:label="@string/activity_query_rendered_features_box_highlight"> + <meta-data + android:name="@string/category" + android:value="@string/category_features" /> </activity> <!-- For Unit tests --> - <activity android:name=".activity.style.RuntimeStyleTestActivity" /> - <activity android:name=".activity.style.RuntimeStyleTimingTestActivity" /> + <activity android:name=".activity.style.RuntimeStyleTestActivity"/> + <activity android:name=".activity.style.RuntimeStyleTimingTestActivity"/> <!-- Configuration Settings --> <meta-data android:name="com.mapbox.TestEventsServer" - android:value="https://cloudfront-staging.tilestream.net" /> + android:value="https://cloudfront-staging.tilestream.net"/> <meta-data android:name="com.mapbox.TestEventsAccessToken" - android:value="sk.eyJ1IjoiYmxlZWdlIiwiYSI6InNpcml1c2x5In0.KyT-boMyC_xZYTYojTc8zg" /> + android:value="sk.eyJ1IjoiYmxlZWdlIiwiYSI6InNpcml1c2x5In0.KyT-boMyC_xZYTYojTc8zg"/> <!-- Comment out this setting to switch to external storage (and disable internal) in your app --> <!-- <meta-data --> <!-- android:name="com.mapbox.SetStorageExternal" --> <!-- android:value="true" /> --> - <service android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService" /> + <service android:name="com.mapbox.mapboxsdk.telemetry.TelemetryService"/> </application> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java new file mode 100644 index 0000000000..532bb1c18c --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxCountActivity.java @@ -0,0 +1,157 @@ +package com.mapbox.mapboxsdk.testapp.activity.feature; + +import android.graphics.RectF; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.google.gson.JsonElement; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.testapp.R; +import com.mapbox.services.commons.geojson.Feature; + +import java.util.List; +import java.util.Map; + +/** + * Demo's query rendered features + */ +public class QueryRenderedFeaturesBoxCountActivity extends AppCompatActivity { + private static final String TAG = QueryRenderedFeaturesBoxCountActivity.class.getSimpleName(); + + public MapView mapView; + private MapboxMap mapboxMap; + private Marker marker; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_query_features_box); + setupActionBar(); + + final float density = getResources().getDisplayMetrics().density; + + final View selectionBox = findViewById(R.id.selection_box); + + //Initialize map as normal + mapView = (MapView) findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(new OnMapReadyCallback() { + @SuppressWarnings("ConstantConditions") + @Override + public void onMapReady(final MapboxMap mapboxMap) { + QueryRenderedFeaturesBoxCountActivity.this.mapboxMap = mapboxMap; + + + selectionBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //Query + int top = selectionBox.getTop() - mapView.getTop(); + int left = selectionBox.getLeft() - mapView.getLeft(); + RectF box = new RectF(left, top, left + selectionBox.getWidth(), top + selectionBox.getHeight()); + Log.i(TAG, String.format("Querying box %s", box)); + List<Feature> features = mapboxMap.queryRenderedFeatures(box); + + //Show count + Toast.makeText(QueryRenderedFeaturesBoxCountActivity.this, String.format("%s features in box", features.size()), Toast.LENGTH_SHORT).show(); + + //Debug output + debugOutput(features); + } + }); + + //Little taste of home + mapboxMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(52.0907, 5.1214), 16)); + } + }); + + } + + private void debugOutput(List<Feature> features) { + Log.i(TAG, String.format("Got %s features", features.size())); + for (Feature feature : features) { + if (feature != null) { + Log.i(TAG, String.format("Got feature %s with %s properties and Geometry %s", + feature.getId(), + feature.getProperties() != null ? feature.getProperties().entrySet().size() : "<null>", + feature.getGeometry() != null ? feature.getGeometry().getClass().getSimpleName() : "<null>") + ); + if (feature.getProperties() != null) { + for (Map.Entry<String, JsonElement> entry : feature.getProperties().entrySet()) { + Log.i(TAG, String.format("Prop %s - %s", entry.getKey(), entry.getValue())); + } + } + } else { + Log.i(TAG, "Got NULL feature %s"); + } + } + } + + public MapboxMap getMapboxMap() { + return mapboxMap; + } + + @Override + public void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void setupActionBar() { + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); + } + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java new file mode 100644 index 0000000000..53937b4eca --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesBoxHighlightActivity.java @@ -0,0 +1,155 @@ +package com.mapbox.mapboxsdk.testapp.activity.feature; + +import android.graphics.Color; +import android.graphics.RectF; +import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import com.google.gson.JsonElement; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.annotations.PolygonOptions; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.testapp.R; +import com.mapbox.services.commons.geojson.Feature; +import com.mapbox.services.commons.geojson.Polygon; +import com.mapbox.services.commons.models.Position; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * Demo's query rendered features + */ +public class QueryRenderedFeaturesBoxHighlightActivity extends AppCompatActivity { + private static final String TAG = QueryRenderedFeaturesBoxHighlightActivity.class.getSimpleName(); + + public MapView mapView; + private MapboxMap mapboxMap; + private Marker marker; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_query_features_box); + setupActionBar(); + + final float density = getResources().getDisplayMetrics().density; + + final View selectionBox = findViewById(R.id.selection_box); + + //Initialize map as normal + mapView = (MapView) findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(new OnMapReadyCallback() { + @SuppressWarnings("ConstantConditions") + @Override + public void onMapReady(final MapboxMap mapboxMap) { + QueryRenderedFeaturesBoxHighlightActivity.this.mapboxMap = mapboxMap; + + + selectionBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + //Query + int top = selectionBox.getTop() - mapView.getTop(); + int left = selectionBox.getLeft() - mapView.getLeft(); + RectF box = new RectF(left, top, left + selectionBox.getWidth(), top + selectionBox.getHeight()); + Log.i(TAG, String.format("Querying box %s for buildings", box)); + List<Feature> features = mapboxMap.queryRenderedFeatures(box, "building"); + + //Show count + Toast.makeText(QueryRenderedFeaturesBoxHighlightActivity.this, String.format("%s features in box", features.size()), Toast.LENGTH_SHORT).show(); + + for (Feature feature : features) { + if (feature.getGeometry() instanceof Polygon) { + Polygon building = (Polygon) feature.getGeometry(); + + //Convert outer ring + List<List<Position>> coordinates = building.getCoordinates(); + List<Position> outerRing = coordinates.get(0); + List<LatLng> points = new ArrayList<LatLng>(); + for (Position position : outerRing) { + points.add(new LatLng(position.getLatitude(), position.getLongitude())); + } + + mapboxMap.addPolygon(new PolygonOptions().addAll(points).fillColor(Color.RED)); + } + } + } + }); + + //Little taste of home + mapboxMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(52.0907, 5.1214), 16)); + } + }); + + } + + public MapboxMap getMapboxMap() { + return mapboxMap; + } + + @Override + public void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void setupActionBar() { + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); + } + } + +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java new file mode 100644 index 0000000000..7ce1a68a79 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/java/com/mapbox/mapboxsdk/testapp/activity/feature/QueryRenderedFeaturesPropertiesActivity.java @@ -0,0 +1,258 @@ +package com.mapbox.mapboxsdk.testapp.activity.feature; + +import android.graphics.Color; +import android.graphics.PointF; +import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; +import android.support.annotation.NonNull; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.google.gson.JsonElement; +import com.mapbox.mapboxsdk.annotations.BaseMarkerOptions; +import com.mapbox.mapboxsdk.annotations.Marker; +import com.mapbox.mapboxsdk.camera.CameraUpdateFactory; +import com.mapbox.mapboxsdk.geometry.LatLng; +import com.mapbox.mapboxsdk.maps.MapView; +import com.mapbox.mapboxsdk.maps.MapboxMap; +import com.mapbox.mapboxsdk.maps.OnMapReadyCallback; +import com.mapbox.mapboxsdk.testapp.R; +import com.mapbox.services.commons.geojson.Feature; + +import java.util.List; +import java.util.Map; + +/** + * Demo's query rendered features + */ +public class QueryRenderedFeaturesPropertiesActivity extends AppCompatActivity { + private static final String TAG = QueryRenderedFeaturesPropertiesActivity.class.getSimpleName(); + + public MapView mapView; + private MapboxMap mapboxMap; + private Marker marker; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_query_features_point); + setupActionBar(); + + final float density = getResources().getDisplayMetrics().density; + + //Initialize map as normal + mapView = (MapView) findViewById(R.id.mapView); + mapView.onCreate(savedInstanceState); + mapView.getMapAsync(new OnMapReadyCallback() { + @Override + public void onMapReady(final MapboxMap mapboxMap) { + QueryRenderedFeaturesPropertiesActivity.this.mapboxMap = mapboxMap; + + //Add custom window adapter + addCustomInfoWindowAdapter(mapboxMap); + + //Add a click listener + mapboxMap.setOnMapClickListener(new MapboxMap.OnMapClickListener() { + @Override + public void onMapClick(@NonNull LatLng point) { + //Query + final PointF pixel = mapboxMap.getProjection().toScreenLocation(point); + Log.i(TAG, String.format("Requesting features for %sx%s (%sx%s adjusted for density)", pixel.x, pixel.y, pixel.x / density, pixel.y / density)); + List<Feature> features = mapboxMap.queryRenderedFeatures(pixel); + + //Debug output + debugOutput(features); + + //Remove any previous markers + if (marker != null) { + mapboxMap.removeMarker(marker); + } + + //Add a marker on the clicked point + marker = mapboxMap.addMarker(new CustomMarkerOptions().position(point).features(features)); + mapboxMap.selectMarker(marker); + } + }); + + //Little taste of home + mapboxMap.animateCamera(CameraUpdateFactory.newLatLngZoom(new LatLng(52.0907, 5.1214), 16)); + } + }); + + } + + private void debugOutput(List<Feature> features) { + Log.i(TAG, String.format("Got %s features", features.size())); + for (Feature feature : features) { + if (feature != null) { + Log.i(TAG, String.format("Got feature %s with %s properties and Geometry %s", + feature.getId(), + feature.getProperties() != null ? feature.getProperties().entrySet().size() : "<null>", + feature.getGeometry() != null ? feature.getGeometry().getClass().getSimpleName() : "<null>") + ); + if (feature.getProperties() != null) { + for (Map.Entry<String, JsonElement> entry : feature.getProperties().entrySet()) { + Log.i(TAG, String.format("Prop %s - %s", entry.getKey(), entry.getValue())); + } + } + } else { + Log.i(TAG, "Got NULL feature %s"); + } + } + } + + private void addCustomInfoWindowAdapter(MapboxMap mapboxMap) { + mapboxMap.setInfoWindowAdapter(new MapboxMap.InfoWindowAdapter() { + + private TextView row(String text) { + TextView view = new TextView(QueryRenderedFeaturesPropertiesActivity.this); + view.setText(text); + return view; + } + + private int tenDp = (int) getResources().getDimension(R.dimen.attr_margin); + + @Override + public View getInfoWindow(@NonNull Marker marker) { + CustomMarker customMarker = (CustomMarker) marker; + LinearLayout view = new LinearLayout(QueryRenderedFeaturesPropertiesActivity.this); + view.setOrientation(LinearLayout.VERTICAL); + view.setBackgroundColor(Color.WHITE); + + if (customMarker.features.size() > 0) { + view.addView(row(String.format("Found %s features", customMarker.features.size()))); + Feature feature = customMarker.features.get(0); + for (Map.Entry<String, JsonElement> prop : feature.getProperties().entrySet()) { + view.addView(row(String.format("%s: %s", prop.getKey(), prop.getValue()))); + } + } else { + view.addView(row("No features here")); + } + + return view; + } + }); + } + + public MapboxMap getMapboxMap() { + return mapboxMap; + } + + @Override + public void onResume() { + super.onResume(); + mapView.onResume(); + } + + @Override + public void onPause() { + super.onPause(); + mapView.onPause(); + } + + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + mapView.onSaveInstanceState(outState); + } + + @Override + protected void onDestroy() { + super.onDestroy(); + mapView.onDestroy(); + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + mapView.onLowMemory(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case android.R.id.home: + onBackPressed(); + return true; + default: + return super.onOptionsItemSelected(item); + } + } + + private void setupActionBar() { + Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setDisplayHomeAsUpEnabled(true); + actionBar.setDisplayShowHomeEnabled(true); + } + } + + private static class CustomMarker extends Marker { + + private final List<Feature> features; + + public CustomMarker(BaseMarkerOptions baseMarkerOptions, List<Feature> features) { + super(baseMarkerOptions); + this.features = features; + } + } + + private static class CustomMarkerOptions extends BaseMarkerOptions<CustomMarker, CustomMarkerOptions> { + + + private List<Feature> features; + + public CustomMarkerOptions features(List<Feature> features) { + this.features = features; + return this; + } + + public CustomMarkerOptions() { + } + + private CustomMarkerOptions(Parcel in) { + //Should implement this + } + + @Override + public CustomMarkerOptions getThis() { + return this; + } + + @Override + public CustomMarker getMarker() { + return new CustomMarker(this, features); + } + + public static final Parcelable.Creator<CustomMarkerOptions> CREATOR + = new Parcelable.Creator<CustomMarkerOptions>() { + public CustomMarkerOptions createFromParcel(Parcel in) { + return new CustomMarkerOptions(in); + } + + public CustomMarkerOptions[] newArray(int size) { + return new CustomMarkerOptions[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel out, int flags) { + //Should implement this + } + } +} diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_query_features_box.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_query_features_box.xml new file mode 100644 index 0000000000..e1d710880c --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_query_features_box.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="@color/primary" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> + + <com.mapbox.mapboxsdk.maps.MapView + android:id="@+id/mapView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_below="@id/toolbar" /> + + <FrameLayout + android:id="@+id/selection_box" + android:layout_width="150dp" + android:layout_height="150dp" + android:layout_centerInParent="true" + android:alpha="0.3" + android:background="#1d72da" /> + +</RelativeLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_query_features_point.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_query_features_point.xml new file mode 100644 index 0000000000..4c0d067c14 --- /dev/null +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/layout/activity_query_features_point.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent"> + + <android.support.v7.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?attr/actionBarSize" + android:background="@color/primary" + android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"/> + + <com.mapbox.mapboxsdk.maps.MapView + android:id="@+id/mapView" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_below="@id/toolbar"/> + +</RelativeLayout> diff --git a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml index 1cdf449a6c..4436e40200 100644 --- a/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml +++ b/platform/android/MapboxGLAndroidSDKTestApp/src/main/res/values/strings.xml @@ -57,6 +57,9 @@ <string name="activity_geojson_clustering">GeoJson Clustering</string> <string name="activity_print">Print a map</string> <string name="activity_surfaceview_overlay">SurfaceView MediaOverlay</string> + <string name="activity_query_rendered_feature_properties">Query feature properties</string> + <string name="activity_query_rendered_features_box_count">Count features in box</string> + <string name="activity_query_rendered_features_box_highlight">Highlight features in box</string> <string name="title_activity_navigation_drawer">Android SDK View integration</string> <!-- Description --> @@ -100,6 +103,9 @@ <string name="description_print">Shows how to print a map</string> <string name="description_navigation_drawer">Test animation of Android SDK View components</string> <string name="description_surfaceview_mediacontrols">Test overlaying SurfaceView</string> + <string name="description_query_rendered_feature_properties_point">Query rendered feature properties on click</string> + <string name="description_query_rendered_features_box_count">Count all rendered features in box</string> + <string name="description_query_rendered_features_box_highlight">Hightligh buildings in box</string> <string name="menuitem_title_concurrent_infowindow">Concurrent Open InfoWindows</string> <string name="menuitem_title_deselect_markers_on_tap">Deselect Markers On Tap</string> @@ -121,6 +127,7 @@ <string name="category_userlocation">User Location</string> <string name="category_navigation">Navigation</string> <string name="category_style">Styling</string> + <string name="category_features">Features</string> <string name="action_visible_bounds_explanation">Center map around 2 markers</string> <string name="action_remove_polylines">Remove polylines</string> diff --git a/platform/android/src/conversion/collection.hpp b/platform/android/src/conversion/collection.hpp new file mode 100644 index 0000000000..4256d5f969 --- /dev/null +++ b/platform/android/src/conversion/collection.hpp @@ -0,0 +1,42 @@ +#pragma once + +#include "conversion.hpp" +#include "constant.hpp" + +#include <mbgl/util/optional.hpp> +#include <jni/jni.hpp> + +#include <vector> + +namespace mbgl { +namespace android { +namespace conversion { + +/** + * Convert jarray -> ArrayList + */ +template <class T> +inline jni::jobject* toArrayList(JNIEnv& env, jni::jarray<T>& array) { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/util/Arrays")).release(); + static jni::jmethodID* asList = &jni::GetStaticMethodID(env, *javaClass, "asList", "([Ljava/lang/Object;)Ljava/util/List;"); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *asList, array)); +} + +// Java -> C++ + + +inline std::vector<std::string> toVector(JNIEnv& env, jni::jarray<jni::jobject>& array) { + std::vector<std::string> vector; + std::size_t len = jni::GetArrayLength(env, array); + + for (std::size_t i = 0; i < len; i++) { + jni::jstring* jstr = reinterpret_cast<jni::jstring*>(jni::GetObjectArrayElement(env, array, i)); + vector.push_back(*convert<std::string, jni::String>(env, jni::String(jstr))); + } + + return vector; +} + +} +} +} diff --git a/platform/android/src/conversion/constant.hpp b/platform/android/src/conversion/constant.hpp index 9a570d3717..f1a8171b99 100644 --- a/platform/android/src/conversion/constant.hpp +++ b/platform/android/src/conversion/constant.hpp @@ -24,6 +24,13 @@ struct Converter<jni::jobject*, bool> { }; template <> +struct Converter<jni::jboolean, bool> { + Result<jni::jboolean> operator()(jni::JNIEnv&, const bool& value) const { + return {(jni::jboolean) value}; + } +}; + +template <> struct Converter<jni::jobject*, float> { Result<jni::jobject*> operator()(jni::JNIEnv& env, const float& value) const { static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/lang/Float")).release(); @@ -33,6 +40,45 @@ struct Converter<jni::jobject*, float> { }; template <> +struct Converter<jni::jfloat, float> { + Result<jni::jfloat> operator()(jni::JNIEnv&, const float& value) const { + return {(jni::jfloat) value}; + } +}; + + +template <> +struct Converter<jni::jobject*, double> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const double& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/lang/Double")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(D)V"); + return {&jni::NewObject(env, *javaClass, *constructor, (jfloat) value)}; + } +}; + +template <> +struct Converter<jni::jdouble, float> { + Result<jni::jdouble> operator()(jni::JNIEnv&, const double& value) const { + return {(jni::jdouble) value}; + } +}; + +/** + * All integrals. java is limited to 64 bit signed, so... + * TODO: use BigDecimal for > 64 / unsigned? + */ +template<typename T> +struct Converter<jni::jobject*, T, typename std::enable_if<std::is_integral<T>::value>::type> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const T& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "java/lang/Long")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(J)V"); + return {&jni::NewObject(env, *javaClass, *constructor, (jlong) value)}; + } +}; + +//TODO: convert integral types to primitive jni types + +template <> struct Converter<jni::jobject*, std::string> { Result<jni::jobject*> operator()(jni::JNIEnv& env, const std::string& value) const { return {jni::Make<jni::String>(env, value).Get()}; @@ -40,6 +86,13 @@ struct Converter<jni::jobject*, std::string> { }; template <> +struct Converter<jni::jstring*, std::string> { + Result<jni::jstring*> operator()(jni::JNIEnv& env, const std::string& value) const { + return {jni::Make<jni::String>(env, value).Get()}; + } +}; + +template <> struct Converter<jni::jobject*, Color> { Result<jni::jobject*> operator()(jni::JNIEnv& env, const Color& value) const { std::stringstream sstream; @@ -90,6 +143,15 @@ struct Converter<jni::jobject*, std::vector<float>> { } }; +// Java -> C++ + +template <> +struct Converter<std::string, jni::String> { + Result<std::string> operator()(jni::JNIEnv& env, const jni::String& value) const { + return { jni::Make<std::string>(env, value) }; + } +}; + } // namespace conversion } // namespace style } // namespace mbgl diff --git a/platform/android/src/conversion/conversion.hpp b/platform/android/src/conversion/conversion.hpp index ea8a31bcf2..1277f3f67e 100644 --- a/platform/android/src/conversion/conversion.hpp +++ b/platform/android/src/conversion/conversion.hpp @@ -37,7 +37,7 @@ public: } }; -template <class T, class V> +template <class T, class V, class Enable = void> struct Converter; template <class T, typename V, class...Args> diff --git a/platform/android/src/geometry/conversion/feature.hpp b/platform/android/src/geometry/conversion/feature.hpp new file mode 100644 index 0000000000..857b76da50 --- /dev/null +++ b/platform/android/src/geometry/conversion/feature.hpp @@ -0,0 +1,209 @@ +#pragma once + +#include "../../conversion/constant.hpp" +#include "../../conversion/conversion.hpp" +#include "geometry.hpp" + +#include <mbgl/util/feature.hpp> +#include <mapbox/variant.hpp> +#include <mapbox/geometry.hpp> + +#include <jni/jni.hpp> +#include "../../jni/local_object.hpp" + +#include <string> +#include <array> +#include <vector> +#include <sstream> + +#include <mbgl/platform/log.hpp> + +namespace mbgl { +namespace android { +namespace conversion { + +/** + * Turn feature identifier into std::string + */ +class FeatureIdVisitor { +public: + + template<class T> + std::string operator()(const T& i) const { + return std::to_string(i); + } + + std::string operator()(const std::string& i) const { + return i; + } + + std::string operator()(const std::nullptr_t&) const { + return ""; + } + +}; + +/** + * Turn properties into Java GSON JsonObject's + */ +class PropertyValueEvaluator { +public: + jni::JNIEnv& env; + + /** + * null + */ + jni::jobject* operator()(const mapbox::geometry::null_value_t &) const { + return (jni::jobject*) nullptr; + } + + /** + * Boolean primitive + */ + jni::jobject* operator()(const bool& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/google/gson/JsonPrimitive")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(Z)V"); + + //Create JsonPrimitive + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, *convert<jni::jobject*, bool>(env, value)); + jni::jobject* object = &jni::NewObject(env, *javaClass, *constructor, *converted); + + return object; + } + + /** + * String primitive + */ + jni::jobject* operator()(const std::string& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/google/gson/JsonPrimitive")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(Ljava/lang/String;)V"); + + //Create JsonPrimitive + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, *convert<jni::jobject*, std::string>(env, value)); + jni::jobject* object = &jni::NewObject(env, *javaClass, *constructor, converted.get()); + + return object; + } + + /** + * Number primitives + */ + template <class Number> + jni::jobject* operator()(const Number& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/google/gson/JsonPrimitive")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "(Ljava/lang/Number;)V"); + + //Create JsonPrimitive + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, *convert<jni::jobject*, Number>(env, value)); + jni::jobject* object = &jni::NewObject(env, *javaClass, *constructor, converted.get()); + + return object; + } + + + /** + * Json Array + */ + jni::jobject* operator()(const std::vector<mbgl::Value> &values) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/google/gson/JsonArray")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "()V");; + static jni::jmethodID* add = &jni::GetMethodID(env, *javaClass, "add", "(Lcom/google/gson/JsonElement;)V"); + + //Create json array + jni::jobject* jarray = &jni::NewObject(env, *javaClass, *constructor); + + //Add values + for (const auto &v : values) { + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, mbgl::Value::visit(v, *this)); + jni::CallMethod<void>(env, jarray, *add, converted.get()); + } + + return jarray; + } + + /** + * Json Object + */ + jni::jobject* operator()(const std::unordered_map<std::string, mbgl::Value> &value) const { + //TODO: clean up duplication here + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/google/gson/JsonObject")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "()V");; + static jni::jmethodID* add = &jni::GetMethodID(env, *javaClass, "add", "(Ljava/lang/String;Lcom/google/gson/JsonElement;)V"); + + //Create json object + jni::jobject* jsonObject = &jni::NewObject(env, *javaClass, *constructor); + + //Add items + for (auto &item : value) { + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, mbgl::Value::visit(item.second, *this)); + jni::LocalObject<jni::jobject> key = jni::NewLocalObject(env, *convert<jni::jobject*, std::string>(env, item.first)); + jni::CallMethod<void>(env, jsonObject, *add, key.get(), converted.get()); + } + + return jsonObject; + } +}; + +template <> +struct Converter<jni::jobject*, std::unordered_map<std::string, mbgl::Value>> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const std::unordered_map<std::string, mbgl::Value>& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/google/gson/JsonObject")).release(); + static jni::jmethodID* constructor = &jni::GetMethodID(env, *javaClass, "<init>", "()V");; + static jni::jmethodID* add = &jni::GetMethodID(env, *javaClass, "add", "(Ljava/lang/String;Lcom/google/gson/JsonElement;)V"); + + //Create json object + jni::jobject* jsonObject = &jni::NewObject(env, *javaClass, *constructor); + + //Add items + PropertyValueEvaluator evaluator {env}; + for (auto &item : value) { + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, mbgl::Value::visit(item.second, evaluator)); + jni::LocalObject<jni::jobject> key = jni::NewLocalObject(env, *convert<jni::jobject*, std::string>(env, item.first)); + jni::CallMethod<void>(env, jsonObject, *add, key.get(), converted.get()); + } + + return {jsonObject}; + } +}; + + +template <> +struct Converter<jni::jobject*, mbgl::Feature> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const mbgl::Feature& value) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Feature")).release(); + static jni::jmethodID* fromGeometry = &jni::GetStaticMethodID(env, *javaClass, "fromGeometry", "(Lcom/mapbox/services/commons/geojson/Geometry;Lcom/google/gson/JsonObject;Ljava/lang/String;)Lcom/mapbox/services/commons/geojson/Feature;"); + + //Convert Id + FeatureIdVisitor idEvaluator; + std::string id = (value.id) ? mapbox::geometry::identifier::visit(value.id.value(), idEvaluator) : ""; + jni::LocalObject<jni::jobject> jid = jni::NewLocalObject(env, *convert<jni::jobject*>(env, id)); + + //Convert properties + jni::LocalObject<jni::jobject> properties = jni::NewLocalObject(env, *convert<jni::jobject*>(env, value.properties)); + + //Convert geometry + jni::LocalObject<jni::jobject> geometry = jni::NewLocalObject(env, *convert<jni::jobject*>(env, value.geometry)); + + //Create feature + return {reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometry, geometry.get(), properties.get(), jid.get()))}; + } +}; + +template <> +struct Converter<jni::jarray<jni::jobject>*, std::vector<mbgl::Feature>> { + Result<jni::jarray<jni::jobject>*> operator()(jni::JNIEnv& env, const std::vector<mbgl::Feature>& value) const { + static jni::jclass* featureClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Feature")).release(); + jni::jarray<jni::jobject>& jarray = jni::NewObjectArray(env, value.size(), *featureClass); + + for(size_t i = 0; i < value.size(); i = i + 1) { + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, *convert<jni::jobject*, mbgl::Feature>(env, value.at(i))); + jni::SetObjectArrayElement(env, jarray, i, converted.get()); + } + + return {&jarray}; + } +}; + +} // namespace conversion +} // namespace android +} // namespace mbgl diff --git a/platform/android/src/geometry/conversion/geometry.hpp b/platform/android/src/geometry/conversion/geometry.hpp new file mode 100644 index 0000000000..385ba9034e --- /dev/null +++ b/platform/android/src/geometry/conversion/geometry.hpp @@ -0,0 +1,184 @@ +#pragma once + +#include "../../conversion/constant.hpp" +#include "../../conversion/collection.hpp" + +#include <mapbox/geometry.hpp> +#include <jni/jni.hpp> +#include "../../jni/local_object.hpp" + +namespace mbgl { +namespace android { +namespace conversion { + +/** + * Turn mapbox::geometry type into Java GeoJson Geometries + */ +template <typename T> +class GeometryEvaluator { +public: + + jni::JNIEnv& env; + + /** + * Point (double[]) + */ + jni::jobject* operator()(const mapbox::geometry::point<T> &geometry) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Point")).release(); + static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([D)Lcom/mapbox/services/commons/geojson/Point;"); + + //Create Point + jni::LocalObject<jni::jarray<jni::jdouble>> position = jni::NewLocalObject(env, toGeoJsonPosition(env, geometry.x, geometry.y)); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, position.get())); + } + + /** + * LineString (double[][]) + */ + jni::jobject* operator()(const mapbox::geometry::line_string<T> &geometry) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/LineString")).release(); + static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[D)Lcom/mapbox/services/commons/geojson/LineString;"); + + //Create + jni::LocalObject<jni::jarray<jni::jobject>> coordinates = jni::NewLocalObject(env, toGeoJsonCoordinates(env, geometry)); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, coordinates.get())); + } + + /** + * MultiPoint (double[][]) + */ + jni::jobject* operator()(const mapbox::geometry::multi_point<T> &geometry) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/MultiPoint")).release(); + static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[D)Lcom/mapbox/services/commons/geojson/MultiPoint;"); + + //Create + jni::LocalObject<jni::jarray<jni::jobject>> coordinates = jni::NewLocalObject(env, toGeoJsonCoordinates(env, geometry)); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, coordinates.get())); + } + + /** + * Polygon (double[][][]) + */ + jni::jobject* operator()(const mapbox::geometry::polygon<T> &geometry) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Polygon")).release(); + static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[[D)Lcom/mapbox/services/commons/geojson/Polygon;"); + + //Create + jni::LocalObject<jni::jarray<jni::jobject>> shape = jni::NewLocalObject(env, toShape<>(env, geometry)); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, shape.get())); + } + + /** + * MultiLineString (double[][][]) + */ + jni::jobject* operator()(const mapbox::geometry::multi_line_string<T> &geometry) const { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/MultiLineString")).release(); + static jni::jmethodID* fromCoordinates = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[[D)Lcom/mapbox/services/commons/geojson/MultiLineString;"); + + //Create + jni::LocalObject<jni::jarray<jni::jobject>> shape = jni::NewLocalObject(env, toShape<>(env, geometry)); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromCoordinates, shape.get())); + } + + /** + * MultiPolygon (double[][][][]) -> [[[D + Object array == [[[[D + */ + jni::jobject* operator()(const mapbox::geometry::multi_polygon<T> &geometry) const { + static jni::jclass* listClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[[[D")).release(); + jni::LocalObject<jni::jarray<jni::jobject>> jarray = jni::NewLocalObject(env, &jni::NewObjectArray(env, geometry.size(), *listClass)); + + for(size_t i = 0; i < geometry.size(); i = i + 1) { + jni::LocalObject<jni::jarray<jni::jobject>> shape = jni::NewLocalObject(env, toShape<>(env, geometry.at(i))); + jni::SetObjectArrayElement(env, *jarray, i, shape.get()); + } + + //Create the MultiPolygon + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/MultiPolygon")).release(); + static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromCoordinates", "([[[[D)Lcom/mapbox/services/commons/geojson/MultiPolygon;"); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, jarray.get())); + } + + /** + * GeometryCollection + */ + jni::jobject* operator()(const mapbox::geometry::geometry_collection<T> &collection) const { + static jni::jclass* geometryClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/Geometry")).release(); + jni::LocalObject<jni::jarray<jni::jobject>> jarray = jni::NewLocalObject(env, &jni::NewObjectArray(env, collection.size(), *geometryClass)); + + for(size_t i = 0; i < collection.size(); i = i + 1) { + auto& geometry = collection.at(i); + jni::LocalObject<jni::jobject> converted = jni::NewLocalObject(env, mapbox::geometry::geometry<T>::visit(geometry, *this)); + jni::SetObjectArrayElement(env, *jarray, i, converted.get()); + } + + //Turn into array list and create the GeometryCollection + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "com/mapbox/services/commons/geojson/GeometryCollection")).release(); + static jni::jmethodID* fromGeometries = &jni::GetStaticMethodID(env, *javaClass, "fromGeometries", "(Ljava/util/List;)Lcom/mapbox/services/commons/geojson/GeometryCollection;"); + + jni::LocalObject<jni::jobject> list = jni::NewLocalObject(env, toArrayList<>(env, *jarray)); + return reinterpret_cast<jni::jobject*>(jni::CallStaticMethod<jni::jobject*>(env, *javaClass, *fromGeometries, list.get())); + } + +private: + + /** + * x, y -> jarray<jdouble> ([x,y]) + */ + static jni::jarray<jni::jdouble>* toGeoJsonPosition(JNIEnv& env, double x, double y) { + jni::jarray<jni::jdouble>& jarray = jni::NewArray<jni::jdouble>(env, 2); + jni::jdouble array[] = {x, y}; + jni::SetArrayRegion(env, jarray, 0, 2, array); + return &jarray; + } + + /** + * vector<point<T>> -> jarray<jobject> (double[][]) -> [D + Object array == [[D + */ + static jni::jarray<jni::jobject>* toGeoJsonCoordinates(JNIEnv& env, std::vector<mapbox::geometry::point<T>> points) { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[D")).release(); + jni::jarray<jni::jobject>& jarray = jni::NewObjectArray(env, points.size(), *javaClass); + + for(size_t i = 0; i < points.size(); i = i + 1) { + mapbox::geometry::point<T> point = points.at(i); + jni::LocalObject<jni::jarray<jni::jdouble>> position = jni::NewLocalObject(env, toGeoJsonPosition(env, point.x, point.y)); + jni::SetObjectArrayElement(env, jarray, i, position.get()); + } + + return &jarray; + } + + /** + * polygon<T> + * multi_line_string<T> + * -> jarray<jobject> (double[][][]) -> [[D + Object array == [[[D + */ + template <class SHAPE> + static jni::jarray<jni::jobject>* toShape(JNIEnv& env, SHAPE value) { + static jni::jclass* javaClass = jni::NewGlobalRef(env, &jni::FindClass(env, "[[D")).release(); + jni::jarray<jni::jobject>& jarray = jni::NewObjectArray(env, value.size(), *javaClass); + + for(size_t i = 0; i < value.size(); i = i + 1) { + jni::LocalObject<jni::jarray<jni::jobject>> coordinates = jni::NewLocalObject(env, toGeoJsonCoordinates(env, value.at(i))); + jni::SetObjectArrayElement(env, jarray, i, coordinates.get()); + } + + return &jarray; + } +}; + +/** + * mapbox::geometry::geometry<T> -> Java GeoJson Geometry<> + */ +template <class T> +struct Converter<jni::jobject*, mapbox::geometry::geometry<T>> { + Result<jni::jobject*> operator()(jni::JNIEnv& env, const mapbox::geometry::geometry<T>& value) const { + GeometryEvaluator<double> evaluator { env } ; + jni::jobject* converted = mapbox::geometry::geometry<double>::visit(value, evaluator); + return {converted}; + } +}; + + +} +} +} diff --git a/platform/android/src/jni.cpp b/platform/android/src/jni.cpp index 89dbfe1cc2..acaa5e8b42 100755 --- a/platform/android/src/jni.cpp +++ b/platform/android/src/jni.cpp @@ -14,6 +14,10 @@ #include "style/layers/layers.hpp" #include "style/sources/sources.hpp" +#include "conversion/conversion.hpp" +#include "conversion/collection.hpp" +#include "geometry/conversion/feature.hpp" + #include <mbgl/map/map.hpp> #include <mbgl/map/camera.hpp> #include <mbgl/annotation/annotation.hpp> @@ -23,9 +27,12 @@ #include <mbgl/platform/log.hpp> #include <mbgl/storage/network_status.hpp> #include <mbgl/util/exception.hpp> +#include <mbgl/util/optional.hpp> #include <mbgl/util/string.hpp> #include <mbgl/util/run_loop.hpp> +#include <mapbox/geometry.hpp> + #include <jni/jni.hpp> #pragma clang diagnostic ignored "-Wunused-parameter" @@ -929,6 +936,40 @@ void nativeSetVisibleCoordinateBounds(JNIEnv *env, jni::jobject* obj, jlong nati nativeMapView->getMap().easeTo(cameraOptions, animationOptions); } +jni::jarray<jni::jobject>* nativeQueryRenderedFeaturesForPoint(JNIEnv *env, jni::jobject* obj, jlong nativeMapViewPtr, jni::jfloat x, jni::jfloat y, jni::jarray<jni::jobject>* layerIds) { + using namespace mbgl::android::conversion; + using namespace mapbox::geometry; + + mbgl::Log::Debug(mbgl::Event::JNI, "nativeQueryRenderedFeatures for Point"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr); + + mbgl::optional<std::vector<std::string>> layers; + if (layerIds != nullptr && jni::GetArrayLength(*env, *layerIds) > 0) { + layers = toVector(*env, *layerIds); + } + point<double> point = {x, y}; + + return *convert<jni::jarray<jni::jobject>*, std::vector<mbgl::Feature>>(*env, nativeMapView->getMap().queryRenderedFeatures(point, layers)); +} + +jni::jarray<jni::jobject>* nativeQueryRenderedFeaturesForBox(JNIEnv *env, jni::jobject* obj, jlong nativeMapViewPtr, jni::jfloat left, jni::jfloat top, jni::jfloat right, jni::jfloat bottom, jni::jarray<jni::jobject>* layerIds) { + using namespace mbgl::android::conversion; + using namespace mapbox::geometry; + + mbgl::Log::Debug(mbgl::Event::JNI, "nativeQueryRenderedFeatures for Box %.2f %.2f %.2f %.2f", left, top, right, bottom); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast<NativeMapView *>(nativeMapViewPtr); + + mbgl::optional<std::vector<std::string>> layers; + if (layerIds != nullptr && jni::GetArrayLength(*env, *layerIds) > 0) { + layers = toVector(*env, *layerIds); + } + box<double> box = { point<double>{ left, top}, point<double>{ right, bottom } }; + + return *convert<jni::jarray<jni::jobject>*, std::vector<mbgl::Feature>>(*env, nativeMapView->getMap().queryRenderedFeatures(box, layers)); +} + void nativeOnLowMemory(JNIEnv *env, jni::jobject* obj, jlong nativeMapViewPtr) { mbgl::Log::Debug(mbgl::Event::JNI, "nativeOnLowMemory"); assert(nativeMapViewPtr != 0); @@ -1800,7 +1841,9 @@ extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) { MAKE_NATIVE_METHOD(nativeAddSource, "(JLjava/lang/String;Lcom/mapbox/mapboxsdk/style/sources/Source;)V"), MAKE_NATIVE_METHOD(nativeRemoveSource, "(JLjava/lang/String;)V"), MAKE_NATIVE_METHOD(nativeSetContentPadding, "(JDDDD)V"), - MAKE_NATIVE_METHOD(nativeScheduleTakeSnapshot, "(J)V") + MAKE_NATIVE_METHOD(nativeScheduleTakeSnapshot, "(J)V"), + MAKE_NATIVE_METHOD(nativeQueryRenderedFeaturesForPoint, "(JFF[Ljava/lang/String;)[Lcom/mapbox/services/commons/geojson/Feature;"), + MAKE_NATIVE_METHOD(nativeQueryRenderedFeaturesForBox, "(JFFFF[Ljava/lang/String;)[Lcom/mapbox/services/commons/geojson/Feature;") ); // Offline begin diff --git a/platform/android/src/jni/local_object.hpp b/platform/android/src/jni/local_object.hpp new file mode 100644 index 0000000000..00fc4a1933 --- /dev/null +++ b/platform/android/src/jni/local_object.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include <jni/jni.hpp> + +namespace jni { + + class LocalRefDeleter { + private: + JNIEnv* env = nullptr; + + public: + LocalRefDeleter() = default; + LocalRefDeleter(JNIEnv& e) : env(&e) {} + + void operator()(jobject* object) const { + if (object) { + assert(env); + DeleteLocalRef(*env, object); + } + } + }; + + template < class T > + using LocalObject = std::unique_ptr< T, LocalRefDeleter >; + + /** + * Use a LocalObject to discard of local references as soon as possible + */ + template < class T > + LocalObject<T> NewLocalObject(JNIEnv& env, T* t) { + return LocalObject<T>(t, LocalRefDeleter(env)); + } +} |