Andrey F
Andrey F

Reputation: 21

mapbox_maps_flutter markers and clusters live thier own lives separately and clusters not visible if map's style is any other than MapboxStyles.LIGHT

Version mapbox_maps_flutter: ^0.5.1 is being used to render a MapBox in Flutter application

Flutter version 3.16.2

1. First problem The 1st issue I want to highlight that if I apply my own map style other than by default MapboxStyles.LIGHT, all clusters and unClusteredLayer are below the main map layout and not visible - means that I see them during some milliseconds (before map layout was loaded) and later map just covers them. Even if I use MapboxStyles.DARK the same problem exists.

2. Second problem Is there any way to combine Markers and Clusters in the way that they know that each other exists on the map?

I render Markers by means of pointAnnotationManager and render Clusters as per your example file (example). Both Markers and Clusters are displayed on the screen, but when some points are inside Cluster their markers are still visible. In example YOU use unclusteredLayer instead of Markers. As for the Cluster I found the way how to style it, however as for the unClusteredLayer I didn't find any way or example on how to set it as an Image but not a simple circle. Nevertheless, even if I find the way how to set unClusteredLayer as an Image I didn't find any way to listen to tap on cluster or on unClusteredLayer.

Please, provide a complete example, that includes both clusters and "custom" markers as an Image where these "custom" markers are not visible inside cluster and the way to listen tap on cluster or unClusteredLayer.

Here is the my code that is responsible for both Clusters and Markers.

...

void setMarkers(BuildContext context, List<PlaceEntity> places) async {
    mapboxMap.annotations.createPointAnnotationManager().then((pointAnnotationManager) async {
      var options = <PointAnnotationOptions>[];

      for (var i = 0; i < places.length; i++) {
        final point = points[i];
        final place = places[i];

        final widget = IEyeMarker(
          place: place,
          onDetailsTap: () {},
        );

        final Uint8List? image = await createImageFromWidget(context, widget);

        options.add(
          PointAnnotationOptions(
            geometry: Point(coordinates: Position(point.lng, point.lat)).toJson(),
            image: image,
            textField: place.id.toString(),
            textColor: 0x00000000,
            iconSize: 1,
          ),
        );
      }

      pointAnnotationManager.createMulti(options);
      pointAnnotationManager
          .addOnPointAnnotationClickListener(AnnotationClickListener(context: context));
    });

    List<Map<String, Object>> features = [];

    for (var i = 0; i < places.length; i++) {
      final point = points[i];
      final place = places[i];
      features.add({
        'type': 'Feature',
        'id': place.id,
        'properties': {
          'point_count_abbreviated': '1',
          'cluster_id': place.id,
          // 'cluster': true,
          // 'point_count': 1
        },
        'geometry': {
          'type': 'Point',
          'coordinates': [point.lng, point.lat, 0.0]
        }
      });
    }

    final data = {
      'type': 'FeatureCollection',
      'crs': {
        'type': 'name',
        'properties': {'name': 'urn:ogc:def:crs:OGC:1.3:CRS84'}
      },
      'features': features,
    };

    final layout = {
      'type': 'geojson',
      'data': data,
      'cluster': true,
      'clusterMaxZoom': 14,
      'clusterRadius': 50,
    };

    mapboxMap.style.styleSourceExists('places').then((value) async {
      if (!value) {
        var source = jsonEncode(layout);
        mapboxMap.style.addStyleSource('places', source.toString());
      }
    });

    mapboxMap.style.styleLayerExists('clusters').then((value) async {
      if (!value) {
        var layer = await rootBundle.loadString('assets/cluster/cluster_layer.json');
        mapboxMap.style.addStyleLayer(layer, null);

        var clusterCountLayer =
            await rootBundle.loadString('assets/cluster/cluster_count_layer.json');
        mapboxMap.style.addStyleLayer(clusterCountLayer, null);

        var unClusteredLayer =
            await rootBundle.loadString('assets/cluster/unclustered_point_layer.json');
        mapboxMap.style.addStyleLayer(unClusteredLayer, null);
      }
    });
  }

...

final points = [
  LatLng(lat: 51.5382123, lng: -0.1882464),
  LatLng(lat: 51.4382123, lng: -0.1982464),
  LatLng(lat: 51.5090229, lng: -0.2886548),
  LatLng(lat: 51.1090229, lng: -0.2786548),
  LatLng(lat: 51.5054563, lng: -0.0798412),
  LatLng(lat: 51.5090215, lng: -0.1959988),
  LatLng(lat: 51.5190215, lng: -0.1859988),
  LatLng(lat: 51.5577676, lng: -0.2008447),
];  

File "create_image_from_widget.dart":

import 'dart:ui';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

Future<Uint8List?> createImageFromWidget(BuildContext context, Widget widget,
    {Duration? wait, Size? logicalSize, Size? imageSize}) async {
  final repaintBoundary = RenderRepaintBoundary();

  logicalSize ??= View.of(context).physicalSize / View.of(context).devicePixelRatio;
  imageSize ??= View.of(context).physicalSize;

  assert(logicalSize.aspectRatio == imageSize.aspectRatio,
      'logicalSize and imageSize must not be the same');

  final renderView = RenderView(
      child: RenderPositionedBox(alignment: Alignment.center, child: repaintBoundary),
      configuration: ViewConfiguration(
        size: logicalSize,
        devicePixelRatio: 1,
      ),
      view: View.of(context) //PlatformDispatcher.instance.views.first,
      );

  final pipelineOwner = PipelineOwner();
  final buildOwner = BuildOwner(focusManager: FocusManager());

  pipelineOwner.rootNode = renderView;
  renderView.prepareInitialFrame();

  final rootElement = RenderObjectToWidgetAdapter<RenderBox>(
      container: repaintBoundary,
      child: Directionality(
        textDirection: TextDirection.ltr,
        child: widget,
      )).attachToRenderTree(buildOwner);

  buildOwner.buildScope(rootElement);

  if (wait != null) {
    await Future.delayed(wait);
  }

  buildOwner
    ..buildScope(rootElement)
    ..finalizeTree();

  pipelineOwner
    ..flushLayout()
    ..flushCompositingBits()
    ..flushPaint();

  final image = await repaintBoundary.toImage(pixelRatio: imageSize.width / logicalSize.width);
  final byteData = await image.toByteData(format: ImageByteFormat.png);

  return byteData?.buffer.asUint8List();
}

Just for info here is my flutter doctor result:

Doctor summary (to see all details, run flutter doctor -v):
[!] Flutter (Channel stable, 3.16.2, on macOS 14.2.1 23C71 darwin-arm64, locale ru-RU)
    ! Warning: `dart` on your path resolves to /opt/homebrew/Cellar/dart-sdk/3.1.5/libexec/bin/dart, which is not inside your current Flutter SDK checkout at /Users/andreifufylev/fvm/versions/3.16.2. Consider
      adding /Users/andreifufylev/fvm/versions/3.16.2/bin to the front of your path.
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.1)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2023.1)
[✓] IntelliJ IDEA Community Edition (version 2023.2.5)
[✓] VS Code (version 1.85.1)
[✓] Connected device (4 available)
[✓] Network resources

! Doctor found issues in 1 category.

In order to make muself clear I also included the video and screenshots of my problem where rounded boxes with Image inside are my custom Markers converted from Widget to Uint8List:

Without my own custom Markers With markers

I expect that custom markers to be not visible/clickable when inside cluster.

Upvotes: 1

Views: 689

Answers (0)

Related Questions