[google_maps_flutter_web] Reverse Hole winding when needed (#3440)

The Web Version needs the holes in a polygon to be defined in the opposite direction to their parent polygon.

This change automatically reverses the definition of a hole, when needed.

In debug mode, it'll let the user know that the holes need to be wound differently.

Co-authored-by: Anton Borries <anton.borries@2denker.de>
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
index 940419a..caa64ec 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_web/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.1
+
+* Auto-reverse holes if they're the same direction as the polygon. [Issue](https://github.com/flutter/flutter/issues/74096).
+
 ## 0.1.0+10
 
 * Update `package:google_maps_flutter_platform_interface` to `^1.1.0`.
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
index c9fd1cd..95f481a 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart
@@ -482,9 +482,23 @@
   polygon.points.forEach((point) {
     path.add(_latLngToGmLatLng(point));
   });
+  final polygonDirection = _isPolygonClockwise(path);
   List<List<gmaps.LatLng>> paths = [path];
+  int holeIndex = 0;
   polygon.holes?.forEach((hole) {
-    paths.add(hole.map((point) => _latLngToGmLatLng(point)).toList());
+    List<gmaps.LatLng> holePath =
+        hole.map((point) => _latLngToGmLatLng(point)).toList();
+    if (_isPolygonClockwise(holePath) == polygonDirection) {
+      holePath = holePath.reversed.toList();
+      if (kDebugMode) {
+        print(
+            'Hole [$holeIndex] in Polygon [${polygon.polygonId.value}] has been reversed.'
+            ' Ensure holes in polygons are "wound in the opposite direction to the outer path."'
+            ' More info: https://github.com/flutter/flutter/issues/74096');
+      }
+    }
+    paths.add(holePath);
+    holeIndex++;
   });
   return gmaps.PolygonOptions()
     ..paths = paths
@@ -498,6 +512,20 @@
     ..geodesic = polygon.geodesic;
 }
 
+/// Calculates the direction of a given Polygon
+/// based on: https://stackoverflow.com/a/1165943
+///
+/// returns [true] if clockwise [false] if counterclockwise
+bool _isPolygonClockwise(List<gmaps.LatLng> path) {
+  var direction = 0.0;
+  for (var i = 0; i < path.length; i++) {
+    direction = direction +
+        ((path[(i + 1) % path.length].lat - path[i].lat) *
+            (path[(i + 1) % path.length].lng + path[i].lng));
+  }
+  return direction >= 0;
+}
+
 gmaps.PolylineOptions _polylineOptionsFromPolyline(
     gmaps.GMap googleMap, Polyline polyline) {
   List<gmaps.LatLng> paths = [];
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
index bd87955..099a123 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_web/pubspec.yaml
@@ -1,7 +1,7 @@
 name: google_maps_flutter_web
 description: Web platform implementation of google_maps_flutter
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
-version: 0.1.0+10
+version: 0.1.1
 
 flutter:
   plugin:
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/shapes_integration.dart b/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/shapes_integration.dart
index 4d5c2b1..26db542 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/shapes_integration.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/test/test_driver/shapes_integration.dart
@@ -245,6 +245,39 @@
 
       expect(geometry.poly.containsLocation(pointInHole, polygon), false);
     });
+
+    testWidgets('Hole Path gets reversed to display correctly',
+        (WidgetTester tester) async {
+      final polygons = {
+        Polygon(
+          polygonId: PolygonId('BermudaTriangle'),
+          points: [
+            LatLng(25.774, -80.19),
+            LatLng(18.466, -66.118),
+            LatLng(32.321, -64.757),
+          ],
+          holes: [
+            [
+              LatLng(27.339, -66.668),
+              LatLng(29.57, -67.514),
+              LatLng(28.745, -70.579),
+            ],
+          ],
+        ),
+      };
+
+      controller.addPolygons(polygons);
+
+      expect(
+          controller.polygons.values.first.polygon.paths.getAt(1).getAt(0).lat,
+          28.745);
+      expect(
+          controller.polygons.values.first.polygon.paths.getAt(1).getAt(1).lat,
+          29.57);
+      expect(
+          controller.polygons.values.first.polygon.paths.getAt(1).getAt(2).lat,
+          27.339);
+    });
   });
 
   group('PolylinesController', () {