mapbox / mapbox-maps-flutter

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

Overlapping Polygons Affect Opacity of Fill Color #456

Open kyrill-bo opened 7 months ago

kyrill-bo commented 7 months ago

Problem: Currently, our system generates polygons comprising 360 vertices to display a red circle with a fill color set to red and an opacity of 10%. However, when these polygons overlap, the resulting opacity is not consistent. Overlapping polygons cause areas of the circle to become darker than the intended 10% opacity due to the additive effect of overlapping colors.

image

Expected Behavior: We expect the fill color opacity to remain consistent at 10% throughout the entire circle, regardless of overlapping polygons.

This is the current situation: image

What we need is: image

nonSmokingAreas[result[i].id] =
    (await nonSmokingAreaManager?.create(
  PolygonAnnotationOptions(
      fillColor: Colors.red.withOpacity(0.1).value,
      fillSortKey: 1,
      geometry: Polygon(
        coordinates: [
          List.generate(
            points.length,
            (index) => Position(
              points[index].lng,
              points[index].lat,
            ),
          ),
        ],
      ).toJson()),
))!;
import 'dart:math';

/// Generates a list of points representing a regular polygon around a given geographic coordinate (latitude and longitude) with a specified radius.
///
/// Parameters:
/// - `lat` (double): The latitude of the central position of the polygon.
/// - `lng` (double): The longitude of the central position of the polygon.
/// - `radius` (double): The radius of the polygon in meters.
///
/// Returns:
/// - `List<({double lat, double lng})>`: A list of tuples, where each tuple represents a point on the generated polygon. Each point is represented by latitude and longitude.
///
/// Algorithm:
/// The function uses an iterative method to generate the points of the polygon. It calculates the position of each point using the distance (`radius`) and the angle relative to the central position. It uses the `_translateCoordinate` function to compute the geographic coordinates of each point.
///
/// 1. Initialize an empty list `points` to store the generated points.
/// 2. Iterate from 0 to 11 (total of 36 iterations) for creating a regular polygon.
/// 3. Calculate the angle (`angle`) for each point using the index of the iteration.
/// 4. Calculate the displacement (`dx` and `dy`) relative to the radius and angle.
/// 5. Use the `_translateCoordinate` function to compute the geographic coordinates of each point and add them to the `points` list.
/// 6. Return the `points` list containing all the generated points of the polygon.
///
/// Notes:
/// - The algorithm assumes the Earth is a perfect sphere and uses a simple approximation for computing the coordinates.
/// - The function generates a regular polygon with 36 vertices, which corresponds to a regular pentagon. The number of vertices can be changed by adjusting the loop condition.
List<({double lat, double lng})> listgeneratePolygonPoints(
    double lat, double lng, double radius) {
  List<({double lat, double lng})> points = [];

  for (int i = 0; i < 360; i++) {
    double angle = (2 * pi * i) / 360;
    double dx = radius * cos(angle);
    double dy = radius * sin(angle);
    final point = _translateCoordinate((lat: lat, lng: lng), dx, dy);
    points.add(point);
  }

  return points;
}

/// Translates the geographic coordinates of a point based on a given origin position, displacement in latitude and longitude, and Earth radius.
///
/// Parameters:
/// - `coordinate` ({double lat, double lng}): The initial coordinates tuple containing latitude and longitude.
/// - `dx` (double): The displacement in longitude.
/// - `dy` (double): The displacement in latitude.
///
/// Returns:
/// - `({double lat, double lng})`: A tuple containing the computed geographic coordinates of the point resulting from the displacement from the origin position.
///
/// Algorithm:
/// 1. Calculate the new latitude component (`lat`) based on the displacement in latitude and the Earth radius.
/// 2. Calculate the new longitude component (`lon`) based on the displacement in longitude, Earth radius, and the cosine of the latitude of the origin position.
/// 3. Return a tuple containing the calculated latitude and longitude of the new point.
///
/// Notes:
/// - The function considers the curvature of the Earth and uses a simple formula to compute the new coordinates.
/// - It is assumed that the displacement (`dx` and `dy`) is relatively small, so the curvature of the Earth can be neglected.
({double lat, double lng}) _translateCoordinate(
    ({double lat, double lng}) coordinate, double dx, double dy) {
  const double earthRadius = 6378137; // Earth radius in meters
  double lat = coordinate.lat + (dy / earthRadius) * (180 / pi);
  double lon = coordinate.lng +
      (dx / earthRadius) * (180 / pi) / cos(coordinate.lat * pi / 180);
  return (lat: lat, lng: lon);
}
simplylizz commented 5 months ago

It looks like an expected behaviour to me. But you could merge your circles before putting them on the map. The current turf release doesn't support union operation, but you could find a branch with added support in their PRs (upd: I've tested it, it's completely non-working), or alternatively, you could use polybool library. Also, depending on the amount of circles you have, you may want to run this computation in isolate to prevent UI freezes.

Disclaimer: I'm not a maintainer of this repo.