mapbox / mapbox-maps-flutter

Interactive, thoroughly customizable maps for Flutter powered by Mapbox Maps SDK
https://www.mapbox.com/mobile-maps-sdk
Other
288 stars 119 forks source link

How to tap symbols on layer #711

Open chooyan-eng opened 1 month ago

chooyan-eng commented 1 month ago

How can we tap symbols added with SymbolLayer?

I have the source code below to show a symbol, but can't find how to make this tappable.

class _MapScreenState extends State<MapScreen> {
  final position = Position.named(lng: 24.9458, lat: 60.17180);
  MapboxMap? _mapboxMap;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MapWidget(
        cameraOptions: CameraOptions(zoom: 3.0),
        styleUri: '[my_style_uri]',
        textureView: true,
        onMapCreated: (map) => _mapboxMap = map,
        onStyleLoadedListener: _onStyleLoadedCallback,
      ),
    );
  }

  Future<void> _onStyleLoadedCallback(StyleLoadedEventData event) async {
    final point = Point(coordinates: position);

    await _mapboxMap?.style.addSource(GeoJsonSource(
      id: 'sourceId',
      data: json.encode(point),
    ));

    var modelLayer = SymbolLayer(
      id: 'layerId',
      sourceId: 'sourceId',
      iconImage: '[my_icon_name]',
    );
    _mapboxMap?.style.addLayer(modelLayer);
  }
}

This shows one icon on my map, but that's it.

Do we have any examples or docs for this? Thanks.

ThomasAunvik commented 1 month ago

What do you use a style layer with symbols for?

Atm i can only think of a PointAnnotation that can do this.

Future<PointAnnotationManager> createPointManager(
    MapboxMap controller, {
    required void Function(PointAnnotation) callback,
}) async {
    final pointManager = await controller.annotations.createPointAnnotationManager();

    pointManager.addOnPointAnnotationClickListener(
        AnnotationClickListener(callback: callback),
    );

    return pointManager;
}
chooyan-eng commented 1 month ago

@ThomasAunvik Thanks for your suggestion!

However, its document says this API is not capable of displaying large amount of annotations.

Use style layers. This will require writing more code but is more flexible and provides better performance for the large amount of annotations (e.g. hundreds and thousands of them)

And my app needs to display hundreds of thousands of symbols at the same time. That's why I'd like to know the usage of layer in detail.

lantah-1 commented 1 month ago

You can set the GeoJsonSource data with FeatureCollection and set id for point , then listen map click event and use queryRenderedFeatures function to filter id that is point where you want

chooyan-eng commented 1 month ago

@lantah-1 Thanks! I haven't tried queryRenderedFeatures. I'll try.

abdelrahmanmostafa21 commented 1 month ago

This code implements interactive layer tapping:


class _MapScreenState extends State<MapScreen> {
  final position = Position.named(lng: 24.9458, lat: 60.17180);
  MapboxMap? _mapboxMap;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: MapWidget(
        cameraOptions: CameraOptions(zoom: 3.0),
        styleUri: '[my_style_uri]',
        textureView: true,
        onMapCreated: (map) => _mapboxMap = map,
        onStyleLoadedListener: _onStyleLoadedCallback,
        onMapTap: _onMapTap,
      ),
    );
  }

  Future<void> _onStyleLoadedCallback(StyleLoadedEventData event) async {
    final point = Point(coordinates: position);
    final features = [
      {
        "type": "Feature",
        "geometry": {
          "type": "Point",
          "coordinates": [point.coordinates.lng, point.coordinates.lat],
        },
        "properties": {
          'id': 'test-123',
          'anyDataKey': jsonEncode({}) //CUSTOM DATA,
        },
      },
    ];
    await _mapboxMap?.style.addSource(
      GeoJsonSource(
        id: 'sourceId',
        data: json.encode({"type": "FeatureCollection", "features": features}),
      ),
    );
    var modelLayer = SymbolLayer(
      id: 'layerId',
      sourceId: 'sourceId',
      iconImage: '[my_icon_name]',
    );
    _mapboxMap?.style.addLayer(modelLayer);
  }

Future<void> _onMapTap(
  MapContentGestureContext mapContentGestureContext,
) async {
  if (_mapboxMap == null) return;
  final List<QueriedRenderedFeature?> features = await _mapboxMap!.queryRenderedFeatures(
    RenderedQueryGeometry.fromScreenCoordinate(mapContentGestureContext.touchPosition),
    RenderedQueryOptions(
      layerIds: [
        ///LAYERS IDS YOU WANT TO LISTEN ...[]
        'layerId'
      ],
    ),
  );
  final queriedRenderedFeature = features.firstOrNull;
  if (queriedRenderedFeature == null || !mounted) return;
  _onFeatureTapped(queriedRenderedFeature);
}

  void _onFeatureTapped(
    QueriedRenderedFeature queriedRenderedFeature,
  ) {
    Point? point;
    Map? geometry = queriedRenderedFeature.queriedFeature.feature["geometry"] as Map?;
    if (geometry != null && _mapboxMap != null) {
      final lon = geometry["coordinates"][0];
      final lat = geometry["coordinates"][1];
      ///YOU CAN EASE TO
      ///THE POINT CAMERA POSITION
      //fitCameraToPosition(lat: lat, lon: lon);
      point = Point(coordinates: Position(lon, lat));
    }

    ///GET THE SOURCE PROPERITIES WHERE DEFINED Ids
    ///OR CUSTOM DATA YOU SET.
    final properties = queriedRenderedFeature.queriedFeature.feature["properties"] as Map?;
    final id = properties?['id'] as String?;
    if (id == null) return;
    if (id.startsWith('test-')) {
      ///CALLBACK CLICK
      ///YOU HAVE [POINT GEMOETRY,PROPERITIES OF THE POINT],
      /// final anyData = jsonDecode(properties?['anyDataKey'] ?? '{}');
    }
  }
}