You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中基于Mapbox Maps实现地图图层Feature ID点击展示

Mapbox Flutter 点击网格Feature展示ID实现方案

问题描述

我正在开发一个使用Mapbox Maps的Flutter应用,已借助mapbox_maps_flutter: ^0.5.1加载含多要素的GeoJSON数据,并根据要素status字段分配不同地图图层。需要实现:用户点击特定网格图层中的Feature时,通过弹窗、Snackbar等方式展示该Feature的ID,但未找到点击事件的明确文档说明,无法完成需求。

现有代码

View页面代码

SizedBox(
  height: Get.height,
  child: Obx(() => MapWidget(
        key: const ValueKey("mapWidget"),
        onMapCreated: controller.onMapCreated,
        cameraOptions: CameraOptions(
            center: Point(
                    coordinates:
                        Position(-107.882000467, 47.616100673))
                .toJson(),
            zoom: controller.zoom.value),
        resourceOptions: ResourceOptions(
          accessToken: "<MAPBOX ACCESSS TOKEN>",
        ),
      )),
)

Controller页面代码

Rx<MapboxMap?> mapboxMap = Rx<MapboxMap?>(null);

Future<void> onMapCreated(MapboxMap mapboxMap) async {
    this.mapboxMap.value = mapboxMap;

    // Load GeoJSON data
    var data = await rootBundle.loadString('assets/geo.geojson');
    var geoJson = json.decode(data);
    var geoJsonFeatures = geoJson['features'];

    // Create a Map to store colors based on status
    Map<int, Color> statusColors = {
      1: Color.fromARGB(128, 247, 247, 0), // 50% opacity
      2: Color.fromARGB(128, 24, 225, 31), // 50% opacity
      3: Color.fromARGB(128, 13, 104, 185), // 50% opacity
      4: const Color.fromARGB(0, 229, 210, 210), // Custom color for status 4
    };

    // Filter GeoJSON features based on status and create separate sources
    for (var status in statusColors.keys) {
      var filteredFeatures = geoJsonFeatures
          .where((feature) =>
              feature['properties'] != null &&
              feature['properties']['status'] == status)
          .toList();

      var filteredGeoJson = {
        'type': 'FeatureCollection',
        'features': filteredFeatures,
      };

      await mapboxMap.style.addSource(GeoJsonSource(
        id: 'fill_source_$status',
        data: json.encode(filteredGeoJson), // Ensure GeoJSON is a string
      ));

      await mapboxMap.style.addLayer(FillLayer(
        id: 'fill_layer_$status',
        sourceId: 'fill_source_$status',
        fillColor: statusColors[status]?.value ?? Colors.transparent.value,
        fillOutlineColor: Colors.black.value,
        fillOpacity: .5,
      ));
    }
  }

GeoJSON示例

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "geometry": {
        "type": "Polygon",
        "coordinates": [
          [
            [
              -107.900000467,
              47.598100673
            ],
            [
              -107.891000467,
              47.598100673
            ],
            [
              -107.891000467,
              47.589100673
            ],
            [
              -107.900000467,
              47.589100673
            ],
            [
              -107.900000467,
              47.598100673
            ]
          ]
        ]
      },
      "properties": {
        "dbId": "5",
        "status": 4,
        "uid": null,
        "isBlocked": 0,
        "id": "5",
        "left": -107.900000467,
        "top": 47.598100673,
        "right": -107.891000467,
        "bottom": 47.589100673
      }
    },...    
  ]
}

解决方案

要实现Feature点击事件,核心是利用Mapbox提供的onTap监听和queryRenderedFeatures方法获取点击位置的要素数据,具体步骤如下:

1. 添加点击监听器

在onMapCreated方法末尾,为Mapbox实例添加点击事件监听:

// 监听地图点击事件
mapboxMap.onTap.addListener(_handleMapTap);

2. 实现点击处理函数

在Controller中定义_handleMapTap函数,通过queryRenderedFeatures查询点击位置的Feature,并提取ID展示:

void _handleMapTap() {
  final tapPosition = mapboxMap.value?.onTap.value;
  if (tapPosition == null) return;

  // 查询点击位置的渲染要素,指定我们创建的所有fill图层
  mapboxMap.value?.queryRenderedFeatures(
    RenderedQueryGeometry(
      geometry: Point(coordinates: Position(tapPosition.point.x, tapPosition.point.y)).toJson(),
      layers: statusColors.keys.map((status) => 'fill_layer_$status').toList(),
    ),
  ).then((features) {
    if (features.isNotEmpty) {
      // 获取Feature的id字段(从properties中提取)
      final featureId = features.first.properties['id'];
      if (featureId != null) {
        // 使用GetX的Snackbar展示ID,也可替换为弹窗等其他方式
        Get.snackbar(
          'Feature ID',
          'ID: $featureId',
          snackPosition: SnackPosition.BOTTOM,
          duration: const Duration(seconds: 2),
        );
      }
    }
  }).catchError((error) {
    print('查询Feature失败: $error');
  });
}

3. 清理监听器(避免内存泄漏)

在Controller的onClose方法中移除点击监听器:

@override
void onClose() {
  mapboxMap.value?.onTap.removeListener(_handleMapTap);
  super.onClose();
}

完整修改后的Controller代码

import 'package:get/get.dart';
import 'package:mapbox_maps_flutter/mapbox_maps_flutter.dart';
import 'package:flutter/services.dart' show rootBundle;
import 'dart:convert' as convert;
import 'package:flutter/material.dart';

class MapController extends GetxController {
  Rx<MapboxMap?> mapboxMap = Rx<MapboxMap?>(null);
  final RxDouble zoom = 14.0.obs;

  // 状态与颜色映射
  Map<int, Color> statusColors = {
    1: Color.fromARGB(128, 247, 247, 0),
    2: Color.fromARGB(128, 24, 225, 31),
    3: Color.fromARGB(128, 13, 104, 185),
    4: const Color.fromARGB(0, 229, 210, 210),
  };

  Future<void> onMapCreated(MapboxMap mapboxMap) async {
    this.mapboxMap.value = mapboxMap;

    // 加载GeoJSON数据
    var data = await rootBundle.loadString('assets/geo.geojson');
    var geoJson = convert.json.decode(data);
    var geoJsonFeatures = geoJson['features'];

    // 按状态过滤要素并创建图层
    for (var status in statusColors.keys) {
      var filteredFeatures = geoJsonFeatures
          .where((feature) =>
              feature['properties'] != null &&
              feature['properties']['status'] == status)
          .toList();

      var filteredGeoJson = {
        'type': 'FeatureCollection',
        'features': filteredFeatures,
      };

      await mapboxMap.style.addSource(GeoJsonSource(
        id: 'fill_source_$status',
        data: convert.json.encode(filteredGeoJson),
      ));

      await mapboxMap.style.addLayer(FillLayer(
        id: 'fill_layer_$status',
        sourceId: 'fill_source_$status',
        fillColor: statusColors[status]?.value ?? Colors.transparent.value,
        fillOutlineColor: Colors.black.value,
        fillOpacity: .5,
      ));
    }

    // 添加点击监听器
    mapboxMap.onTap.addListener(_handleMapTap);
  }

  // 处理地图点击事件
  void _handleMapTap() {
    final tapPosition = mapboxMap.value?.onTap.value;
    if (tapPosition == null) return;

    mapboxMap.value?.queryRenderedFeatures(
      RenderedQueryGeometry(
        geometry: Point(coordinates: Position(tapPosition.point.x, tapPosition.point.y)).toJson(),
        layers: statusColors.keys.map((status) => 'fill_layer_$status').toList(),
      ),
    ).then((features) {
      if (features.isNotEmpty) {
        final featureId = features.first.properties['id'];
        if (featureId != null) {
          Get.snackbar(
            'Feature ID',
            'ID: $featureId',
            snackPosition: SnackPosition.BOTTOM,
            duration: const Duration(seconds: 2),
          );
        }
      }
    }).catchError((error) {
      print('查询Feature失败: $error');
    });
  }

  // 页面销毁时清理监听器
  @override
  void onClose() {
    mapboxMap.value?.onTap.removeListener(_handleMapTap);
    super.onClose();
  }
}

内容的提问来源于stack exchange,提问作者Spike Spiegel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 21:07:34