Android地图—— Mapbox 10.3.0 聚类标签实现

Mapbox的初始化配置请见这个文章:Android地图—— Mapbox 10.3.0 接入与基础使用

我们知道,mapbox显示地图是利用多个 layers 组装而成,比如water layer等。mapbox支持我们添加更多的layer用于自定义用途,这里我们用layer来展示大量的标记。
以下例子实现了新建layer展示标记,并实现标记随地图缩放聚合、以及用户点击交互的功能。
具体的实现我参考了这个博主的代码:MapBoxMap 之 SymbolLayer实现标记聚合,在其基础上进行了10.3.0版本的适配,以及添加了点击标记的交互,这里的实现仅为个人尝试,可能有更好的实现方法。(注:由于官网已有kotlin的实现示例,这里将以java作为示例编写语言)
效果图先行:
在这里插入图片描述
以下文章主要为原理解释,非完整代码展示,建议搭配本文示例程序的代码食用:

1. 基础知识概念

首先明确以下几个概念:

Sources:

地图源,包含要添加到地图中的所有元素描述,以及他们的地理坐标信息。
主要参数:
id: sources的独特标识;
type: 当前sources的类别,类别决定了source中的元素是怎么样的。type的不同也决定了source应该包含什么其他参数;
关于source 不同type包含的参数说明:
https://docs.mapbox.com/mapbox-gl-js/style-spec/sources/

Layers:

样式层,用于决定Sources中的元素在地图上的显示样式。
主要参数:
id: layers的独特标识;
type: layers的类别,不同的type有不同的样式展现形式。
source: 该layer是哪个源中的。
filter: Expression表达式,根据各种条件判断当前layer是否展示。
Layers的更多参数说明:
https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/

一个sources可以对应一个或多个layers,这取决于你想以多少种不同形式展示你的数据。

关于source和layers的官方解释:
https://docs.mapbox.com/android/maps/guides/styles/work-with-layers/

要实现一个具有聚合标记物功能,且标记物可自定义样式的图层,我们应该选择sources类型为geojson,layers类型我们则选择样式最丰富的symbol。

2. source初始化并添加到style中:

定义source-id:

private static final String CLUSTER_SOURCE_ID = "cluster-source-id";

初始化例子1:
例子1为在使用 mMapboxMap.loadStyleUri() 方法拿到加载完毕的 mStyle 后,再去添加 source 的方法。
以下代码我们在常规初始化的基础上添加了对 参数 clusterProperties 的设置(该参数可以自定义聚合标记物所包含的特征值,用于一些个性化设置),我们定义了一个 first_item_id 属性,保存了在聚合标记物对象集合中各标记物之间最小的id值,这是为了方便我们后面设置 聚合对象的显示 做准备。

        //添加地图源 type:GeoJson
        {
            JSONObject clusterProperties = new JSONObject();
            try {
                clusterProperties.put("first_item_id",
                        new JSONArray(Expression.min(Expression.toNumber(Expression.get("id"))).toJson()));
            } catch (JSONException e) {
                e.printStackTrace();
            }
            Log.d(TAG, clusterProperties.toString());
            JSONObject jsonObject = new JSONObject();
            try {
                jsonObject.put("type","geojson");
                jsonObject.put("data","");      // 初始数据为空
                jsonObject.put("cluster",true);  // 启用聚合功能
                jsonObject.put("clusterRadius",30);
                jsonObject.put("clusterProperties",clusterProperties); // 聚合对象包含的特征
            } catch (JSONException e) {
                e.printStackTrace();
            }
//            Log.d(TAG, jsonObject.toString());
            Expected<String, Value> out = Value.fromJson(jsonObject.toString());
            Expected<String, None> success = mStyle.addStyleSource(CLUSTER_SOURCE_ID,
                    Objects.requireNonNull(out.getValue()));
            Log.d(TAG,"[initClusterLayers]source create: " +
                    (success.isError() ? success.getError() : "success"));
        }

初始化例子2:
例子1我们直接生成了一个关于 GenJsonSource 的JSON表达式,通过 styleaddStyleSource() 方法添加到 style 中。
MapBox也提供了直接构造一个GenJson类型Source的方法:

GeoJsonSource geoJsonSource = new GeoJsonSource.Builder(CLUSTER_SOURCE_ID)
        .data("")
        .cluster(true)
        .clusterRadius(30)
        .build();

通过这个方法生成的 GenJsonSource 需要在载入地图style前,添加到 StyleExtensionImpl.Builder() 构建的 StyleExtension ,再通过 mMapboxMap.loadStyle() 方法传入这个 StyleExtension 对象去加载地图Style。

StyleExtensionImpl.Builder builder = new StyleExtensionImpl.Builder(DEFAULT_MAP_STYLE);
builder.addSource(geoJsonSource);
mMapboxMap.loadStyle(builder.build(), new Style.OnStyleLoaded() {
    @Override
    public void onStyleLoaded(@NotNull Style style) {

    }
});

3. layer初始化并添加到style中:

layer表示数据的呈现形式,在这里我们定义三个类型的layer用于展示我们的数据:

  • 单个标记物的icon展示layer
  • 聚合对象(多个标记物的集合)的icon展示layer
  • 聚合对象(多个标记物的集合)的text展示layer

如果一个对象为 聚合对象cluster ,其将包含以下几个属性:
在这里插入图片描述
除了上面四个默认属性,其还将包含你在 Source 创建时设置的 clusterProperties 中的属性值。
这里以单个标记物对象的展示的layer的初始化为例:
定义全局变量:

private static final String LAYER_ID_SINGLE_POINT = "layer-id-single-points";
private static final String POINT_COUNT = "point_count";

首先,不同的 Layer 应该在不同条件下显示(如我们上面定义的那样,单个标记物的icon展示的layer应该在标记物为 非聚合对象 的情况下展示),通过 Layer 的属性 filter,我们可以设置 Layer 的展示条件,该属性接受一个 Expression 表达式 作为输入。

根据上面所述,我们可以利用 cluster聚合对象 包含 ”point_count” 属性这一特征区分 聚合对象 和 单一对象。

如下代码所示,我们构造了一个判断当前对象是否有 ”point_count” 属性的 Express表达式 ,该表达式 当 当前对象 不含有 ”point_count” 属性时会返回 true。

该Expression的Json表达式为:

[“!”,[“has”,”point_count”]]

java构建Experssion表达式代码:

// 构造filter表达式
Expression.ExpressionBuilder filterExpression = new Expression.ExpressionBuilder("!");
filterExpression.has(POINT_COUNT);

上述构造的 expression 将传入 layerfilter 属性中,以控制该 layer 是否显示。

其次,这一层的标记物我们打算将以图像的形式展现,利用 symbol 中的 icon-image 属性即可实现,为了控制这个图像的显示特点,我们定义一个自定义的 iconLayout Json对象,来描述图像显示的属性:

JSONObject iconLayout = new JSONObject();
try {
    iconLayout.put("icon-ignore-placement",true); // 设置其他symbol与icon碰撞时仍显示
    iconLayout.put("icon-allow-overlap",true);	 // 设置icon与其他symbol碰撞时仍显示
} catch (JSONException e) {
    e.printStackTrace();
}

icon-ignore-placement 等属性值为symbol标记类型所包含的属性,更多symbol特殊属性值可参照:
https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#symbol

最后Layer的构造代码所下所示:
其中 “{” + IMAGE_ID_SINGLE_POINT_ICON + "}” 表达式,可以获取到当前单一标记物对象中属性名为 IMAGE_ID_SINGLE_POINT_ICON 的属性值。
icon-image 属性接受该表达式,以在Style中检索 通过 style.addImage() 方法传入的 与之id相匹配的图片;这样即可对拥有不同id的标记,显示不同的图片。(向style中addImage可参看第5部分)

private static final String IMAGE_ID_SINGLE_POINT_ICON = "id";
// 构造layer
JSONObject jsonObject = new JSONObject();
try {
    jsonObject.put("id", LAYER_ID_SINGLE_POINT);
    jsonObject.put("type","symbol");
    jsonObject.put("source", CLUSTER_SOURCE_ID);
    jsonObject.put("icon-image","{" + IMAGE_ID_SINGLE_POINT_ICON + "}");
    jsonObject.put("layout", iconLayout);
    jsonObject.put("filter", new JSONArray(filterExpression.build().toJson()));
} catch (JSONException e) {
    e.printStackTrace();
}
Expected<String, Value> out = Value.fromJson(jsonObject.toString());
Expected<String, None> success = mStyle.addStyleLayer(Objects.requireNonNull(out.getValue()),
        new LayerPosition(null, null, null));
Log.d(TAG,"[initClusterLayers]single icon layer create: " +
        (success.isError() ? success.getError() : "success"));

Source 的构造一样,我们也可以直接使用类 SymbolLayer 直接进行构造。但其也需要在Style加载前添加到 StyleExtension 中,这里不再举例。

其余layer的添加代码见文章开头的gitee库代码。
在示例的代码中,我们对 聚合对象(多个标记物的集合)的icon展示layer 这一类型的layer构建了三个,这样可以分别在聚合对象包含 标记物数量x,x>=150;150>x>=20; 20>x>1; 三种情况下定制不同的展示模式(但示例代码并没有赋予他们不同的展示,如果你也不需要对此进行区别展示,可以仅为聚合对象的icon创建一个 layer 即可)。
在上述区分情况下,filter 属性中的 Expression表达式 需有如下考虑:

  • 当前展示对象为聚合对象;
  • 当前聚合对象所包含的标记物数量在某区间内;

根据上面所示,我们 filter 的判断条件有多个,因此我们使用 all操作符 ,当任意一个条件不满足时就返回false,不进行显示。
首先,我们通过以下代码获取到显示对象中属性名为 ”point_count” 的属性值,并转为数字 (使用to-number操作符)。该属性值即表示该对象中包含的标记物数量。

Expression pointCount = Expression.toNumber(Expression.get(POINT_COUNT));

第一个条件:当前展示对象为聚合对象,即:要有”point_count”属性。

expressionBuilder.has(POINT_COUNT);

第二个条件:当前聚合对象所包含的标记物数量在某区间内(这里以x>=150为例)我们使用 gte操作符 (greater or equal)比较 ”point_count” 和150;

完整代码如下所示:

int[] layers = new int[]{150, 20, 1};   // 按照聚合对象中包含的标记物数量区分
for (int i = 0; i < layers.length; i++) {
    Expression pointCount = Expression.toNumber(Expression.get(POINT_COUNT));
    Expression.ExpressionBuilder expressionBuilder = new Expression.ExpressionBuilder("all");
    int finalI = i;
    expressionBuilder.has(POINT_COUNT);
    if(finalI == 0) {
        expressionBuilder.gte(expressionBuilder1 -> {
            expressionBuilder1.addArgument(pointCount);
            expressionBuilder1.literal(layers[finalI]);
            return null;
        });
    } else {
        expressionBuilder.gte(expressionBuilder12 -> {
            expressionBuilder12.addArgument(pointCount);
            expressionBuilder12.literal(layers[finalI]);
            return null;
        });
        expressionBuilder.lt(expressionBuilder13 -> {
            expressionBuilder13.addArgument(pointCount);
            expressionBuilder13.literal(layers[finalI - 1]);
            return null;
        });
    }
    // ...
}

关于Expression操作符的更多说明可以参见:
https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions

4. 向Source中添加数据:

对于 genjson 类型的 source 而言,其中存储数据的为属性data,向 source 中添加数据即设置其属性值data
data属性接受一个geojson对象。

GeoJSON 是一种对各种地理数据结构进行编码的格式。GeoJSON对象可以表示几何、特征或者特征集合。关于GeoJson的说明可以参见下面这个博主的文章:GeoJSON 学习

根据 GeoJSON 的数据组织格式,我们构建了一个自己的 GeoJSON类

public class MapGeoJson {
    /**
     * GeoJSON是一种对各种地理数据结构进行编码的格式。GeoJSON对象可以表示几何、特征或者特征集合。
     * 样例:https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson
     * type : FeatureCollection
     * crs : {"type":"name","properties":{"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}}
     * features : [{"type":"Feature","properties":{"id":"ak16994521","mag":2.3,"time":1507425650893,"felt":null,"tsunami":0},"geometry":{"type":"Point","coordinates":[-151.5129,63.1016,0]}},{"type":"Feature","properties":{"id":"ak16994519","mag":1.7,"time":1507425289659,"felt":null,"tsunami":0},"geometry":{"type":"Point","coordinates":[-150.4048,63.1224,105.5]}},{"type":"Feature","properties":{"id":"ak16994517","mag":1.6,"time":1507424832518,"felt":null,"tsunami":0},"geometry":{"type":"Point","coordinates":[-151.3597,63.0781,0]}}]
     */

    private final String type = "FeatureCollection";    // MapGeoJson类型
    private final CrsBean crs = new CrsBean();  // [可选]坐标参考系
    private List<FeaturesBean> features;        // 特征对象的集合

    public List<FeaturesBean> getFeatures() {
        return features;
    }

    public void setFeatures(List<FeaturesBean> features) {
        this.features = features;
    }

    public static class CrsBean {
        /**
         * 非空的CRS对象有两个强制拥有的对象:“type"和"properties”。
         * type表明Crs对象类型(名字Crs 或者 链接Crs)
         * type : name
         * properties : {"name":"urn:ogc:def:crs:OGC:1.3:CRS84"}
         */
        private final String type = "name";
        private final CrsProperties properties = new CrsProperties();

        public static class CrsProperties {
            /**
             * 名字Crs的Properties必须为 name
             * name : urn:ogc:def:crs:OGC:1.3:CRS84
             */
            private final String name = "urn:ogc:def:crs:OGC:1.3:CRS84";  // 标识坐标参考系统的字符串
        }
    }

    // 特征对象
    public static class FeaturesBean {
        /**
         * 类型为"Feature"的GeoJSON对象是特征对象
         * type : Feature
         * properties : {"type":0,"id":"ak16994521","mag":2.3,"time":1507425650893,"felt":null,"tsunami":0}
         * geometry : {"type":"Point","coordinates":[-151.5129,63.1016,0]}
         */
        private final String type = "Feature";
        private FeatureProperties properties;
        private FeatureGeometry geometry;

        public FeatureProperties getProperties() {
            return properties;
        }

        public void setProperties(FeatureProperties properties) {
            this.properties = properties;
        }

        public FeatureGeometry getGeometry() {
            return geometry;
        }

        public void setGeometry(FeatureGeometry geometry) {
            this.geometry = geometry;
        }

        /**
         * 特征对象特征值
         */
        public static class FeatureProperties {
            private String id;
            private String imageName;

            public String getId() {
                return id;
            }

            public void setId(String id) {
                this.id = id;
            }

            public String getImageName() {
                return imageName;
            }

            public void setImageName(String imageName) {
                this.imageName = imageName;
            }
        }

        /**
         * 几何对象
         * 包含 coordinates 数组表示几何位置
         */
        public static class FeatureGeometry {
            /**
             * type : Point
             * coordinates : [-151.5129,63.1016,0]
             */
            private String type = "Point";
            private List<Double> coordinates;

            public String getType() {
                return type;
            }

            public void setType(String type) {
                this.type = type;
            }

            public List<Double> getCoordinates() {
                return coordinates;
            }

            public void setCoordinates(List<Double> coordinates) {
                this.coordinates = coordinates;
            }
        }
    }

    /**
     * 生成Json字符串
     */
    public String crateJson() {
        Gson gson = new Gson();
        return gson.toJson(this);
    }
}

根据我们的集合数据构造一个 GeoJson 对象:
构造自定义的标记物对象PhotoAnnotation.java

/**
 * 地图 照片类型 注释
 */
public class PhotoAnnotation {
    private final String mId;
    private final String mImageName;

    private final double mLatitude;     // 标记物坐标
    private final double mLongitude;

    public PhotoAnnotation(String id, String imageName, double latitude, double longitude) {
        mId = id;
        mImageName = imageName;
        mLatitude = latitude;
        mLongitude = longitude;
    }

    public String getId() {
        return mId;
    }

    public double getLatitude() {
        return mLatitude;
    }

    public double getLongitude() {
        return mLongitude;
    }

    public String getImageName() {
        return mImageName;
    }
}

构造GeoJson对象工具类:

public class MapGeoJsonUtil {

    public static MapGeoJson getMapGeoJsonFromList(List<PhotoAnnotation> list) {
        if (list == null || list.size() <= 0){
            return null;
        }
        MapGeoJson mapGeoJson = new MapGeoJson();
        List<MapGeoJson.FeaturesBean> featureList = new ArrayList<>();
        //填充数据
        for (PhotoAnnotation photoAnnotation : list) {
            MapGeoJson.FeaturesBean fBean = new MapGeoJson.FeaturesBean();
            fBean.setProperties(getDevicePropertiesBeanX(photoAnnotation.getId(), photoAnnotation.getImageName()));
            fBean.setGeometry(getGeometryBean(photoAnnotation.getLatitude(), photoAnnotation.getLongitude()));
            featureList.add(fBean);
        }
        mapGeoJson.setFeatures(featureList);
        return mapGeoJson;
    }

    private static MapGeoJson.FeaturesBean.FeatureGeometry getGeometryBean(double latitude, double longitude) {
        MapGeoJson.FeaturesBean.FeatureGeometry geometryBean = new MapGeoJson.FeaturesBean.FeatureGeometry();
        List<Double> coordinates = new ArrayList<>();
        coordinates.add(longitude);
        coordinates.add(latitude);
        geometryBean.setCoordinates(coordinates);
        return geometryBean;
    }

    private static MapGeoJson.FeaturesBean.FeatureProperties getDevicePropertiesBeanX(String id, String imageName) {
        MapGeoJson.FeaturesBean.FeatureProperties pBeanX = new MapGeoJson.FeaturesBean.FeatureProperties();
        pBeanX.setId(id);
        pBeanX.setImageName(imageName);
        return pBeanX;
    }
}

通过以下代码即可生成Geojson对象:

MapGeoJson mapGeoJson = MapGeoJsonUtil.getMapGeoJsonFromList(photoAnnotationList);

这里的 photoAnnotationList 需要自行生成,这里提供一个例子:
例子中传入的imageName为项目assert中包含的图片名称,通过相关方法即可从assert加载对应图片,详情请看本文的示例程序。

    public void addImage() {
        List<PhotoAnnotation> demoObjList = new ArrayList<>();
        Random random = new Random();
        // 随机添加100张图片(在中国范围内)
        for (int i = 0; i < 100; i++) {
            int lat = random.nextInt(30) + 20;
            int lng = random.nextInt(64) + 71;
            String imageName = random.nextInt(9) + ".jpg";
//            Log.d(TAG,"imageName: " + imageName);
            PhotoAnnotation demoObj = new PhotoAnnotation("" + i,
                    imageName, lat, lng);
            demoObjList.add(demoObj);
        }
        mapBoxUtil.setClusterLayerPhotoItem(demoObjList);
        // 缩小地图
        mapBoxUtil.moveCameraTo(Point.fromLngLat(103, 35), 2, 1000);
    }

最后将 mapGeoJson 传入属性data中即可(注意,这一步需要在主线程中进行,关于mapView的操作均需要在主线程中进行,因为都是涉及UI的更改):

    /**
     * 向聚类源中设置数据集
     * @param mapGeoJson 为空时则清空数据
     */
    private void setClusterSourceProperties(MapGeoJson mapGeoJson) {
        if (mStyle == null) {
            return;
        }
        JSONObject properties = new JSONObject();
        try {
            properties.put("type","geojson");
            if (mapGeoJson == null) {
                properties.put("data","");
            }else {
                properties.put("data",mapGeoJson.crateJson());
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        Expected<String, Value> out = Value.fromJson(properties.toString());
        if (out.getValue() == null) {
            Log.d(TAG,"[setClusterSourceProperties]property turn to value error:" + out.getError());
            return;
        }
        Expected<String, None> success = mStyle.setStyleSourceProperties(CLUSTER_SOURCE_ID, out.getValue());
        Log.d(TAG,"[setClusterSourceProperties]" + (success.isError() ? success.getError() : "success"));
    }

最后还有一步,即

5. 向 style 中添加图片,以用于展示:

利用 styleaddImage 方法,该方法接受两个参数:

  • 一个为图像标识id;
  • 一个为图像bitmap。

layer 中标记物显示时,会根据我们上面创建的 layericon-image 属性的值,在 style 中检索对应的 图像标识id 进行显示。

mStyle.addImage();
// 注意,在不使用时记得清理image:
mStyle.removeStyleImage(imageId)

为了实现自定义标记物的展示,我们可以构建一个自定义layout,加载成view,再转成bitmap传入即可(记得转化前需要先进行layout操作以赋予view大小)。

layout代码:

/**
 * 设置view的大小
 */
public static void layoutView(View v, int width, int height) {
    if (v == null || width <= 0 || height <=0) {
        return;
    }
    v.layout(0,0, width, height);
    int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.AT_MOST);
    int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.AT_MOST);
    v.measure(measuredWidth, measuredHeight);
    v.layout(0,0, v.getMeasuredWidth(), v.getMeasuredHeight());
}

view转bitmap代码:

/**
 * 从view中创建bitmap
 * @param view 需要有大小
 */
public static Bitmap getBitmapFromView(View view) {
    if (view == null || view.getWidth() <= 0 || view.getHeight() <= 0){
        return null;
    }
    //是ImageView直接获取
    if (view instanceof ImageView) {
        Drawable drawable = ((ImageView) view).getDrawable();
        if (drawable instanceof BitmapDrawable) {
            return ((BitmapDrawable) drawable).getBitmap();
        }
    }
    view.clearFocus();
    Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
    if (bitmap != null) {
        Canvas canvas = new Canvas(bitmap);
        view.draw(canvas);
        canvas.setBitmap(null);
    }
    return bitmap;
}

6. 点击标记物的实现(若为聚合对象,可以获取到其中的包含的所有标记物):

通过 mMapboxMapqueryRenderedFeatures 方法,我们可以根据用户点击相对于 mapView 左上角的坐标,查询到指定Layers中的标记对象。

若标记对象为聚合对象,我们再利用 getGeoJsonClusterLeaves 方法,根据聚合对象的特征获取到该聚合对象中包含的所有标记物对象。

相关代码如下所示:

/**
     * 地图点击事件响应(用于判断被点击组件是否为cluster)
     * @param clickX 触摸点相对于mapView左上角的坐标X
     * @param clickY 触摸点相对于mapView左上角的坐标Y
     */
    public void onMapClickEvent(float clickX, float clickY) {
        if (mMapboxMap == nulll) {
            return;
        }
//        Log.d(TAG,"onMapClickEvent(" + clickX + "," + clickY + ")");
        // 单张图片点击事件
        mMapboxMap.queryRenderedFeatures(
                new RenderedQueryGeometry(new ScreenCoordinate(clickX, clickY)),
                new RenderedQueryOptions(Collections.singletonList(LAYER_ID_SINGLE_POINT), null),
                features -> {
                    if (features.getError() != null || features.getValue() == null) {
                        return;
                    }
                    List<QueriedFeature> list = features.getValue();
                    if (list == null || list.size() <= 0) {
                        return;
                    }
                    // 默认取第一个
                    QueriedFeature curFeature = list.get(0);
                    JsonObject jsonObject = curFeature.getFeature().properties();
                    if (jsonObject == null) {
                        return;
                    }
                    Log.d(TAG,"[getClusterChildFromClick]"+jsonObject.toString());
                    String id = jsonObject.get("id").getAsString();
		//id 即为被点击的标记物id
                });

        // 聚合点点击事件
        mMapboxMap.queryRenderedFeatures(
                new RenderedQueryGeometry(new ScreenCoordinate(clickX, clickY)),
                new RenderedQueryOptions(Arrays.asList(LAYER_ID_CLUSTER_ + "0",
                        LAYER_ID_CLUSTER_ + "1",
                        LAYER_ID_CLUSTER_ + "2"), null),
                features -> {
                    if (features.getError() != null) {
                        return;
                    }
                    List<QueriedFeature> list = features.getValue();
                    if (list == null || list.size() <= 0) {
                        return;
                    }
                    // 默认取第一个
                    QueriedFeature curFeature = list.get(0);
                    Log.d(TAG,"[getClusterChildFromClick]"+curFeature.toString());
                    // 查询该聚合下的子集
                    long maxPointNum = 5000;    // 单次查询返回子集数量最大值
                    mMapboxMap.getGeoJsonClusterLeaves(CLUSTER_SOURCE_ID, curFeature.getFeature(),
                            maxPointNum, extension -> {
                                if (extension.getError() != null || extension.getValue() == null) {
                                    return;
                                }
                                FeatureExtensionValue features1 = extension.getValue();
                                if (features1 == null || features1.getFeatureCollection() == null) {
                                    return;
                                }
                                Log.d(TAG,"include features size:"+features.getFeatureCollection().size());
                                List<String> idList = new ArrayList<>();
                                for (Feature feature : features1.getFeatureCollection()) {
                                    String id = feature.getProperty("id").getAsString();
                                    idList.add(id);
                                }
                                //idList即为被点击的聚合对象包含的所有标记物id
                            });
                });
    }

关于如何获取到用户点击相对于mapview左上角坐标:
mapView 的 onClick 方法不会返回点击坐标信息。我们应该使用 onTouch 方法,通过返回的event.getX() 和 event.getY() 来获取用户点击相对于mapview左上角坐标。但注意要记得区分单击和双击事件,可以利用 GestureDetector 进行区分。

GestureDetector mGestureDetector = new GestureDetector(... ...);
mGestureDetector.setOnDoubleTapListener(new GestureDetector.OnDoubleTapListener() {
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        // mapView单击
        return false;
    }
    ... ...
}
mapView.setOnTouchListener((v, event) -> mGestureDetector.onTouchEvent(event));
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
引用\[3\]中的代码展示了如何使用react-mapbox-gl来实现地图搜索。在代码中,我们首先导入了ReactMapboxGl和其他必要的组件。然后,我们创建了一个Map组件,并传入了一个accessToken作为参数。接下来,我们定义了一个newStyle对象,其中包含了地图的样式和图层信息。在这个例子中,我们使用了天地图的瓦片服务作为地图的底图。然后,我们在Map组件中使用了这个newStyle作为地图的样式,并设置了地图的容器大小和中心点坐标。最后,我们在地图上添加了一个标记点,并设置了标记点的图标样式。通过这样的方式,我们可以在地图实现地图搜索功能。 #### 引用[.reference_title] - *1* *2* [react-native-mapbox-gl在RN中的使用 --工作笔记](https://blog.csdn.net/simper_boy/article/details/105654598)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] - *3* [React-mapbox-gl](https://blog.csdn.net/qq_34870529/article/details/103823205)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^insertT0,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值