シンボルレイヤに情報ウィンドウ
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:mapbox="http://schemas.android.com/apk/res-auto"android:layout_width="match_parent"android:layout_height="match_parent"> <com.mapbox.mapboxsdk.maps.MapViewandroid:id="@+id/mapView"android:layout_width="match_parent"android:layout_height="match_parent"mapbox:mapbox_cameraTargetLat="42.149683"mapbox:mapbox_cameraTargetLng="-119.155770"mapbox:mapbox_cameraZoom="3.853171"/> </androidx.constraintlayout.widget.ConstraintLayout>
InfoWindowSymbolLayerActivity.java
package com.mapbox.mapboxandroiddemo.examples.dds; import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.PointF;import android.os.AsyncTask;import android.os.Bundle;import androidx.annotation.NonNull;import androidx.appcompat.app.AppCompatActivity;import android.view.LayoutInflater;import android.view.View;import android.widget.TextView;import android.widget.Toast; import com.mapbox.geojson.Feature;import com.mapbox.geojson.FeatureCollection;import com.mapbox.mapboxandroiddemo.R;import com.mapbox.mapboxsdk.Mapbox;import com.mapbox.mapboxsdk.annotations.BubbleLayout;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.maps.Style;import com.mapbox.mapboxsdk.style.layers.SymbolLayer;import com.mapbox.mapboxsdk.style.sources.GeoJsonSource; import java.io.InputStream;import java.lang.ref.WeakReference;import java.nio.charset.Charset;import java.util.HashMap;import java.util.List; import static com.mapbox.mapboxsdk.style.expressions.Expression.eq;import static com.mapbox.mapboxsdk.style.expressions.Expression.get;import static com.mapbox.mapboxsdk.style.expressions.Expression.literal;import static com.mapbox.mapboxsdk.style.layers.Property.ICON_ANCHOR_BOTTOM;import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAllowOverlap;import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconAnchor;import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconImage;import static com.mapbox.mapboxsdk.style.layers.PropertyFactory.iconOffset; /*** Use a SymbolLayer to show a BubbleLayout above a SymbolLayer icon. This is a more performant* way to show the BubbleLayout that appears when using the MapboxMap.addMarker() method.*/public class InfoWindowSymbolLayerActivity extends AppCompatActivity implementsOnMapReadyCallback, MapboxMap.OnMapClickListener { private static final String GEOJSON_SOURCE_ID = "GEOJSON_SOURCE_ID";private static final String MARKER_IMAGE_ID = "MARKER_IMAGE_ID";private static final String MARKER_LAYER_ID = "MARKER_LAYER_ID";private static final String CALLOUT_LAYER_ID = "CALLOUT_LAYER_ID";private static final String PROPERTY_SELECTED = "selected";private static final String PROPERTY_NAME = "name";private static final String PROPERTY_CAPITAL = "capital";private MapView mapView;private MapboxMap mapboxMap;private GeoJsonSource source;private FeatureCollection featureCollection; @Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState); // Mapbox access token is configured here. This needs to be called either in your application// object or in the same activity which contains the mapview.Mapbox.getInstance(this, getString(R.string.access_token)); // This contains the MapView in XML and needs to be called after the access token is configured.setContentView(R.layout.activity_info_window_symbol_layer); // Initialize the map viewmapView = findViewById(R.id.mapView);mapView.onCreate(savedInstanceState);mapView.getMapAsync(this);} @Overridepublic void onMapReady(@NonNull final MapboxMap mapboxMap) {this.mapboxMap = mapboxMap;mapboxMap.setStyle(Style.MAPBOX_STREETS, new Style.OnStyleLoaded() {@Overridepublic void onStyleLoaded(@NonNull Style style) {new LoadGeoJsonDataTask(InfoWindowSymbolLayerActivity.this).execute();mapboxMap.addOnMapClickListener(InfoWindowSymbolLayerActivity.this);}});} @Overridepublic boolean onMapClick(@NonNull LatLng point) {return handleClickIcon(mapboxMap.getProjection().toScreenLocation(point));} /*** Sets up all of the sources and layers needed for this example** @param collection the FeatureCollection to set equal to the globally-declared FeatureCollection*/public void setUpData(final FeatureCollection collection) {featureCollection = collection;if (mapboxMap != null) {mapboxMap.getStyle(style -> {setupSource(style);setUpImage(style);setUpMarkerLayer(style);setUpInfoWindowLayer(style);});}} /*** Adds the GeoJSON source to the map*/private void setupSource(@NonNull Style loadedStyle) {source = new GeoJsonSource(GEOJSON_SOURCE_ID, featureCollection);loadedStyle.addSource(source);} /*** Adds the marker image to the map for use as a SymbolLayer icon*/private void setUpImage(@NonNull Style loadedStyle) {loadedStyle.addImage(MARKER_IMAGE_ID, BitmapFactory.decodeResource(this.getResources(), R.drawable.red_marker));} /*** Updates the display of data on the map after the FeatureCollection has been modified*/private void refreshSource() {if (source != null && featureCollection != null) {source.setGeoJson(featureCollection);}} /*** Setup a layer with maki icons, eg. west coast city.*/private void setUpMarkerLayer(@NonNull Style loadedStyle) {loadedStyle.addLayer(new SymbolLayer(MARKER_LAYER_ID, GEOJSON_SOURCE_ID).withProperties(iconImage(MARKER_IMAGE_ID),iconAllowOverlap(true),iconOffset(new Float[] {0f, -8f})));} /*** Setup a layer with Android SDK call-outs* <p>* name of the feature is used as key for the iconImage* </p>*/private void setUpInfoWindowLayer(@NonNull Style loadedStyle) {loadedStyle.addLayer(new SymbolLayer(CALLOUT_LAYER_ID, GEOJSON_SOURCE_ID).withProperties(/* show image with id title based on the value of the name feature property */iconImage("{name}"), /* set anchor of icon to bottom-left */iconAnchor(ICON_ANCHOR_BOTTOM), /* all info window and marker image to appear at the same time*/iconAllowOverlap(true), /* offset the info window to be above the marker */iconOffset(new Float[] {-2f, -28f}))/* add a filter to show only when selected feature property is true */.withFilter(eq((get(PROPERTY_SELECTED)), literal(true))));} /*** This method handles click events for SymbolLayer symbols.* <p>* When a SymbolLayer icon is clicked, we moved that feature to the selected state.* </p>** @param screenPoint the point on screen clicked*/private boolean handleClickIcon(PointF screenPoint) {List<Feature> features = mapboxMap.queryRenderedFeatures(screenPoint, MARKER_LAYER_ID);if (!features.isEmpty()) {String name = features.get(0).getStringProperty(PROPERTY_NAME);List<Feature> featureList = featureCollection.features();if (featureList != null) {for (int i = 0; i < featureList.size(); i++) {if (featureList.get(i).getStringProperty(PROPERTY_NAME).equals(name)) {if (featureSelectStatus(i)) {setFeatureSelectState(featureList.get(i), false);} else {setSelected(i);}}}}return true;} else {return false;}} /*** Set a feature selected state.** @param index the index of selected feature*/private void setSelected(int index) {if (featureCollection.features() != null) {Feature feature = featureCollection.features().get(index);setFeatureSelectState(feature, true);refreshSource();}} /*** Selects the state of a feature** @param feature the feature to be selected.*/private void setFeatureSelectState(Feature feature, boolean selectedState) {if (feature.properties() != null) {feature.properties().addProperty(PROPERTY_SELECTED, selectedState);refreshSource();}} /*** Checks whether a Feature's boolean "selected" property is true or false** @param index the specific Feature's index position in the FeatureCollection's list of Features.* @return true if "selected" is true. False if the boolean property is false.*/private boolean featureSelectStatus(int index) {if (featureCollection == null) {return false;}return featureCollection.features().get(index).getBooleanProperty(PROPERTY_SELECTED);} /*** Invoked when the bitmaps have been generated from a view.*/public void setImageGenResults(HashMap<String, Bitmap> imageMap) {if (mapboxMap != null) {mapboxMap.getStyle(style -> {// calling addImages is faster as separate addImage calls for each bitmap.style.addImages(imageMap);});}} /*** AsyncTask to load data from the assets folder.*/private static class LoadGeoJsonDataTask extends AsyncTask<Void, Void, FeatureCollection> { private final WeakReference<InfoWindowSymbolLayerActivity> activityRef; LoadGeoJsonDataTask(InfoWindowSymbolLayerActivity activity) {this.activityRef = new WeakReference<>(activity);} @Overrideprotected FeatureCollection doInBackground(Void... params) {InfoWindowSymbolLayerActivity activity = activityRef.get(); if (activity == null) {return null;} String geoJson = loadGeoJsonFromAsset(activity, "us_west_coast.geojson");return FeatureCollection.fromJson(geoJson);} @Overrideprotected void onPostExecute(FeatureCollection featureCollection) {super.onPostExecute(featureCollection);InfoWindowSymbolLayerActivity activity = activityRef.get();if (featureCollection == null || activity == null) {return;} // This example runs on the premise that each GeoJSON Feature has a "selected" property,// with a boolean value. If your data's Features don't have this boolean property,// add it to the FeatureCollection 's features with the following code:for (Feature singleFeature : featureCollection.features()) {singleFeature.addBooleanProperty(PROPERTY_SELECTED, false);} activity.setUpData(featureCollection);new GenerateViewIconTask(activity).execute(featureCollection);} static String loadGeoJsonFromAsset(Context context, String filename) {try {// Load GeoJSON file from local asset folderInputStream is = context.getAssets().open(filename);int size = is.available();byte[] buffer = new byte[size];is.read(buffer);is.close();return new String(buffer, Charset.forName("UTF-8"));} catch (Exception exception) {throw new RuntimeException(exception);}}} /*** AsyncTask to generate Bitmap from Views to be used as iconImage in a SymbolLayer.* <p>* Call be optionally be called to update the underlying data source after execution.* </p>* <p>* Generating Views on background thread since we are not going to be adding them to the view hierarchy.* </p>*/private static class GenerateViewIconTask extends AsyncTask<FeatureCollection, Void, HashMap<String, Bitmap>> { private final HashMap<String, View> viewMap = new HashMap<>();private final WeakReference<InfoWindowSymbolLayerActivity> activityRef;private final boolean refreshSource; GenerateViewIconTask(InfoWindowSymbolLayerActivity activity, boolean refreshSource) {this.activityRef = new WeakReference<>(activity);this.refreshSource = refreshSource;} GenerateViewIconTask(InfoWindowSymbolLayerActivity activity) {this(activity, false);} @SuppressWarnings("WrongThread")@Overrideprotected HashMap<String, Bitmap> doInBackground(FeatureCollection... params) {InfoWindowSymbolLayerActivity activity = activityRef.get();if (activity != null) {HashMap<String, Bitmap> imagesMap = new HashMap<>();LayoutInflater inflater = LayoutInflater.from(activity); FeatureCollection featureCollection = params[0]; for (Feature feature : featureCollection.features()) { BubbleLayout bubbleLayout = (BubbleLayout)inflater.inflate(R.layout.symbol_layer_info_window_layout_callout, null); String name = feature.getStringProperty(PROPERTY_NAME);TextView titleTextView = bubbleLayout.findViewById(R.id.info_window_title);titleTextView.setText(name); String style = feature.getStringProperty(PROPERTY_CAPITAL);TextView descriptionTextView = bubbleLayout.findViewById(R.id.info_window_description);descriptionTextView.setText(String.format(activity.getString(R.string.capital), style)); int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);bubbleLayout.measure(measureSpec, measureSpec); float measuredWidth = bubbleLayout.getMeasuredWidth(); bubbleLayout.setArrowPosition(measuredWidth / 2 - 5); Bitmap bitmap = SymbolGenerator.generate(bubbleLayout);imagesMap.put(name, bitmap);viewMap.put(name, bubbleLayout);} return imagesMap;} else {return null;}} @Overrideprotected void onPostExecute(HashMap<String, Bitmap> bitmapHashMap) {super.onPostExecute(bitmapHashMap);InfoWindowSymbolLayerActivity activity = activityRef.get();if (activity != null && bitmapHashMap != null) {activity.setImageGenResults(bitmapHashMap);if (refreshSource) {activity.refreshSource();}}Toast.makeText(activity, R.string.tap_on_marker_instruction, Toast.LENGTH_SHORT).show();}} /*** Utility class to generate Bitmaps for Symbol.*/private static class SymbolGenerator { /*** Generate a Bitmap from an Android SDK View.** @param view the View to be drawn to a Bitmap* @return the generated bitmap*/static Bitmap generate(@NonNull View view) {int measureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);view.measure(measureSpec, measureSpec); int measuredWidth = view.getMeasuredWidth();int measuredHeight = view.getMeasuredHeight(); view.layout(0, 0, measuredWidth, measuredHeight);Bitmap bitmap = Bitmap.createBitmap(measuredWidth, measuredHeight, Bitmap.Config.ARGB_8888);bitmap.eraseColor(Color.TRANSPARENT);Canvas canvas = new Canvas(bitmap);view.draw(canvas);return bitmap;}} @Overrideprotected void onStart() {super.onStart();mapView.onStart();} @Overridepublic void onResume() {super.onResume();mapView.onResume();} @Overridepublic void onPause() {super.onPause();mapView.onPause();} @Overrideprotected void onStop() {super.onStop();mapView.onStop();} @Overrideprotected void onSaveInstanceState(Bundle outState) {super.onSaveInstanceState(outState);mapView.onSaveInstanceState(outState);} @Overridepublic void onLowMemory() {super.onLowMemory();mapView.onLowMemory();} @Overrideprotected void onDestroy() {super.onDestroy();if (mapboxMap != null) {mapboxMap.removeOnMapClickListener(this);}mapView.onDestroy();}}