From 8f49dba0f0ecd64843549de827201239374062ad Mon Sep 17 00:00:00 2001
From: uditswaroopa <udit.swaroopa@burrowsoftware.com>
Date: Fri, 30 May 2025 16:06:44 +0530
Subject: [PATCH 1/5] Improve logic to compute center of a polygon

---
 lib/src/layer/polygon_layer/label.dart | 63 ++++++++++++++++++++++++--
 1 file changed, 59 insertions(+), 4 deletions(-)

diff --git a/lib/src/layer/polygon_layer/label.dart b/lib/src/layer/polygon_layer/label.dart
index e3de7d4fd..a44fb8e73 100644
--- a/lib/src/layer/polygon_layer/label.dart
+++ b/lib/src/layer/polygon_layer/label.dart
@@ -81,10 +81,65 @@ LatLng _computeLabelPosition(
 
 /// Calculate the centroid of a given list of [LatLng] points.
 LatLng _computeCentroid(List<LatLng> points) {
-  return LatLng(
-    points.map((e) => e.latitude).average,
-    points.map((e) => e.longitude).average,
-  );
+  if (points.isEmpty) {
+    throw ArgumentError('Polygon must contain at least one point');
+  }
+  
+  if (points.length == 1) {
+    return points[0];
+  }
+  
+  double signedArea = 0;
+  double centroidX = 0;
+  double centroidY = 0;
+  
+  // For all vertices except last
+  for (int i = 0; i < points.length - 1; i++) {
+    final double x0 = points[i].longitude;
+    final double y0 = points[i].latitude;
+    final double x1 = points[i + 1].longitude;
+    final double y1 = points[i + 1].latitude;
+    
+    // Calculate signed area contribution of current vertex
+    final double a = x0 * y1 - x1 * y0;
+    signedArea += a;
+    
+    // Accumulate centroid components weighted by signed area
+    centroidX += (x0 + x1) * a;
+    centroidY += (y0 + y1) * a;
+  }
+  
+  // Close the polygon by connecting last vertex to first
+  final double x0 = points.last.longitude;
+  final double y0 = points.last.latitude;
+  final double x1 = points.first.longitude;
+  final double y1 = points.first.latitude;
+  final double a = x0 * y1 - x1 * y0;
+  signedArea += a;
+  centroidX += (x0 + x1) * a;
+  centroidY += (y0 + y1) * a;
+  
+  // Complete the signed area calculation
+  signedArea *= 0.5;
+  
+  // Calculate centroid coordinates
+  centroidX /= 6 * signedArea;
+  centroidY /= 6 * signedArea;
+  
+  // Handle special case of zero area (collinear points)
+  if (signedArea == 0) {
+    // Default to average of all points
+    double sumX = 0;
+    double sumY = 0;
+    for (final point in points) {
+      sumX += point.longitude;
+      sumY += point.latitude;
+    }
+    return LatLng(sumY / points.length, sumX / points.length);
+  }
+  
+  return LatLng(centroidY, centroidX);
+  
 }
 
 /// Calculate the centroid of a given list of [LatLng] points with multiple worlds.

From 89dd495fa6bf5179d080ec3b88bec4005fe9da55 Mon Sep 17 00:00:00 2001
From: uditswaroopa <uditswaroopa@icloud.com>
Date: Mon, 2 Jun 2025 21:36:11 +0530
Subject: [PATCH 2/5] resolve formatting issue

---
 lib/src/layer/polygon_layer/label.dart | 21 ++++++++++-----------
 1 file changed, 10 insertions(+), 11 deletions(-)

diff --git a/lib/src/layer/polygon_layer/label.dart b/lib/src/layer/polygon_layer/label.dart
index a44fb8e73..645c1e675 100644
--- a/lib/src/layer/polygon_layer/label.dart
+++ b/lib/src/layer/polygon_layer/label.dart
@@ -84,31 +84,31 @@ LatLng _computeCentroid(List<LatLng> points) {
   if (points.isEmpty) {
     throw ArgumentError('Polygon must contain at least one point');
   }
-  
+
   if (points.length == 1) {
     return points[0];
   }
-  
+
   double signedArea = 0;
   double centroidX = 0;
   double centroidY = 0;
-  
+
   // For all vertices except last
   for (int i = 0; i < points.length - 1; i++) {
     final double x0 = points[i].longitude;
     final double y0 = points[i].latitude;
     final double x1 = points[i + 1].longitude;
     final double y1 = points[i + 1].latitude;
-    
+
     // Calculate signed area contribution of current vertex
     final double a = x0 * y1 - x1 * y0;
     signedArea += a;
-    
+
     // Accumulate centroid components weighted by signed area
     centroidX += (x0 + x1) * a;
     centroidY += (y0 + y1) * a;
   }
-  
+
   // Close the polygon by connecting last vertex to first
   final double x0 = points.last.longitude;
   final double y0 = points.last.latitude;
@@ -118,14 +118,14 @@ LatLng _computeCentroid(List<LatLng> points) {
   signedArea += a;
   centroidX += (x0 + x1) * a;
   centroidY += (y0 + y1) * a;
-  
+
   // Complete the signed area calculation
   signedArea *= 0.5;
-  
+
   // Calculate centroid coordinates
   centroidX /= 6 * signedArea;
   centroidY /= 6 * signedArea;
-  
+
   // Handle special case of zero area (collinear points)
   if (signedArea == 0) {
     // Default to average of all points
@@ -137,9 +137,8 @@ LatLng _computeCentroid(List<LatLng> points) {
     }
     return LatLng(sumY / points.length, sumX / points.length);
   }
-  
+
   return LatLng(centroidY, centroidX);
-  
 }
 
 /// Calculate the centroid of a given list of [LatLng] points with multiple worlds.

From 65da4cfe6c8d2f60832de4ebb50e6d5adf48cfbf Mon Sep 17 00:00:00 2001
From: JaffaKetchup <github@jaffaketchup.dev>
Date: Tue, 3 Jun 2025 15:40:03 +0100
Subject: [PATCH 3/5] Deprecate `PolygonLabelPlacement` Add
 `PolygonLabelPlacementCalculator` Added polygon label placement example to
 Polygon demo

---
 example/lib/misc/tile_providers.dart          |   1 +
 example/lib/pages/polygon.dart                | 706 ++++++++++--------
 example/lib/pages/polygon_perf_stress.dart    |   3 -
 example/lib/pages/polyline.dart               | 110 ++-
 example/lib/pages/repeated_worlds.dart        |   5 +-
 lib/flutter_map.dart                          |   2 +
 lib/src/layer/polygon_layer/label.dart        | 204 -----
 .../label/build_text_painter.dart             |  67 ++
 .../label/deprecated_placements.dart          |  48 ++
 .../label/placement_calculators/centroid.dart |  75 ++
 .../placement_calculator.dart                 |  55 ++
 .../placement_calculators/polylabel.dart      |  62 ++
 .../simple_centroid.dart                      |  59 ++
 lib/src/layer/polygon_layer/polygon.dart      |  69 +-
 .../layer/polygon_layer/polygon_layer.dart    |   4 +-
 15 files changed, 875 insertions(+), 595 deletions(-)
 delete mode 100644 lib/src/layer/polygon_layer/label.dart
 create mode 100644 lib/src/layer/polygon_layer/label/build_text_painter.dart
 create mode 100644 lib/src/layer/polygon_layer/label/deprecated_placements.dart
 create mode 100644 lib/src/layer/polygon_layer/label/placement_calculators/centroid.dart
 create mode 100644 lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart
 create mode 100644 lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart
 create mode 100644 lib/src/layer/polygon_layer/label/placement_calculators/simple_centroid.dart

diff --git a/example/lib/misc/tile_providers.dart b/example/lib/misc/tile_providers.dart
index 8c6db0b27..e0ba8ede2 100644
--- a/example/lib/misc/tile_providers.dart
+++ b/example/lib/misc/tile_providers.dart
@@ -1,6 +1,7 @@
 import 'package:flutter_map/flutter_map.dart';
 import 'package:flutter_map_cancellable_tile_provider/flutter_map_cancellable_tile_provider.dart';
 
+// TODO: This causes unneccessary rebuilding
 TileLayer get openStreetMapTileLayer => TileLayer(
       urlTemplate: 'https://tile.openstreetmap.org/{z}/{x}/{y}.png',
       userAgentPackageName: 'dev.fleaflet.flutter_map.example',
diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart
index 02f646041..55990f89b 100644
--- a/example/lib/pages/polygon.dart
+++ b/example/lib/pages/polygon.dart
@@ -23,268 +23,275 @@ class _PolygonPageState extends State<PolygonPage> {
   List<Polygon<HitValue>>? _hoverGons;
 
   bool _useInvertedFill = false;
+  PolygonLabelPlacementCalculator _labelPlacementCalculator =
+      const PolygonLabelPlacementCalculator.centroid();
 
-  final _polygonsRaw = <Polygon<HitValue>>[
-    Polygon(
-      points: const [
-        LatLng(51.5, -0.09),
-        LatLng(53.3498, -6.2603),
-        LatLng(48.8566, 2.3522),
-      ],
-      borderColor: Colors.red,
-      borderStrokeWidth: 4,
-      hitValue: (
-        title: 'Basic Unfilled Polygon',
-        subtitle: 'Nothing really special here...',
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(55.5, -0.09),
-        LatLng(54.3498, -6.2603),
-        LatLng(52.8566, 2.3522),
-      ],
-      color: Colors.purple,
-      borderColor: Colors.yellow,
-      borderStrokeWidth: 4,
-      hitValue: (
-        title: 'Basic Filled Polygon',
-        subtitle: 'Nothing really special here...',
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(46.35, 4.94),
-        LatLng(46.22, -0.11),
-        LatLng(44.399, 1.76),
-      ],
-      pattern: StrokePattern.dashed(segments: const [50, 20]),
-      borderStrokeWidth: 4,
-      borderColor: Colors.lightBlue,
-      color: Colors.yellow,
-      hitValue: (
-        title: 'Polygon With Dashed Borders',
-        subtitle: '...',
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(60.16, -9.38),
-        LatLng(60.16, -4.16),
-        LatLng(61.18, -4.16),
-        LatLng(61.18, -9.38),
-      ],
-      borderStrokeWidth: 4,
-      borderColor: Colors.purple,
-      label: 'Label!',
-      hitValue: (
-        title: 'Polygon With Label',
-        subtitle: 'This is a very descriptive label!',
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(59.77, -10.28),
-        LatLng(58.21, -10.28),
-        LatLng(58.21, -7.01),
-        LatLng(59.77, -7.01),
-        LatLng(60.77, -6.01),
-      ],
-      borderStrokeWidth: 4,
-      borderColor: Colors.purple,
-      label: 'Rotated!',
-      rotateLabel: true,
-      labelPlacement: PolygonLabelPlacement.polylabel,
-      hitValue: (
-        title: 'Polygon With Rotated Label',
-        subtitle: "Now you don't have to turn your head so much",
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(50, -18),
-        LatLng(50, -14),
-        LatLng(51.5, -12.5),
-        LatLng(54, -14),
-        LatLng(54, -18),
-      ].map((latlng) => LatLng(latlng.latitude, latlng.longitude + 8)).toList(),
-      pattern: const StrokePattern.dotted(),
-      holePointsList: [
-        const [
-          LatLng(52, -9),
-          LatLng(52, -8),
-          LatLng(51.5, -7.5),
-          LatLng(51, -8),
-          LatLng(51, -9),
-        ],
-        const [
-          LatLng(53.5, -9),
-          LatLng(53.5, -8),
-          LatLng(53, -7),
-          LatLng(52.25, -7),
-          LatLng(52.25, -8),
-          LatLng(52.75, -9),
-        ],
-        const [
-          LatLng(52.683614, -8.141285),
-          LatLng(51.663083, -8.684529),
-          LatLng(51.913924, -7.2193),
-        ],
-      ],
-      borderStrokeWidth: 4,
-      borderColor: Colors.orange,
-      color: Colors.orange.withAlpha(128),
-      label: 'This one is not\nperformantly rendered',
-      rotateLabel: true,
-      labelPlacement: PolygonLabelPlacement.centroid,
-      labelStyle: const TextStyle(color: Colors.black),
-      hitValue: (
-        title: 'Polygon With Hole',
-        subtitle: 'A bit like Swiss cheese maybe?',
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(50, -18),
-        LatLng(53, -16),
-        LatLng(51.5, -12.5),
-        LatLng(54, -14),
-        LatLng(54, -18),
-      ]
-          .map((latlng) => LatLng(latlng.latitude - 6, latlng.longitude + 8))
-          .toList(),
-      pattern: const StrokePattern.dotted(),
-      holePointsList: [
-        const [
-          LatLng(46, -9),
-          LatLng(46, -8),
-          LatLng(45.5, -7.5),
-          LatLng(45, -8),
-          LatLng(45, -9),
-        ].reversed.toList(growable: false), // Testing winding consitency
-        const [
-          LatLng(47.5, -9),
-          LatLng(47.5, -8),
-          LatLng(47, -7),
-          LatLng(46.25, -7),
-          LatLng(46.25, -8),
-          LatLng(46.75, -9),
-        ].reversed.toList(growable: false),
-        const [
-          LatLng(46.683614, -8.141285),
-          LatLng(45.663083, -8.684529),
-          LatLng(45.913924, -7.2193),
-        ].reversed.toList(growable: false),
-      ],
-      borderStrokeWidth: 4,
-      borderColor: Colors.orange,
-      color: Colors.orange.withAlpha(128),
-      label: 'This one is not\nperformantly rendered',
-      rotateLabel: true,
-      labelPlacement: PolygonLabelPlacement.centroid,
-      labelStyle: const TextStyle(color: Colors.black),
-      hitValue: (
-        title: 'Polygon With Hole & Self Intersection',
-        subtitle: 'This one still works with performant rendering',
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(61.861042, 0.946502),
-        LatLng(61.861458, 0.949468),
-        LatLng(61.861427, 0.949626),
-        LatLng(61.859015, 0.951513),
-        LatLng(61.858129, 0.952652)
-      ],
-      holePointsList: [],
-      color: Colors.lightGreen.withAlpha(128),
-      borderColor: Colors.lightGreen.withAlpha(128),
-      borderStrokeWidth: 10,
-      hitValue: (
-        title: 'Testing opacity treatment (small)',
-        subtitle:
-            "Holes shouldn't be cut, and colors should be mixed correctly",
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(61.861042, 0.946502),
-        LatLng(61.861458, 0.949468),
-        LatLng(61.861427, 0.949626),
-        LatLng(61.859015, 0.951513),
-        LatLng(61.858129, 0.952652),
-        LatLng(61.857633, 0.953214),
-        LatLng(61.855842, 0.954683),
-        LatLng(61.855769, 0.954692),
-        LatLng(61.855679, 0.954565),
-        LatLng(61.855417, 0.953926),
-        LatLng(61.855268, 0.953431),
-        LatLng(61.855173, 0.952443),
-        LatLng(61.855161, 0.951147),
-        LatLng(61.855222, 0.950822),
-        LatLng(61.855928, 0.948422),
-        LatLng(61.856365, 0.946638),
-        LatLng(61.856456, 0.946586),
-        LatLng(61.856787, 0.946656),
-        LatLng(61.857578, 0.946675),
-        LatLng(61.859338, 0.946453),
-        LatLng(61.861042, 0.946502)
-      ],
-      holePointsList: const [
-        [
-          LatLng(61.858881, 0.947234),
-          LatLng(61.858728, 0.947126),
-          LatLng(61.858562, 0.947132),
-          LatLng(61.858458, 0.947192),
-          LatLng(61.85844, 0.947716),
-          LatLng(61.858488, 0.947819),
-          LatLng(61.858766, 0.947818),
-          LatLng(61.858893, 0.947779),
-          LatLng(61.858975, 0.947542),
-          LatLng(61.858881, 0.947234)
-        ]
-      ],
-      color: Colors.lightGreen.withAlpha(128),
-      borderColor: Colors.lightGreen.withAlpha(128),
-      borderStrokeWidth: 10,
-      hitValue: (
-        title: 'Testing opacity treatment (large)',
-        subtitle:
-            "Holes shouldn't be cut, and colors should be mixed correctly",
-      ),
-    ),
-    Polygon(
-      points: const [
-        LatLng(40, 150),
-        LatLng(45, 160),
-        LatLng(50, 170),
-        LatLng(55, 180),
-        LatLng(50, -170),
-        LatLng(45, -160),
-        LatLng(40, -150),
-        LatLng(35, -160),
-        LatLng(30, -170),
-        LatLng(25, -180),
-        LatLng(30, 170),
-        LatLng(35, 160),
-      ],
-      holePointsList: const [
-        [
-          LatLng(45, 175),
-          LatLng(45, -175),
-          LatLng(35, -175),
-          LatLng(35, 175),
-        ],
-      ],
-      color: const Color(0xFFFF0000),
-      hitValue: (
-        title: 'Red Line',
-        subtitle: 'Across the universe...',
-      ),
-    ),
-  ];
-  late final _polygons =
-      Map.fromEntries(_polygonsRaw.map((e) => MapEntry(e.hitValue, e)));
+  late var _polygonsRaw = generatePolygons();
+  List<Polygon<HitValue>> generatePolygons() => [
+        Polygon(
+          points: const [
+            LatLng(51.5, -0.09),
+            LatLng(53.3498, -6.2603),
+            LatLng(48.8566, 2.3522),
+          ],
+          borderColor: Colors.red,
+          borderStrokeWidth: 4,
+          hitValue: (
+            title: 'Basic Unfilled Polygon',
+            subtitle: 'Nothing really special here...',
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(55.5, -0.09),
+            LatLng(54.3498, -6.2603),
+            LatLng(52.8566, 2.3522),
+          ],
+          color: Colors.purple,
+          borderColor: Colors.yellow,
+          borderStrokeWidth: 4,
+          label: 'Label!',
+          labelPlacementCalculator: _labelPlacementCalculator,
+          hitValue: (
+            title: 'Basic Filled Polygon',
+            subtitle: 'Nothing really special here...',
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(46.35, 4.94),
+            LatLng(46.22, -0.11),
+            LatLng(44.399, 1.76),
+          ],
+          pattern: StrokePattern.dashed(segments: const [50, 20]),
+          borderStrokeWidth: 4,
+          borderColor: Colors.lightBlue,
+          color: Colors.yellow,
+          hitValue: (
+            title: 'Polygon With Dashed Borders',
+            subtitle: '...',
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(60.16, -9.38),
+            LatLng(60.16, -4.16),
+            LatLng(61.18, -4.16),
+            LatLng(61.18, -9.38),
+          ],
+          borderStrokeWidth: 4,
+          borderColor: Colors.purple,
+          label: 'Label!',
+          labelPlacementCalculator: _labelPlacementCalculator,
+          hitValue: (
+            title: 'Polygon With Label',
+            subtitle: 'This is a very descriptive label!',
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(59.77, -10.28),
+            LatLng(58.21, -10.28),
+            LatLng(58.21, -7.01),
+            LatLng(59.77, -7.01),
+            LatLng(60.77, -6.01),
+          ],
+          borderStrokeWidth: 4,
+          borderColor: Colors.purple,
+          label: 'Rotated!',
+          rotateLabel: true,
+          labelPlacementCalculator: _labelPlacementCalculator,
+          hitValue: (
+            title: 'Polygon With Rotated Label',
+            subtitle: "Now you don't have to turn your head so much",
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(50, -18),
+            LatLng(50, -14),
+            LatLng(51.5, -12.5),
+            LatLng(54, -14),
+            LatLng(54, -18),
+          ]
+              .map((latlng) => LatLng(latlng.latitude, latlng.longitude + 8))
+              .toList(),
+          pattern: const StrokePattern.dotted(),
+          holePointsList: [
+            const [
+              LatLng(52, -9),
+              LatLng(52, -8),
+              LatLng(51.5, -7.5),
+              LatLng(51, -8),
+              LatLng(51, -9),
+            ],
+            const [
+              LatLng(53.5, -9),
+              LatLng(53.5, -8),
+              LatLng(53, -7),
+              LatLng(52.25, -7),
+              LatLng(52.25, -8),
+              LatLng(52.75, -9),
+            ],
+            const [
+              LatLng(52.683614, -8.141285),
+              LatLng(51.663083, -8.684529),
+              LatLng(51.913924, -7.2193),
+            ],
+          ],
+          borderStrokeWidth: 4,
+          borderColor: Colors.orange,
+          color: Colors.orange.withAlpha(128),
+          label: 'This one is not\nperformantly rendered',
+          rotateLabel: true,
+          labelPlacementCalculator: _labelPlacementCalculator,
+          labelStyle: const TextStyle(color: Colors.black),
+          hitValue: (
+            title: 'Polygon With Hole',
+            subtitle: 'A bit like Swiss cheese maybe?',
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(50, -18),
+            LatLng(53, -16),
+            LatLng(51.5, -12.5),
+            LatLng(54, -14),
+            LatLng(54, -18),
+          ]
+              .map(
+                  (latlng) => LatLng(latlng.latitude - 6, latlng.longitude + 8))
+              .toList(),
+          pattern: const StrokePattern.dotted(),
+          holePointsList: [
+            const [
+              LatLng(46, -9),
+              LatLng(46, -8),
+              LatLng(45.5, -7.5),
+              LatLng(45, -8),
+              LatLng(45, -9),
+            ].reversed.toList(growable: false), // Testing winding consitency
+            const [
+              LatLng(47.5, -9),
+              LatLng(47.5, -8),
+              LatLng(47, -7),
+              LatLng(46.25, -7),
+              LatLng(46.25, -8),
+              LatLng(46.75, -9),
+            ].reversed.toList(growable: false),
+            const [
+              LatLng(46.683614, -8.141285),
+              LatLng(45.663083, -8.684529),
+              LatLng(45.913924, -7.2193),
+            ].reversed.toList(growable: false),
+          ],
+          borderStrokeWidth: 4,
+          borderColor: Colors.orange,
+          color: Colors.orange.withAlpha(128),
+          label: 'This one is not\nperformantly rendered',
+          rotateLabel: true,
+          labelPlacementCalculator: _labelPlacementCalculator,
+          labelStyle: const TextStyle(color: Colors.black),
+          hitValue: (
+            title: 'Polygon With Hole & Self Intersection',
+            subtitle: 'This one still works with performant rendering',
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(61.861042, 0.946502),
+            LatLng(61.861458, 0.949468),
+            LatLng(61.861427, 0.949626),
+            LatLng(61.859015, 0.951513),
+            LatLng(61.858129, 0.952652)
+          ],
+          holePointsList: [],
+          color: Colors.lightGreen.withAlpha(128),
+          borderColor: Colors.lightGreen.withAlpha(128),
+          borderStrokeWidth: 10,
+          hitValue: (
+            title: 'Testing opacity treatment (small)',
+            subtitle:
+                "Holes shouldn't be cut, and colors should be mixed correctly",
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(61.861042, 0.946502),
+            LatLng(61.861458, 0.949468),
+            LatLng(61.861427, 0.949626),
+            LatLng(61.859015, 0.951513),
+            LatLng(61.858129, 0.952652),
+            LatLng(61.857633, 0.953214),
+            LatLng(61.855842, 0.954683),
+            LatLng(61.855769, 0.954692),
+            LatLng(61.855679, 0.954565),
+            LatLng(61.855417, 0.953926),
+            LatLng(61.855268, 0.953431),
+            LatLng(61.855173, 0.952443),
+            LatLng(61.855161, 0.951147),
+            LatLng(61.855222, 0.950822),
+            LatLng(61.855928, 0.948422),
+            LatLng(61.856365, 0.946638),
+            LatLng(61.856456, 0.946586),
+            LatLng(61.856787, 0.946656),
+            LatLng(61.857578, 0.946675),
+            LatLng(61.859338, 0.946453),
+            LatLng(61.861042, 0.946502)
+          ],
+          holePointsList: const [
+            [
+              LatLng(61.858881, 0.947234),
+              LatLng(61.858728, 0.947126),
+              LatLng(61.858562, 0.947132),
+              LatLng(61.858458, 0.947192),
+              LatLng(61.85844, 0.947716),
+              LatLng(61.858488, 0.947819),
+              LatLng(61.858766, 0.947818),
+              LatLng(61.858893, 0.947779),
+              LatLng(61.858975, 0.947542),
+              LatLng(61.858881, 0.947234)
+            ]
+          ],
+          color: Colors.lightGreen.withAlpha(128),
+          borderColor: Colors.lightGreen.withAlpha(128),
+          borderStrokeWidth: 10,
+          hitValue: (
+            title: 'Testing opacity treatment (large)',
+            subtitle:
+                "Holes shouldn't be cut, and colors should be mixed correctly",
+          ),
+        ),
+        Polygon(
+          points: const [
+            LatLng(40, 150),
+            LatLng(45, 160),
+            LatLng(50, 170),
+            LatLng(55, 180),
+            LatLng(50, -170),
+            LatLng(45, -160),
+            LatLng(40, -150),
+            LatLng(35, -160),
+            LatLng(30, -170),
+            LatLng(25, -180),
+            LatLng(30, 170),
+            LatLng(35, 160),
+          ],
+          holePointsList: const [
+            [
+              LatLng(45, 175),
+              LatLng(45, -175),
+              LatLng(35, -175),
+              LatLng(35, 175),
+            ],
+          ],
+          color: const Color(0xFFFF0000),
+          hitValue: (
+            title: 'Red Line',
+            subtitle: 'Across the universe...',
+          ),
+        ),
+      ];
 
   @override
   Widget build(BuildContext context) {
@@ -311,7 +318,8 @@ class _PolygonPageState extends State<PolygonPage> {
                   _prevHitValues = hitValues;
 
                   final hoverLines = hitValues.map((v) {
-                    final original = _polygons[v]!;
+                    final original =
+                        _polygonsRaw.singleWhere((p) => p.hitValue == v);
 
                     return Polygon<HitValue>(
                       points: original.points,
@@ -430,70 +438,142 @@ class _PolygonPageState extends State<PolygonPage> {
           ),
           Positioned(
             top: 16,
+            left: 16,
             right: 16,
-            child: ClipRRect(
-              borderRadius: BorderRadius.circular(kIsWeb ? 16 : 32),
-              child: ColoredBox(
-                color: Theme.of(context).colorScheme.surface,
-                child: Column(
-                  children: [
-                    Padding(
-                      padding: const EdgeInsets.only(
-                        left: 12,
-                        right: 8,
-                        top: 4,
-                        bottom: 4,
+            child: Column(
+              spacing: 8,
+              crossAxisAlignment: CrossAxisAlignment.end,
+              children: [
+                Container(
+                  decoration: BoxDecoration(
+                    color: Theme.of(context).colorScheme.surface,
+                    borderRadius: BorderRadius.circular(26),
+                  ),
+                  padding:
+                      const EdgeInsets.symmetric(vertical: 4, horizontal: 16),
+                  child: Wrap(
+                    alignment: WrapAlignment.end,
+                    crossAxisAlignment: WrapCrossAlignment.center,
+                    spacing: 8,
+                    runSpacing: 8,
+                    children: [
+                      const Tooltip(
+                        message: 'Label Placement',
+                        child: Icon(Icons.format_align_center),
                       ),
-                      child: Row(
-                        mainAxisSize: MainAxisSize.max,
-                        spacing: 8,
-                        mainAxisAlignment: MainAxisAlignment.center,
-                        children: [
-                          const Tooltip(
-                            message: 'Use Inverted Fill',
-                            child: Icon(Icons.invert_colors),
-                          ),
-                          Switch.adaptive(
-                            value: _useInvertedFill,
-                            onChanged: (v) =>
-                                setState(() => _useInvertedFill = v),
-                          ),
-                        ],
+                      ChoiceChip(
+                        label: const Text('Simple Centroid'),
+                        selected: _labelPlacementCalculator ==
+                            const PolygonLabelPlacementCalculator
+                                .simpleCentroid(),
+                        shape: const StadiumBorder(),
+                        onSelected: (v) {
+                          if (v) {
+                            setState(() {
+                              _labelPlacementCalculator =
+                                  const PolygonLabelPlacementCalculator
+                                      .simpleCentroid();
+                              _polygonsRaw = generatePolygons();
+                            });
+                          }
+                        },
+                      ),
+                      ChoiceChip(
+                        label: const Text('Centroid'),
+                        selected: _labelPlacementCalculator ==
+                            const PolygonLabelPlacementCalculator.centroid(),
+                        shape: const StadiumBorder(),
+                        onSelected: (v) {
+                          if (v) {
+                            setState(() {
+                              _labelPlacementCalculator =
+                                  const PolygonLabelPlacementCalculator
+                                      .centroid();
+                              _polygonsRaw = generatePolygons();
+                            });
+                          }
+                        },
                       ),
+                      ChoiceChip(
+                        label: const Text('Polylabel'),
+                        selected: _labelPlacementCalculator ==
+                            const PolygonLabelPlacementCalculator.polylabel(),
+                        shape: const StadiumBorder(),
+                        onSelected: (v) {
+                          if (v) {
+                            setState(() {
+                              _labelPlacementCalculator =
+                                  const PolygonLabelPlacementCalculator
+                                      .polylabel();
+                              _polygonsRaw = generatePolygons();
+                            });
+                          }
+                        },
+                      ),
+                    ],
+                  ),
+                ),
+                FittedBox(
+                  child: DecoratedBox(
+                    decoration: BoxDecoration(
+                      borderRadius: BorderRadius.circular(kIsWeb ? 16 : 32),
+                      color: Theme.of(context).colorScheme.surface,
                     ),
-                    if (kIsWeb)
-                      ColoredBox(
-                        color: Colors.amber,
-                        child: Padding(
+                    child: Column(
+                      children: [
+                        Padding(
                           padding: const EdgeInsets.only(
-                            left: 16,
-                            right: 16,
-                            top: 6,
-                            bottom: 6,
-                          ),
+                              left: 12, right: 8, top: 4, bottom: 4),
                           child: Row(
-                            mainAxisSize: MainAxisSize.min,
+                            mainAxisSize: MainAxisSize.max,
                             spacing: 8,
+                            mainAxisAlignment: MainAxisAlignment.end,
                             children: [
-                              const Icon(Icons.warning),
-                              const Icon(Icons.web_asset_off),
-                              IconButton(
-                                onPressed: () => launchUrl(Uri.parse(
-                                  'https://docs.fleaflet.dev/layers/polygon-layer#inverted-filling',
-                                )),
-                                style: ButtonStyle(
-                                  backgroundColor:
-                                      WidgetStatePropertyAll(Colors.amber[100]),
-                                ),
-                                icon: const Icon(Icons.open_in_new),
+                              const Tooltip(
+                                message: 'Use Inverted Fill',
+                                child: Icon(Icons.invert_colors),
+                              ),
+                              Switch.adaptive(
+                                value: _useInvertedFill,
+                                onChanged: (v) =>
+                                    setState(() => _useInvertedFill = v),
                               ),
                             ],
                           ),
                         ),
-                      ),
-                  ],
+                        if (kIsWeb)
+                          Container(
+                            decoration: BoxDecoration(
+                              color: Colors.amber,
+                              borderRadius:
+                                  BorderRadius.circular(kIsWeb ? 16 : 32),
+                            ),
+                            padding: const EdgeInsets.only(
+                                left: 16, right: 16, top: 6, bottom: 6),
+                            child: Row(
+                              mainAxisSize: MainAxisSize.min,
+                              spacing: 8,
+                              children: [
+                                const Icon(Icons.warning),
+                                const Icon(Icons.web_asset_off),
+                                IconButton(
+                                  onPressed: () => launchUrl(Uri.parse(
+                                    'https://docs.fleaflet.dev/layers/polygon-layer#inverted-filling',
+                                  )),
+                                  style: ButtonStyle(
+                                    backgroundColor: WidgetStatePropertyAll(
+                                        Colors.amber[100]),
+                                  ),
+                                  icon: const Icon(Icons.open_in_new),
+                                ),
+                              ],
+                            ),
+                          ),
+                      ],
+                    ),
+                  ),
                 ),
-              ),
+              ],
             ),
           ),
         ],
diff --git a/example/lib/pages/polygon_perf_stress.dart b/example/lib/pages/polygon_perf_stress.dart
index d2b0bd131..bbc90c2a4 100644
--- a/example/lib/pages/polygon_perf_stress.dart
+++ b/example/lib/pages/polygon_perf_stress.dart
@@ -129,9 +129,6 @@ class _PolygonPerfStressPageState extends State<PolygonPerfStressPage> {
                           ),
                         ),
                       ),
-                      // Not ideal that we have to re-parse the GeoJson every
-                      // time this is changed, but the library gives no easy
-                      // way to change it after
                       UnconstrainedBox(
                         child: Container(
                           decoration: BoxDecoration(
diff --git a/example/lib/pages/polyline.dart b/example/lib/pages/polyline.dart
index 94b38ba26..0c085bb95 100644
--- a/example/lib/pages/polyline.dart
+++ b/example/lib/pages/polyline.dart
@@ -173,68 +173,64 @@ class _PolylinePageState extends State<PolylinePage> {
     return Scaffold(
       appBar: AppBar(title: const Text('Polylines')),
       drawer: const MenuDrawer(PolylinePage.route),
-      body: Stack(
+      body: FlutterMap(
+        options: const MapOptions(
+          initialCenter: LatLng(51.5, -0.09),
+          initialZoom: 5,
+        ),
         children: [
-          FlutterMap(
-            options: const MapOptions(
-              initialCenter: LatLng(51.5, -0.09),
-              initialZoom: 5,
-            ),
-            children: [
-              openStreetMapTileLayer,
-              MouseRegion(
-                hitTestBehavior: HitTestBehavior.deferToChild,
-                cursor: SystemMouseCursors.click,
-                onHover: (_) {
-                  final hitValues = _hitNotifier.value?.hitValues.toList();
-                  if (hitValues == null) return;
+          openStreetMapTileLayer,
+          MouseRegion(
+            hitTestBehavior: HitTestBehavior.deferToChild,
+            cursor: SystemMouseCursors.click,
+            onHover: (_) {
+              final hitValues = _hitNotifier.value?.hitValues.toList();
+              if (hitValues == null) return;
 
-                  if (listEquals(hitValues, _prevHitValues)) return;
-                  _prevHitValues = hitValues;
+              if (listEquals(hitValues, _prevHitValues)) return;
+              _prevHitValues = hitValues;
 
-                  final hoverLines = hitValues.map((v) {
-                    final original = _polylines[v]!;
+              final hoverLines = hitValues.map((v) {
+                final original = _polylines[v]!;
 
-                    return Polyline<HitValue>(
-                      points: original.points,
-                      strokeWidth:
-                          original.strokeWidth + original.borderStrokeWidth,
-                      color: Colors.transparent,
-                      borderStrokeWidth: 15,
-                      borderColor: Colors.green,
-                      useStrokeWidthInMeter: original.useStrokeWidthInMeter,
-                    );
-                  }).toList();
-                  setState(() => _hoverLines = hoverLines);
-                },
-                onExit: (_) {
-                  _prevHitValues = null;
-                  setState(() => _hoverLines = null);
-                },
-                child: GestureDetector(
-                  onTap: () => _openTouchedLinesModal(
-                    'Tapped',
-                    _hitNotifier.value!.hitValues,
-                    _hitNotifier.value!.coordinate,
-                  ),
-                  onLongPress: () => _openTouchedLinesModal(
-                    'Long pressed',
-                    _hitNotifier.value!.hitValues,
-                    _hitNotifier.value!.coordinate,
-                  ),
-                  onSecondaryTap: () => _openTouchedLinesModal(
-                    'Secondary tapped',
-                    _hitNotifier.value!.hitValues,
-                    _hitNotifier.value!.coordinate,
-                  ),
-                  child: PolylineLayer(
-                    hitNotifier: _hitNotifier,
-                    simplificationTolerance: 0,
-                    polylines: [..._polylinesRaw, ...?_hoverLines],
-                  ),
-                ),
+                return Polyline<HitValue>(
+                  points: original.points,
+                  strokeWidth:
+                      original.strokeWidth + original.borderStrokeWidth,
+                  color: Colors.transparent,
+                  borderStrokeWidth: 15,
+                  borderColor: Colors.green,
+                  useStrokeWidthInMeter: original.useStrokeWidthInMeter,
+                );
+              }).toList();
+              setState(() => _hoverLines = hoverLines);
+            },
+            onExit: (_) {
+              _prevHitValues = null;
+              setState(() => _hoverLines = null);
+            },
+            child: GestureDetector(
+              onTap: () => _openTouchedLinesModal(
+                'Tapped',
+                _hitNotifier.value!.hitValues,
+                _hitNotifier.value!.coordinate,
               ),
-            ],
+              onLongPress: () => _openTouchedLinesModal(
+                'Long pressed',
+                _hitNotifier.value!.hitValues,
+                _hitNotifier.value!.coordinate,
+              ),
+              onSecondaryTap: () => _openTouchedLinesModal(
+                'Secondary tapped',
+                _hitNotifier.value!.hitValues,
+                _hitNotifier.value!.coordinate,
+              ),
+              child: PolylineLayer(
+                hitNotifier: _hitNotifier,
+                simplificationTolerance: 0,
+                polylines: [..._polylinesRaw, ...?_hoverLines],
+              ),
+            ),
           ),
         ],
       ),
diff --git a/example/lib/pages/repeated_worlds.dart b/example/lib/pages/repeated_worlds.dart
index ffd6fed90..ee74b74df 100644
--- a/example/lib/pages/repeated_worlds.dart
+++ b/example/lib/pages/repeated_worlds.dart
@@ -147,8 +147,9 @@ class _RepeatedWorldsPageState extends State<RepeatedWorldsPage> {
                       label: 'Aloha!',
                       labelStyle:
                           const TextStyle(color: Colors.green, fontSize: 40),
-                      labelPlacement:
-                          PolygonLabelPlacement.centroidWithMultiWorld,
+                      labelPlacementCalculator:
+                          const PolygonLabelPlacementCalculator
+                              .simpleMultiWorldCentroid(),
                       rotateLabel: false,
                       points: const [
                         LatLng(40, 149),
diff --git a/lib/flutter_map.dart b/lib/flutter_map.dart
index acb68995f..91ca2397a 100644
--- a/lib/flutter_map.dart
+++ b/lib/flutter_map.dart
@@ -30,6 +30,8 @@ export 'package:flutter_map/src/layer/attribution_layer/simple.dart';
 export 'package:flutter_map/src/layer/circle_layer/circle_layer.dart';
 export 'package:flutter_map/src/layer/marker_layer/marker_layer.dart';
 export 'package:flutter_map/src/layer/overlay_image_layer/overlay_image_layer.dart';
+export 'package:flutter_map/src/layer/polygon_layer/label/deprecated_placements.dart';
+export 'package:flutter_map/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart';
 export 'package:flutter_map/src/layer/polygon_layer/polygon_layer.dart';
 export 'package:flutter_map/src/layer/polyline_layer/polyline_layer.dart';
 export 'package:flutter_map/src/layer/scalebar/scalebar.dart';
diff --git a/lib/src/layer/polygon_layer/label.dart b/lib/src/layer/polygon_layer/label.dart
deleted file mode 100644
index 645c1e675..000000000
--- a/lib/src/layer/polygon_layer/label.dart
+++ /dev/null
@@ -1,204 +0,0 @@
-part of 'polygon_layer.dart';
-
-void Function(Canvas canvas)? _buildLabelTextPainter({
-  required Size mapSize,
-  required Offset placementPoint,
-  required ({Offset min, Offset max}) bounds,
-  required TextPainter textPainter,
-  required double rotationRad,
-  required bool rotate,
-  required double padding,
-}) {
-  final dx = placementPoint.dx;
-  final dy = placementPoint.dy;
-  final width = textPainter.width;
-  final height = textPainter.height;
-
-  // Cull labels where the polygon is still on the map but the label would not be.
-  // Currently this is only enabled when the map isn't rotated, since the placementOffset
-  // is relative to the MobileLayerTransformer rather than in actual screen coordinates.
-  final double textWidth;
-  final double textHeight;
-  final double mapWidth;
-  final double mapHeight;
-  if (rotationRad == 0) {
-    textWidth = width;
-    textHeight = height;
-    mapWidth = mapSize.width;
-    mapHeight = mapSize.height;
-  } else {
-    // lazily we imagine the worst case scenario regarding sizes, instead of
-    // computing the angles
-    textWidth = textHeight = max(width, height);
-    mapWidth = mapHeight = max(mapSize.width, mapSize.height);
-  }
-  if (dx + textWidth / 2 < 0 || dx - textWidth / 2 > mapWidth) {
-    return null;
-  }
-  if (dy + textHeight / 2 < 0 || dy - textHeight / 2 > mapHeight) {
-    return null;
-  }
-
-  // Note: I'm pretty sure this doesn't work for concave shapes. It would be more
-  // correct to evaluate the width of the polygon at the height of the label.
-  if (bounds.max.dx - bounds.min.dx - padding > width) {
-    return (canvas) {
-      if (rotate) {
-        canvas.save();
-        canvas.translate(dx, dy);
-        canvas.rotate(-rotationRad);
-        canvas.translate(-dx, -dy);
-      }
-
-      textPainter.paint(
-        canvas,
-        Offset(
-          dx - width / 2,
-          dy - height / 2,
-        ),
-      );
-
-      if (rotate) {
-        canvas.restore();
-      }
-    };
-  }
-  return null;
-}
-
-/// Calculate the [LatLng] position for the given [PolygonLabelPlacement].
-LatLng _computeLabelPosition(
-  PolygonLabelPlacement labelPlacement,
-  List<LatLng> points,
-) {
-  return switch (labelPlacement) {
-    PolygonLabelPlacement.centroid => _computeCentroid(points),
-    PolygonLabelPlacement.centroidWithMultiWorld =>
-      _computeCentroidWithMultiWorld(points),
-    PolygonLabelPlacement.polylabel => _computePolylabel(points),
-  };
-}
-
-/// Calculate the centroid of a given list of [LatLng] points.
-LatLng _computeCentroid(List<LatLng> points) {
-  if (points.isEmpty) {
-    throw ArgumentError('Polygon must contain at least one point');
-  }
-
-  if (points.length == 1) {
-    return points[0];
-  }
-
-  double signedArea = 0;
-  double centroidX = 0;
-  double centroidY = 0;
-
-  // For all vertices except last
-  for (int i = 0; i < points.length - 1; i++) {
-    final double x0 = points[i].longitude;
-    final double y0 = points[i].latitude;
-    final double x1 = points[i + 1].longitude;
-    final double y1 = points[i + 1].latitude;
-
-    // Calculate signed area contribution of current vertex
-    final double a = x0 * y1 - x1 * y0;
-    signedArea += a;
-
-    // Accumulate centroid components weighted by signed area
-    centroidX += (x0 + x1) * a;
-    centroidY += (y0 + y1) * a;
-  }
-
-  // Close the polygon by connecting last vertex to first
-  final double x0 = points.last.longitude;
-  final double y0 = points.last.latitude;
-  final double x1 = points.first.longitude;
-  final double y1 = points.first.latitude;
-  final double a = x0 * y1 - x1 * y0;
-  signedArea += a;
-  centroidX += (x0 + x1) * a;
-  centroidY += (y0 + y1) * a;
-
-  // Complete the signed area calculation
-  signedArea *= 0.5;
-
-  // Calculate centroid coordinates
-  centroidX /= 6 * signedArea;
-  centroidY /= 6 * signedArea;
-
-  // Handle special case of zero area (collinear points)
-  if (signedArea == 0) {
-    // Default to average of all points
-    double sumX = 0;
-    double sumY = 0;
-    for (final point in points) {
-      sumX += point.longitude;
-      sumY += point.latitude;
-    }
-    return LatLng(sumY / points.length, sumX / points.length);
-  }
-
-  return LatLng(centroidY, centroidX);
-}
-
-/// Calculate the centroid of a given list of [LatLng] points with multiple worlds.
-LatLng _computeCentroidWithMultiWorld(List<LatLng> points) {
-  if (points.isEmpty) return _computeCentroid(points);
-  const halfWorld = 180;
-  int count = 0;
-  double sum = 0;
-  late double lastLng;
-  for (final LatLng point in points) {
-    double lng = point.longitude;
-    count++;
-    if (count > 1) {
-      if (lng - lastLng > halfWorld) {
-        lng -= 2 * halfWorld;
-      } else if (lng - lastLng < -halfWorld) {
-        lng += 2 * halfWorld;
-      }
-    }
-    lastLng = lng;
-    sum += lastLng;
-  }
-  return LatLng(points.map((e) => e.latitude).average, sum / count);
-}
-
-/// Use the Maxbox Polylabel algorithm to calculate the [LatLng] position for
-/// a given list of points.
-LatLng _computePolylabel(List<LatLng> points) {
-  final labelPosition = polylabel(
-    [
-      List<Point<double>>.generate(points.length,
-          (i) => Point<double>(points[i].longitude, points[i].latitude)),
-    ],
-    // "precision" is a bit of a misnomer. It's a threshold for when to stop
-    // dividing-and-conquering the polygon in the hopes of finding a better
-    // point with more distance to the polygon's outline. It's given in
-    // point-units, i.e. degrees here. A bigger number means less precision,
-    // i.e. cheaper at the expense off less optimal label placement.
-    // TODO: Make this an external option
-    precision: 0.0001,
-  );
-  return LatLng(
-    labelPosition.point.y.toDouble(),
-    labelPosition.point.x.toDouble(),
-  );
-}
-
-/// Defines the algorithm used to calculate the position of the [Polygon] label.
-///
-/// > [!IMPORTANT]
-/// > If your project allows users to browse across multiple worlds, and your
-/// > polygons may be over the anti-meridan boundary, [centroidWithMultiWorld]
-/// > must be used - other algorithms will produce unexpected results.
-enum PolygonLabelPlacement {
-  /// Use the centroid of the [Polygon] outline as position for the label.
-  centroid,
-
-  /// Use the centroid in a multi-world as position for the label.
-  centroidWithMultiWorld,
-
-  /// Use the Mapbox Polylabel algorithm as position for the label.
-  polylabel,
-}
diff --git a/lib/src/layer/polygon_layer/label/build_text_painter.dart b/lib/src/layer/polygon_layer/label/build_text_painter.dart
new file mode 100644
index 000000000..82dfb31e4
--- /dev/null
+++ b/lib/src/layer/polygon_layer/label/build_text_painter.dart
@@ -0,0 +1,67 @@
+part of '../polygon_layer.dart';
+
+void Function(Canvas canvas)? _buildLabelTextPainter({
+  required Size mapSize,
+  required Offset placementPoint,
+  required ({Offset min, Offset max}) bounds,
+  required TextPainter textPainter,
+  required double rotationRad,
+  required bool rotate,
+  required double padding,
+}) {
+  final dx = placementPoint.dx;
+  final dy = placementPoint.dy;
+  final width = textPainter.width;
+  final height = textPainter.height;
+
+  // Cull labels where the polygon is still on the map but the label would not be.
+  // Currently this is only enabled when the map isn't rotated, since the placementOffset
+  // is relative to the MobileLayerTransformer rather than in actual screen coordinates.
+  final double textWidth;
+  final double textHeight;
+  final double mapWidth;
+  final double mapHeight;
+  if (rotationRad == 0) {
+    textWidth = width;
+    textHeight = height;
+    mapWidth = mapSize.width;
+    mapHeight = mapSize.height;
+  } else {
+    // lazily we imagine the worst case scenario regarding sizes, instead of
+    // computing the angles
+    textWidth = textHeight = max(width, height);
+    mapWidth = mapHeight = max(mapSize.width, mapSize.height);
+  }
+  if (dx + textWidth / 2 < 0 || dx - textWidth / 2 > mapWidth) {
+    return null;
+  }
+  if (dy + textHeight / 2 < 0 || dy - textHeight / 2 > mapHeight) {
+    return null;
+  }
+
+  // Note: I'm pretty sure this doesn't work for concave shapes. It would be more
+  // correct to evaluate the width of the polygon at the height of the label.
+  if (bounds.max.dx - bounds.min.dx - padding > width) {
+    return (canvas) {
+      if (rotate) {
+        canvas.save();
+        canvas.translate(dx, dy);
+        canvas.rotate(-rotationRad);
+        canvas.translate(-dx, -dy);
+      }
+
+      textPainter.paint(
+        canvas,
+        Offset(
+          dx - width / 2,
+          dy - height / 2,
+        ),
+      );
+
+      if (rotate) {
+        canvas.restore();
+      }
+    };
+  }
+  return null;
+}
diff --git a/lib/src/layer/polygon_layer/label/deprecated_placements.dart b/lib/src/layer/polygon_layer/label/deprecated_placements.dart
new file mode 100644
index 000000000..1214f7eed
--- /dev/null
+++ b/lib/src/layer/polygon_layer/label/deprecated_placements.dart
@@ -0,0 +1,48 @@
+import 'package:flutter_map/flutter_map.dart';
+
+/// Defines the algorithm used to calculate the position of the [Polygon] label.
+///
+/// > [!IMPORTANT]
+/// > If polygons may be over the anti-meridan boundary,
+/// > [PolygonLabelPlacementCalculator.simpleMultiWorldCentroid] must be used -
+/// > other calculators will produce unexpected results.
+@Deprecated(
+  'Use `Polygon.labelPlacementCalculator` with the equivalent calculator '
+  'instead. '
+  'This enables more flexibility and extensibility. '
+  'This was deprecated after v8.2.0, and will be removed in a future version.',
+)
+enum PolygonLabelPlacement {
+  /// Alias for [PolygonLabelPlacementCalculator.centroid]
+  ///
+  /// The new [PolygonLabelPlacementCalculator.centroid] algorithm has differing
+  /// behaviour than the old one. To remain using the existing behaviour, use
+  /// [PolygonLabelPlacementCalculator.simpleCentroid].
+  @Deprecated(
+    'Use `Polygon.labelPlacementCalculator` with '
+    '`const PolygonLabelPlacementCalculator.simpleCentroid()` or `.centroid()` '
+    'instead. '
+    'This enables more flexibility and extensibility. '
+    'This was deprecated after v8.2.0, and will be removed in a future version.',
+  )
+  centroid,
+
+  /// Alias for [PolygonLabelPlacementCalculator.simpleMultiWorldCentroid]
+  @Deprecated(
+    'Use `Polygon.labelPlacementCalculator` with '
+    '`const PolygonLabelPlacementCalculator.simpleMultiWorldCentroid()` '
+    'instead. '
+    'This enables more flexibility and extensibility. '
+    'This was deprecated after v8.2.0, and will be removed in a future version.',
+  )
+  centroidWithMultiWorld,
+
+  /// Alias for [PolygonLabelPlacementCalculator.polylabel]
+  @Deprecated(
+    'Use `Polygon.labelPlacementCalculator` with '
+    '`const PolygonLabelPlacementCalculator.polylabel()` instead. '
+    'This enables more flexibility and extensibility. '
+    'This was deprecated after v8.2.0, and will be removed in a future version.',
+  )
+  polylabel,
+}
diff --git a/lib/src/layer/polygon_layer/label/placement_calculators/centroid.dart b/lib/src/layer/polygon_layer/label/placement_calculators/centroid.dart
new file mode 100644
index 000000000..de25f497a
--- /dev/null
+++ b/lib/src/layer/polygon_layer/label/placement_calculators/centroid.dart
@@ -0,0 +1,75 @@
+part of 'placement_calculator.dart';
+
+/// {@template fm.polygonLabelPlacementCalculator.centroid}
+/// Places the [Polygon.label] at the centroid calculated using the
+/// [signed area formula](https://en.wikipedia.org/wiki/Centroid#Of_a_polygon)
+///
+/// This a little more computationally expensive than the simple centroid
+/// calculator, but yields better results, especially for non-convex polygons.
+/// {@endtemplate}
+class CentroidCalculator implements PolygonLabelPlacementCalculator {
+  @literal // Required for equality purposes
+  const CentroidCalculator._();
+
+  @override
+  LatLng call(Polygon polygon) {
+    final points = polygon.points;
+
+    if (points.isEmpty) {
+      throw ArgumentError('Polygon must contain at least one point');
+    }
+
+    if (points.length == 1) return points[0];
+
+    double signedArea = 0;
+    double centroidX = 0;
+    double centroidY = 0;
+
+    // For all vertices except last
+    for (int i = 0; i < points.length - 1; i++) {
+      final double x0 = points[i].longitude;
+      final double y0 = points[i].latitude;
+      final double x1 = points[i + 1].longitude;
+      final double y1 = points[i + 1].latitude;
+
+      // Calculate signed area contribution of current vertex
+      final double a = x0 * y1 - x1 * y0;
+      signedArea += a;
+
+      // Accumulate centroid components weighted by signed area
+      centroidX += (x0 + x1) * a;
+      centroidY += (y0 + y1) * a;
+    }
+
+    // Close the polygon by connecting last vertex to first
+    final double x0 = points.last.longitude;
+    final double y0 = points.last.latitude;
+    final double x1 = points.first.longitude;
+    final double y1 = points.first.latitude;
+    final double a = x0 * y1 - x1 * y0;
+    signedArea += a;
+    centroidX += (x0 + x1) * a;
+    centroidY += (y0 + y1) * a;
+
+    // Complete the signed area calculation
+    signedArea *= 0.5;
+
+    // Calculate centroid coordinates
+    centroidX /= 6 * signedArea;
+    centroidY /= 6 * signedArea;
+
+    // Handle special case of zero area (collinear points)
+    if (signedArea == 0) {
+      // Default to average of all points
+      double sumX = 0;
+      double sumY = 0;
+      for (final point in points) {
+        sumX += point.longitude;
+        sumY += point.latitude;
+      }
+      return LatLng(sumY / points.length, sumX / points.length);
+    }
+
+    return LatLng(centroidY, centroidX);
+  }
+}
diff --git a/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart b/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart
new file mode 100644
index 000000000..f4c7b28c2
--- /dev/null
+++ b/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart
@@ -0,0 +1,55 @@
+import 'dart:math';
+
+import 'package:collection/collection.dart';
+import 'package:flutter_map/flutter_map.dart';
+import 'package:latlong2/latlong.dart';
+import 'package:meta/meta.dart';
+import 'package:polylabel/polylabel.dart' as mapbox_polylabel;
+
+part 'centroid.dart';
+part 'polylabel.dart';
+part 'simple_centroid.dart';
+
+/// Calculates the position of a [Polygon.label] within its [Polygon] in
+/// geographic (lat/lng) space
+///
+/// > [!IMPORTANT]
+/// > If the polygon may be over the anti-meridan boundary,
+/// > [PolygonLabelPlacementCalculator.simpleMultiWorldCentroid] must be used -
+/// > other calculators will produce unexpected results.
+///
+/// ---
+///
+/// Implementers: if the constructor is zero-argument, and there is no equality
+/// implementation, constructors should always be invoked with the `const`
+/// keyword, which may be enforced with the [literal] annotation.
+@immutable
+abstract interface class PolygonLabelPlacementCalculator {
+  /// {@macro fm.polygonLabelPlacementCalculator.simpleCentroid}
+  @literal
+  const factory PolygonLabelPlacementCalculator.simpleCentroid() =
+      SimpleCentroidCalculator._;
+
+  /// Similar to [PolygonLabelPlacementCalculator.simpleCentroid], but supports
+  /// correct placement of the [Polygon.label] when the polygon lies across the
+  /// anti-meridian
+  @literal
+  const factory PolygonLabelPlacementCalculator.simpleMultiWorldCentroid() =
+      SimpleMultiWorldCentroidCalculator._;
+
+  /// {@macro fm.polygonLabelPlacementCalculator.centroid}
+  @literal
+  const factory PolygonLabelPlacementCalculator.centroid() =
+      CentroidCalculator._;
+
+  /// {@macro fm.polygonLabelPlacementCalculator.polylabel}
+  const factory PolygonLabelPlacementCalculator.polylabel({double precision}) =
+      PolylabelCalculator._;
+
+  /// Given a polygon (and its points), calculate a single position at which
+  /// the center of the label should be placed
+  ///
+  /// [Polygon.points] is not guaranteed to be non-empty. If empty, this may
+  /// throw.
+  LatLng call(Polygon polygon);
+}
diff --git a/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart b/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart
new file mode 100644
index 000000000..e5d97f9a8
--- /dev/null
+++ b/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart
@@ -0,0 +1,62 @@
+part of 'placement_calculator.dart';
+
+/// {@template fm.polygonLabelPlacementCalculator.polylabel}
+/// Places the [Polygon.label] at the point furthest away from the outline,
+/// calculated using the
+/// ['polylabel' Mapbox algorithm](https://github.com/beroso/dart_polylabel)
+///
+/// This is more computationally expensive than other calculators but may yield
+/// better results.
+///
+/// The [precision] may be adjusted to change the computational expense and
+/// result accuracy. See documentation on [precision] for more information.
+/// {@endtemplate}
+class PolylabelCalculator implements PolygonLabelPlacementCalculator {
+  // Does not need to be `@literal`, as equality is implemented based on state
+  const PolylabelCalculator._({
+    this.precision = 0.0001,
+  });
+
+  /// Threshold for when to stop dividing-and-conquering the polygon in the
+  /// hopes of finding a better point with more distance to the polygon's
+  /// outline
+  ///
+  /// A higher number means less precision (less optimal placement), which is
+  /// computationally cheaper (requires fewer iterations).
+  ///
+  /// Specifying a number too small may result in program hangs.
+  ///
+  /// Specified in geographical space, i.e. degrees.
+  ///
+  /// Defaults to 0.0001.
+  final double precision;
+
+  @override
+  LatLng call(Polygon polygon) {
+    final labelPosition = mapbox_polylabel.polylabel(
+      [
+        List<Point<double>>.generate(
+          polygon.points.length,
+          (i) => Point<double>(
+            polygon.points[i].longitude,
+            polygon.points[i].latitude,
+          ),
+          growable: false,
+        ),
+      ],
+      precision: precision,
+    );
+    return LatLng(
+      labelPosition.point.y.toDouble(),
+      labelPosition.point.x.toDouble(),
+    );
+  }
+
+  @override
+  bool operator ==(Object other) =>
+      identical(this, other) ||
+      (other is PolylabelCalculator && precision == other.precision);
+
+  @override
+  int get hashCode => precision.hashCode;
+}
diff --git a/lib/src/layer/polygon_layer/label/placement_calculators/simple_centroid.dart b/lib/src/layer/polygon_layer/label/placement_calculators/simple_centroid.dart
new file mode 100644
index 000000000..3a35bb4a3
--- /dev/null
+++ b/lib/src/layer/polygon_layer/label/placement_calculators/simple_centroid.dart
@@ -0,0 +1,59 @@
+part of 'placement_calculator.dart';
+
+/// {@template fm.polygonLabelPlacementCalculator.simpleCentroid}
+/// Places the [Polygon.label] at the approximate centroid calculated by
+/// averaging all the points of the polygon
+///
+/// This is computationally cheap and gives reasonable results for convex
+/// polygons. However, for more complex or convex polygons, results may not be
+/// as good (but they should still be acceptable).
+///
+/// > [!IMPORTANT]
+/// > If the polygon may be over the anti-meridan boundary,
+/// > [SimpleMultiWorldCentroidCalculator] must be used - other
+/// > calculators will produce unexpected results.
+/// {@endtemplate}
+class SimpleCentroidCalculator implements PolygonLabelPlacementCalculator {
+  @literal // Required for equality purposes
+  const SimpleCentroidCalculator._();
+
+  @override
+  LatLng call(Polygon polygon) => LatLng(
+        polygon.points.map((e) => e.latitude).average,
+        polygon.points.map((e) => e.longitude).average,
+      );
+}
+
+/// Similar to [SimpleCentroidCalculator], but supports correct placement of the
+/// [Polygon.label] when the polygon lies across the anti-meridian
+class SimpleMultiWorldCentroidCalculator
+    implements PolygonLabelPlacementCalculator {
+  @literal // Required for equality purposes
+  const SimpleMultiWorldCentroidCalculator._();
+
+  @override
+  LatLng call(Polygon polygon) {
+    if (polygon.points.isEmpty) {
+      throw ArgumentError('Polygon must contain at least one point');
+    }
+
+    const halfWorld = 180;
+    int count = 0;
+    double sum = 0;
+    late double lastLng;
+    for (final LatLng point in polygon.points) {
+      double lng = point.longitude;
+      count++;
+      if (count > 1) {
+        if (lng - lastLng > halfWorld) {
+          lng -= 2 * halfWorld;
+        } else if (lng - lastLng < -halfWorld) {
+          lng += 2 * halfWorld;
+        }
+      }
+      lastLng = lng;
+      sum += lastLng;
+    }
+    return LatLng(polygon.points.map((e) => e.latitude).average, sum / count);
+  }
+}
diff --git a/lib/src/layer/polygon_layer/polygon.dart b/lib/src/layer/polygon_layer/polygon.dart
index ecedd7ac4..1f882e972 100644
--- a/lib/src/layer/polygon_layer/polygon.dart
+++ b/lib/src/layer/polygon_layer/polygon.dart
@@ -54,18 +54,37 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
 
   /// The placement logic of the [Polygon.label]
   ///
-  /// [PolygonLabelPlacement.polylabel] can be expensive for some polygons. If
-  /// there is a large lag spike, try using [PolygonLabelPlacement.centroid].
-  ///
   /// > [!IMPORTANT]
-  /// > If your project allows users to browse across multiple worlds, and your
-  /// > polygons may be over the anti-meridan boundary,
-  /// > [PolygonLabelPlacement.centroidWithMultiWorld] must be used - other
-  /// > algorithms will produce unexpected results.
+  /// > If polygons may be over the anti-meridan boundary,
+  /// > [SimpleMultiWorldCentroidCalculator] must be used - other
+  /// > calculators will produce unexpected results.
   ///
-  /// Labels will not be drawn if there is not enough space.
+  /// See [labelPlacementCalculator] for more information.
+  @Deprecated(
+    'Use `labelPlacementCalculator` with the equivalent calculator instead. '
+    'Then, remove any arguments to this parameter and allow it to default. '
+    'This enables more flexibility and extensibility. '
+    'This was deprecated after v8.2.0, and will be removed in a future version.',
+  )
   final PolygonLabelPlacement labelPlacement;
 
+  /// The calculator to use to determine the position of the [Polygon.label]
+  ///
+  /// Labels are not drawn if there is not enough space.
+  ///
+  /// > [!IMPORTANT]
+  /// > If polygons may be over the anti-meridan boundary,
+  /// > [PolygonLabelPlacementCalculator.simpleMultiWorldCentroid] must be
+  /// > used - other calculators will produce unexpected results.
+  ///
+  /// Pre-provided calculators are available as constructors on
+  /// [PolygonLabelPlacementCalculator]. See the documentation on each for
+  /// advantages & disadvantages of each implementation.
+  ///
+  /// Defaults to [PolygonLabelPlacementCalculator.centroid]
+  /// ([CentroidCalculator]).
+  final PolygonLabelPlacementCalculator labelPlacementCalculator;
+
   /// Whether to rotate the label counter to the camera's rotation, to ensure
   /// it remains upright
   final bool rotateLabel;
@@ -84,8 +103,7 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
   LatLng? _labelPosition;
 
   /// Get the coordinates of the label position (cached).
-  LatLng get labelPosition =>
-      _labelPosition ??= _computeLabelPosition(labelPlacement, points);
+  LatLng get labelPosition => _labelPosition ??= labelPlacementCalculator(this);
 
   LatLngBounds? _boundingBox;
 
@@ -122,10 +140,35 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
     this.strokeJoin = StrokeJoin.round,
     this.label,
     this.labelStyle = const TextStyle(),
+    // TODO: Remove `labelPlacement`, and make `labelPlacementCalculator`
+    // `this.` with default, then remove initialiser list
+    @Deprecated(
+      'Use `labelPlacementCalculator` with the equivalent calculator instead. '
+      'Then, remove any arguments to this parameter and allow it to default. '
+      'This enables more flexibility and extensibility. '
+      'This was deprecated after v8.2.0, and will be removed in a future '
+      'version.',
+    )
     this.labelPlacement = PolygonLabelPlacement.centroid,
+
+    /// See [labelPlacementCalculator]
+    PolygonLabelPlacementCalculator? labelPlacementCalculator,
     this.rotateLabel = false,
     this.hitValue,
-  }) : _filledAndClockwise = color != null && isClockwise(points);
+  })  : _filledAndClockwise = color != null && isClockwise(points),
+        labelPlacementCalculator = labelPlacementCalculator ??
+            switch (labelPlacement) {
+              // ignore: deprecated_member_use_from_same_package
+              PolygonLabelPlacement.centroid =>
+                const PolygonLabelPlacementCalculator.centroid(),
+              // ignore: deprecated_member_use_from_same_package
+              PolygonLabelPlacement.centroidWithMultiWorld =>
+                const PolygonLabelPlacementCalculator
+                    .simpleMultiWorldCentroid(),
+              // ignore: deprecated_member_use_from_same_package
+              PolygonLabelPlacement.polylabel =>
+                const PolygonLabelPlacementCalculator.polylabel(),
+            };
 
   /// Checks if the [Polygon] points are ordered clockwise in the list.
   static bool isClockwise(List<LatLng> points) {
@@ -152,7 +195,7 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
           strokeJoin == other.strokeJoin &&
           label == other.label &&
           labelStyle == other.labelStyle &&
-          labelPlacement == other.labelPlacement &&
+          labelPlacementCalculator == other.labelPlacementCalculator &&
           rotateLabel == other.rotateLabel &&
           hitValue == other.hitValue &&
           // Expensive computations last to take advantage of lazy logic gates
@@ -183,7 +226,7 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
         ...points,
         label,
         labelStyle,
-        labelPlacement,
+        labelPlacementCalculator,
         rotateLabel,
         renderHashCode,
       ]);
diff --git a/lib/src/layer/polygon_layer/polygon_layer.dart b/lib/src/layer/polygon_layer/polygon_layer.dart
index 4977ccd6c..b6149a65d 100644
--- a/lib/src/layer/polygon_layer/polygon_layer.dart
+++ b/lib/src/layer/polygon_layer/polygon_layer.dart
@@ -1,7 +1,6 @@
 import 'dart:math';
 import 'dart:ui';
 
-import 'package:collection/collection.dart';
 import 'package:dart_earcut/dart_earcut.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/widgets.dart';
@@ -16,9 +15,8 @@ import 'package:flutter_map/src/misc/point_in_polygon.dart';
 import 'package:flutter_map/src/misc/simplify.dart';
 import 'package:latlong2/latlong.dart' hide Path;
 import 'package:logger/logger.dart';
-import 'package:polylabel/polylabel.dart';
 
-part 'label.dart';
+part 'label/build_text_painter.dart';
 part 'painter.dart';
 part 'polygon.dart';
 part 'projected_polygon.dart';

From 3151645635457a1d1542739d13a0db190d9d4c9b Mon Sep 17 00:00:00 2001
From: JaffaKetchup <github@jaffaketchup.dev>
Date: Tue, 10 Jun 2025 13:21:50 +0100
Subject: [PATCH 4/5] Change polylabel algorithm implementation to
 dart_polylabel2 package

---
 example/lib/pages/polygon.dart                |  2 ++
 .../placement_calculator.dart                 |  4 +---
 .../placement_calculators/polylabel.dart      | 22 ++++++++-----------
 pubspec.yaml                                  |  2 +-
 4 files changed, 13 insertions(+), 17 deletions(-)

diff --git a/example/lib/pages/polygon.dart b/example/lib/pages/polygon.dart
index 520ead5cc..8fc6bcacd 100644
--- a/example/lib/pages/polygon.dart
+++ b/example/lib/pages/polygon.dart
@@ -83,6 +83,7 @@ class _PolygonPageState extends State<PolygonPage> {
           borderColor: Colors.purple,
           label: 'Label!',
           labelPlacementCalculator: _labelPlacementCalculator,
+          labelStyle: const TextStyle(color: Colors.black),
           hitValue: (
             title: 'Polygon With Label',
             subtitle: 'This is a very descriptive label!',
@@ -101,6 +102,7 @@ class _PolygonPageState extends State<PolygonPage> {
           label: 'Rotated!',
           rotateLabel: true,
           labelPlacementCalculator: _labelPlacementCalculator,
+          labelStyle: const TextStyle(color: Colors.black),
           hitValue: (
             title: 'Polygon With Rotated Label',
             subtitle: "Now you don't have to turn your head so much",
diff --git a/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart b/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart
index f4c7b28c2..39ca3033f 100644
--- a/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart
+++ b/lib/src/layer/polygon_layer/label/placement_calculators/placement_calculator.dart
@@ -1,10 +1,8 @@
-import 'dart:math';
-
 import 'package:collection/collection.dart';
+import 'package:dart_polylabel2/dart_polylabel2.dart' as dart_polylabel2;
 import 'package:flutter_map/flutter_map.dart';
 import 'package:latlong2/latlong.dart';
 import 'package:meta/meta.dart';
-import 'package:polylabel/polylabel.dart' as mapbox_polylabel;
 
 part 'centroid.dart';
 part 'polylabel.dart';
diff --git a/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart b/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart
index e5d97f9a8..9df2dde91 100644
--- a/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart
+++ b/lib/src/layer/polygon_layer/label/placement_calculators/polylabel.dart
@@ -2,8 +2,8 @@ part of 'placement_calculator.dart';
 
 /// {@template fm.polygonLabelPlacementCalculator.polylabel}
 /// Places the [Polygon.label] at the point furthest away from the outline,
-/// calculated using the
-/// ['polylabel' Mapbox algorithm](https://github.com/beroso/dart_polylabel)
+/// calculated using a Dart implementation of
+/// [Mapbox's 'polylabel' algorithm](https://github.com/JaffaKetchup/dart_polylabel2)
 ///
 /// This is more computationally expensive than other calculators but may yield
 /// better results.
@@ -26,30 +26,26 @@ class PolylabelCalculator implements PolygonLabelPlacementCalculator {
   ///
   /// Specifying a number too small may result in program hangs.
   ///
-  /// Specified in geographical space, i.e. degrees.
+  /// Specified in geographical space, i.e. degrees. Therefore, as the polygon
+  /// gets larger, this should also get larger.
   ///
   /// Defaults to 0.0001.
   final double precision;
 
   @override
   LatLng call(Polygon polygon) {
-    final labelPosition = mapbox_polylabel.polylabel(
+    final (point: (:x, :y), distance: _) = dart_polylabel2.polylabel(
       [
-        List<Point<double>>.generate(
+        List<dart_polylabel2.Point>.generate(
           polygon.points.length,
-          (i) => Point<double>(
-            polygon.points[i].longitude,
-            polygon.points[i].latitude,
-          ),
+          (i) =>
+              (x: polygon.points[i].latitude, y: polygon.points[i].longitude),
           growable: false,
         ),
       ],
       precision: precision,
     );
-    return LatLng(
-      labelPosition.point.y.toDouble(),
-      labelPosition.point.x.toDouble(),
-    );
+    return LatLng(x, y);
   }
 
   @override
diff --git a/pubspec.yaml b/pubspec.yaml
index 3badb5085..ac66310f6 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -29,13 +29,13 @@ dependencies:
   async: ^2.11.0
   collection: ^1.18.0
   dart_earcut: ^1.1.0
+  dart_polylabel2: ^1.0.0
   flutter:
     sdk: flutter
   http: ^1.2.1
   latlong2: ^0.9.1
   logger: ^2.0.0
   meta: ^1.11.0
-  polylabel: ^1.0.1
   proj4dart: ^2.1.0
   vector_math: ^2.1.4
 

From cbab9ba2f48c7cb2e85c05b24c06a030cd976f0f Mon Sep 17 00:00:00 2001
From: JaffaKetchup <github@jaffaketchup.dev>
Date: Thu, 10 Jul 2025 21:59:18 +0100
Subject: [PATCH 5/5] Re-add `labelPlacement` to `Polygon` equality
 considerations

---
 example/pubspec.lock                     | 16 ++++++++--------
 lib/src/layer/polygon_layer/polygon.dart |  4 ++++
 2 files changed, 12 insertions(+), 8 deletions(-)

diff --git a/example/pubspec.lock b/example/pubspec.lock
index 9ddf3867b..edee147f7 100644
--- a/example/pubspec.lock
+++ b/example/pubspec.lock
@@ -57,6 +57,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "1.2.0"
+  dart_polylabel2:
+    dependency: transitive
+    description:
+      name: dart_polylabel2
+      sha256: "7eeab15ce72894e4bdba6a8765712231fc81be0bd95247de4ad9966abc57adc6"
+      url: "https://pub.dev"
+    source: hosted
+    version: "1.0.0"
   fake_async:
     dependency: transitive
     description:
@@ -311,14 +319,6 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "2.1.8"
-  polylabel:
-    dependency: transitive
-    description:
-      name: polylabel
-      sha256: "41b9099afb2aa6c1730bdd8a0fab1400d287694ec7615dd8516935fa3144214b"
-      url: "https://pub.dev"
-    source: hosted
-    version: "1.0.1"
   proj4dart:
     dependency: "direct main"
     description:
diff --git a/lib/src/layer/polygon_layer/polygon.dart b/lib/src/layer/polygon_layer/polygon.dart
index 1f882e972..c1719c9a8 100644
--- a/lib/src/layer/polygon_layer/polygon.dart
+++ b/lib/src/layer/polygon_layer/polygon.dart
@@ -195,6 +195,8 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
           strokeJoin == other.strokeJoin &&
           label == other.label &&
           labelStyle == other.labelStyle &&
+          // ignore: deprecated_member_use_from_same_package
+          labelPlacement == other.labelPlacement &&
           labelPlacementCalculator == other.labelPlacementCalculator &&
           rotateLabel == other.rotateLabel &&
           hitValue == other.hitValue &&
@@ -226,6 +228,8 @@ class Polygon<R extends Object> with HitDetectableElement<R> {
         ...points,
         label,
         labelStyle,
+        // ignore: deprecated_member_use_from_same_package
+        labelPlacement,
         labelPlacementCalculator,
         rotateLabel,
         renderHashCode,