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
相关产品推荐
相关产品推荐

