[android] #5869 - visible feature querying

This commit is contained in:
Ivo van Dongen
2016-08-04 14:45:50 -04:00
parent dcd9e1016c
commit 4e211548d5
17 changed files with 1340 additions and 54 deletions

View File

@@ -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 {

View File

@@ -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
//

View File

@@ -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);
}

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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
}
}
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;
}
}
}
}

View File

@@ -23,6 +23,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 {
@@ -32,6 +39,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 {
@@ -39,6 +85,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 {
@@ -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

View File

@@ -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>

View File

@@ -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

View File

@@ -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};
}
};
}
}
}

View File

@@ -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

View File

@@ -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));
}
}