From 0607ca726603651e4416b7e09895635b0b800d8e Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:05:51 +0200 Subject: [PATCH 1/7] Add Ground Overlay support (platform interface) --- .../lib/src/events/map_event.dart | 8 + .../google_maps_flutter_platform.dart | 17 + .../google_maps_inspector_platform.dart | 20 +- .../lib/src/types/ground_overlay.dart | 385 +++++++++++++++++ .../lib/src/types/ground_overlay_updates.dart | 22 + .../lib/src/types/map_objects.dart | 2 + .../lib/src/types/tile_overlay.dart | 2 +- .../lib/src/types/types.dart | 3 + .../lib/src/types/utils/ground_overlay.dart | 13 + .../test/types/ground_overlay_test.dart | 403 ++++++++++++++++++ 10 files changed, 869 insertions(+), 6 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay_updates.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart index 67a026d9055..eda4dc198f4 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart @@ -152,6 +152,14 @@ class CircleTapEvent extends MapEvent { CircleTapEvent(super.mapId, super.circleId); } +/// An event fired when a [GroundOverlay] is tapped. +class GroundOverlayTapEvent extends MapEvent { + /// Build an GroundOverlayTap Event triggered from the map represented by `mapId`. + /// + /// The `value` of this event is a [GroundOverlayId] object that represents the tapped GroundOverlay. + GroundOverlayTapEvent(super.mapId, super.croundOverlayId); +} + /// An event fired when a Map is tapped. class MapTapEvent extends _PositionedMapEvent { /// Build an MapTap Event triggered from the map represented by `mapId`. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart index a8f8e6d8b32..b8a74bd0b60 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart @@ -169,6 +169,18 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { 'updateClusterManagers() has not been implemented.'); } + /// Updates ground overlay configuration. + /// + /// The returned [Future] completes once the update has been made on the + /// platform side. + Future updateGroundOverlays( + GroundOverlayUpdates groundOverlayUpdates, { + required int mapId, + }) { + throw UnimplementedError( + 'updateGroundOverlays() has not been implemented.'); + } + /// Clears the tile cache so that all tiles will be requested again from the /// [TileProvider]. /// @@ -389,6 +401,11 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { throw UnimplementedError('onClusterTap() has not been implemented.'); } + /// A [GroundOverlay] has been tapped. + Stream onGroundOverlayTap({required int mapId}) { + throw UnimplementedError('onGroundOverlayTap() has not been implemented.'); + } + /// Dispose of whatever resources the `mapId` is holding on to. void dispose({required int mapId}) { throw UnimplementedError('dispose() has not been implemented.'); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart index 8bf6f6f89ba..c5acde75d38 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_inspector_platform.dart @@ -117,11 +117,7 @@ abstract class GoogleMapsInspectorPlatform extends PlatformInterface { } /// If the platform supports getting information about heatmaps. - bool supportsGettingHeatmapInfo() { - throw UnimplementedError( - 'supportsGettingHeatmapInfo() has not been implemented.', - ); - } + bool supportsGettingHeatmapInfo() => false; /// Returns information about the heatmap with the given ID. /// @@ -132,6 +128,20 @@ abstract class GoogleMapsInspectorPlatform extends PlatformInterface { throw UnimplementedError('getHeatmapInfo() has not been implemented.'); } + /// If the platform supports getting information about ground overlays. + bool supportsGettingGroundOverlayInfo() => false; + + /// Returns information about the ground overlay with the given ID. + /// + /// The returned object will be synthesized from platform data, so will not + /// be the same Dart object as the original [GroundOverlay] provided to the + /// platform interface with that ID, and not all fields will be populated. + Future getGroundOverlayInfo(GroundOverlayId groundOverlayId, + {required int mapId}) { + throw UnimplementedError( + 'getGroundOverlayInfo() has not been implemented.'); + } + /// Returns current clusters from [ClusterManager]. Future> getClusters( {required int mapId, required ClusterManagerId clusterManagerId}) { diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart new file mode 100644 index 00000000000..b06112a190f --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart @@ -0,0 +1,385 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:ui'; + +import 'package:flutter/foundation.dart' show immutable; + +import 'types.dart'; + +/// Uniquely identifies a [GroundOverlay] among [GoogleMap] ground overlays. +@immutable +class GroundOverlayId extends MapsObjectId { + /// Creates an immutable identifier for a [GroundOverlay]. + const GroundOverlayId(super.value); +} + +/// Ground overlay to be drawn on the map. +/// +/// A ground overlay is an image that is fixed to a map. Unlike markers, ground +/// overlays are oriented against the Earth's surface rather than the screen, +/// so rotating, tilting, or zooming the map will change the orientation of the +/// image. Ground overlays are useful for fixing a single image at one area on +/// the map. For adding extensive imagery that covers a large portion of the +/// map, a [TileOverlay] should be considered. +/// +/// Because the overlay is automatically scaled to fit either a specified +/// [bounds] or a [position] (combined with [width], [height], and [zoomLevel]), +/// the [image].bitmapScaling must be set to [MapBitmapScaling.none]. +/// +/// Sizing and positioning can be defined in the following ways: +/// - Using [bounds] for exact corners in [LatLngBounds]. +/// Recommended for precise placement. +/// - Using [position] with [width] and [height] in meters. If [height] +/// is omitted, the image aspect ratio is preserved. +/// - Using [position] with [zoomLevel] to scale the image according to +/// a chosen zoom level. +/// +/// The [anchor] parameter defines the anchor’s relative location within the +/// overlay. For example, an anchor of (0.5, 0.5) corresponds to the image’s +/// center. When [position] is used, the overlay shifts so that this anchor +/// aligns with the given position. When [bounds] is used, the anchor specifies +/// the internal anchor position inside the bounds. If [bearing] is set, the +/// image rotates around this anchor. +/// +/// Platform behavior for sizing can vary, and not all sizing or positioning +/// options may be supported equally across all platforms. Combining both +/// [width] and [zoomLevel] can help achieve the desired effect across +/// platforms. Using [bounds] is the most reliable way to position an ground +/// overlay precisely. +/// +/// Use either [GroundOverlay.fromBounds] or [GroundOverlay.fromPosition] to +/// create a ground overlay. +/// +/// Example of [GroundOverlay.fromBounds] method: +/// ```dart +/// GroundOverlay.bounds( +/// groundOverlayId: const GroundOverlayId('overlay_id'), +/// image: await AssetMapBitmap.create( +/// createLocalImageConfiguration(context), +/// 'assets/images/ground_overlay.png', +/// bitmapScaling: MapBitmapScaling.none, +/// ), +/// bounds: LatLngBounds( +/// southwest: LatLng(37.42, -122.08), +/// northeast: LatLng(37.43, -122.09), +/// ), +/// ); +/// ``` +/// +/// Example of [GroundOverlay.fromPosition] method: +/// ```dart +/// GroundOverlay.position( +/// groundOverlayId: const GroundOverlayId('overlay_id'), +/// image: await AssetMapBitmap.create( +/// createLocalImageConfiguration(context), +/// 'assets/images/ground_overlay.png', +/// bitmapScaling: MapBitmapScaling.none, +/// ), +/// position: LatLng(37.42, -122.08), +/// width: 100, +/// height: 100, +/// zoomLevel: 14, +/// ); +/// ``` +@immutable +class GroundOverlay implements MapsObject { + /// Creates an immutable representation of a [GroundOverlay] to + /// draw on [GoogleMap]. + GroundOverlay._({ + required this.groundOverlayId, + required this.image, + this.position, + this.bounds, + this.width, + this.height, + this.anchor = const Offset(0.5, 0.5), + this.transparency = 0.0, + this.bearing = 0.0, + this.zIndex = 0, + this.visible = true, + this.clickable = true, + this.onTap, + this.zoomLevel, + }) : assert(transparency >= 0.0 && transparency <= 1.0), + assert(bearing >= 0.0 && bearing <= 360.0), + assert((position == null) != (bounds == null), + 'Either position or bounds must be given, but not both'), + assert(position == null || (width == null || width > 0), + 'Width must be null or greater than 0 when position is used'), + assert(position == null || (height == null || height > 0), + 'Height must be null or greater than 0 when position is used'), + assert(image.bitmapScaling == MapBitmapScaling.none, + 'The provided image must have its bitmapScaling property set to MapBitmapScaling.none.'); + + /// Creates a [GroundOverlay] fitted to the specified [bounds] with the + /// provided [image]. + /// + /// Example: + /// ```dart + /// GroundOverlay.fromBounds( + /// groundOverlayId: const GroundOverlayId('overlay_id'), + /// image: await AssetMapBitmap.create( + /// createLocalImageConfiguration(context), + /// 'assets/images/ground_overlay.png', + /// bitmapScaling: MapBitmapScaling.none, + /// ), + /// bounds: LatLngBounds( + /// southwest: LatLng(37.42, -122.08), + /// northeast: LatLng(37.43, -122.09), + /// ), + /// ); + factory GroundOverlay.fromBounds({ + required GroundOverlayId groundOverlayId, + required MapBitmap image, + required LatLngBounds bounds, + Offset anchor = const Offset(0.5, 0.5), + double bearing = 0.0, + double transparency = 0.0, + int zIndex = 0, + bool visible = true, + bool clickable = true, + VoidCallback? onTap, + }) { + return GroundOverlay._( + groundOverlayId: groundOverlayId, + image: image, + bounds: bounds, + anchor: anchor, + bearing: bearing, + transparency: transparency, + zIndex: zIndex, + visible: visible, + clickable: clickable, + onTap: onTap, + ); + } + + /// Creates a [GroundOverlay] to given [position] with the given [image]. + /// + /// Example: + /// ```dart + /// GroundOverlay.fromPosition( + /// groundOverlayId: const GroundOverlayId('overlay_id'), + /// image: await AssetMapBitmap.create( + /// createLocalImageConfiguration(context), + /// 'assets/images/ground_overlay.png', + /// bitmapScaling: MapBitmapScaling.none, + /// ), + /// position: LatLng(37.42, -122.08), + /// width: 100, + /// height: 100, + /// zoomLevel: 14, + /// ); + /// ``` + factory GroundOverlay.fromPosition({ + required GroundOverlayId groundOverlayId, + required MapBitmap image, + required LatLng position, + double? width, + double? height, + Offset anchor = const Offset(0.5, 0.5), + double bearing = 0.0, + double transparency = 0.0, + int zIndex = 0, + bool visible = true, + bool clickable = true, + VoidCallback? onTap, + double? zoomLevel, + }) { + return GroundOverlay._( + groundOverlayId: groundOverlayId, + image: image, + position: position, + width: width, + height: height, + anchor: anchor, + bearing: bearing, + transparency: transparency, + zIndex: zIndex, + visible: visible, + clickable: clickable, + onTap: onTap, + zoomLevel: zoomLevel, + ); + } + + /// Uniquely identifies a [GroundOverlay]. + final GroundOverlayId groundOverlayId; + + @override + GroundOverlayId get mapsId => groundOverlayId; + + /// A description of the bitmap used to draw the ground overlay. + /// + /// To create ground overlay from assets, use [AssetMapBitmap], + /// [AssetMapBitmap.create] or [BitmapDescriptor.asset]. + /// + /// To create ground overlay from raw PNG data use [BytesMapBitmap] + /// or [BitmapDescriptor.bytes]. + /// + /// [MapBitmap.bitmapScaling] must be set to [MapBitmapScaling.none]. + final MapBitmap image; + + /// Geographical location to which the anchor will be fixed. The relative + /// location of the [position] on the overlay can be changed with the [anchor] + /// parameter, which is by default (0.5, 0.5) meaning that the [position] is + /// in the middle of the overlay image. + final LatLng? position; + + /// Width of the ground overlay (in meters). This parameter is only available + /// with [position]. + final double? width; + + /// Height of the ground overlay (in meters). This parameter is only available + /// with [position]. If not provided, the image aspect ratio is automatically + /// preserved. + final double? height; + + /// Bounds which will contain the image. If [bounds] is specified, [position] + /// must be null. + final LatLngBounds? bounds; + + /// The [anchor] in normalized coordinates specifying the anchor point of the + /// overlay. When [position] is used, the overlay shifts so that this anchor + /// aligns with the given position. If [bounds] is specified, the anchor is + /// the internal anchor position inside the bounds. + /// + /// * An anchor of (0.0, 0.0) is the top-left corner. + /// * An anchor of (1.0, 1.0) is the bottom-right corner. + /// + /// Defaults to `Offset(0.5, 0.5)`, i.e., the center of the image. + /// If [bearing] is set, the image rotates around this anchor. + final Offset? anchor; + + /// The amount that the image should be rotated in a clockwise direction. The + /// center of the rotation will be the image's [anchor]. + /// The default bearing is 0, i.e., the image is aligned so that up is north. + final double bearing; + + /// The transparency of the ground overlay. Defaults to 0 (opaque). + final double transparency; + + /// The ground overlay's zIndex, i.e., the order in which it will be drawn + /// where overlays with larger values are drawn above those with lower values. + /// Defaults to 0. + final int zIndex; + + /// Whether the ground overlay is visible (true) or hidden (false). + /// Defaults to true. + final bool visible; + + /// Controls if click events are handled for this ground overlay. + /// Defaults to true. + final bool clickable; + + /// Callbacks to receive tap events for ground overlay placed on this map. + final VoidCallback? onTap; + + /// The map zoom level used when setting a ground overlay with a [position]. + /// + /// This parameter determines how the [GroundOverlay.image] is rendered on the + /// map when using [GroundOverlay.position]. The image is scaled as if its + /// actual size corresponds to the camera pixels at the specified `zoomLevel`. + /// Usage of this parameter can differ between platforms. + final double? zoomLevel; + + /// Converts this object to something serializable in JSON. + @override + Object toJson() { + final Map json = {}; + + void addIfPresent(String fieldName, Object? value) { + if (value != null) { + json[fieldName] = value; + } + } + + addIfPresent('groundOverlayId', groundOverlayId.value); + addIfPresent('image', image.toJson()); + addIfPresent('position', position?.toJson()); + addIfPresent('bounds', bounds?.toJson()); + addIfPresent('width', width); + addIfPresent('height', height); + addIfPresent( + 'anchor', anchor != null ? [anchor!.dx, anchor!.dy] : null); + addIfPresent('bearing', bearing); + addIfPresent('transparency', transparency); + addIfPresent('zIndex', zIndex); + addIfPresent('visible', visible); + addIfPresent('clickable', clickable); + addIfPresent('zoomLevel', zoomLevel); + + return json; + } + + /// Creates a new [GroundOverlay] object whose values are the same as this + /// instance, unless overwritten by the specified parameters. + GroundOverlay copyWith({ + double? bearingParam, + double? transparencyParam, + int? zIndexParam, + bool? visibleParam, + bool? clickableParam, + VoidCallback? onTapParam, + }) { + return GroundOverlay._( + groundOverlayId: groundOverlayId, + bearing: bearingParam ?? bearing, + transparency: transparencyParam ?? transparency, + zIndex: zIndexParam ?? zIndex, + visible: visibleParam ?? visible, + clickable: clickableParam ?? clickable, + onTap: onTapParam ?? onTap, + image: image, + position: position, + bounds: bounds, + width: width, + height: height, + anchor: anchor, + zoomLevel: zoomLevel, + ); + } + + @override + GroundOverlay clone() => copyWith(); + + @override + bool operator ==(Object other) { + if (other.runtimeType != runtimeType) { + return false; + } + return other is GroundOverlay && + groundOverlayId == other.groundOverlayId && + image == other.image && + position == other.position && + bounds == other.bounds && + width == other.width && + height == other.height && + anchor == other.anchor && + bearing == other.bearing && + transparency == other.transparency && + zIndex == other.zIndex && + visible == other.visible && + clickable == other.clickable && + zoomLevel == other.zoomLevel; + } + + @override + int get hashCode => Object.hash( + groundOverlayId, + image, + position, + bounds, + width, + height, + anchor, + bearing, + transparency, + zIndex, + visible, + clickable, + zoomLevel, + ); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay_updates.dart new file mode 100644 index 00000000000..152352e78ae --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay_updates.dart @@ -0,0 +1,22 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'types.dart'; + +/// Update specification for a set of [GroundOverlay]s. +class GroundOverlayUpdates extends MapsObjectUpdates { + /// Computes [GroundOverlayUpdates] given previous and current [GroundOverlay]s. + GroundOverlayUpdates.from(super.previous, super.current) + : super.from(objectName: 'groundOverlay'); + + /// Set of GroundOverlays to be added in this update. + Set get groundOverlaysToAdd => objectsToAdd; + + /// Set of GroundOverlayIds to be removed in this update. + Set get groundOverlayIdsToRemove => + objectIdsToRemove.cast(); + + /// Set of GroundOverlays to be changed in this update. + Set get groundOverlaysToChange => objectsToChange; +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart index 23d605c43ef..66ec35f86f5 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/map_objects.dart @@ -23,6 +23,7 @@ class MapObjects { this.heatmaps = const {}, this.tileOverlays = const {}, this.clusterManagers = const {}, + this.groundOverlays = const {}, }); final Set markers; @@ -32,4 +33,5 @@ class MapObjects { final Set heatmaps; final Set tileOverlays; final Set clusterManagers; + final Set groundOverlays; } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart index 4df0fe97e42..43699f82055 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart @@ -70,7 +70,7 @@ class TileOverlay implements MapsObject { final double transparency; /// The tile overlay's zIndex, i.e., the order in which it will be drawn where - /// overlays with larger values are drawn above those with lower values + /// overlays with larger values are drawn above those with lower values. final int zIndex; /// The visibility for the tile overlay. The default visibility is true. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart index 745e300ff05..95c27d5bf95 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/types.dart @@ -12,6 +12,8 @@ export 'circle_updates.dart'; export 'cluster.dart'; export 'cluster_manager.dart'; export 'cluster_manager_updates.dart'; +export 'ground_overlay.dart'; +export 'ground_overlay_updates.dart'; export 'heatmap.dart'; export 'heatmap_updates.dart'; export 'joint_type.dart'; @@ -36,6 +38,7 @@ export 'ui.dart'; // Export the utils used by the Widget export 'utils/circle.dart'; export 'utils/cluster_manager.dart'; +export 'utils/ground_overlay.dart'; export 'utils/heatmap.dart'; export 'utils/marker.dart'; export 'utils/polygon.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart new file mode 100644 index 00000000000..22ee01d86fc --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart @@ -0,0 +1,13 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../types.dart'; +import 'maps_object.dart'; + +/// Converts an [Iterable] of GroundOverlay in a Map of GroundOverlayId -> GroundOverlay. +Map keyByGroundOverlayId( + Iterable groundOverlays) { + return keyByMapsObjectId(groundOverlays) + .cast(); +} diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart new file mode 100644 index 00000000000..04cacda9822 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart @@ -0,0 +1,403 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:flutter_test/flutter_test.dart'; +import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + + group('$GroundOverlay', () { + const GroundOverlayId kID = GroundOverlayId('groundOverlay'); + final LatLngBounds kBounds = LatLngBounds( + southwest: const LatLng(37.42, -122.08), + northeast: const LatLng(37.43, -122.09), + ); + const LatLng kPosition = LatLng(37.42, -122.08); + final MapBitmap kMapBitmap = AssetMapBitmap( + 'assets/asset.png', + imagePixelRatio: 1.0, + bitmapScaling: MapBitmapScaling.none, + ); + const Offset kAnchor = Offset(0.3, 0.7); + const double kBearing = 45.0; + const double kTransparency = 0.5; + const int kZIndex = 1; + const bool kVisible = false; + const bool kClickable = false; + const double kWidth = 200; + const double kHeight = 300; + const double kZoomLevel = 10.0; + + test('fromBounds constructor defaults', () { + final GroundOverlay groundOverlay = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + ); + + expect(groundOverlay.groundOverlayId, kID); + expect(groundOverlay.bounds, kBounds); + expect(groundOverlay.image, kMapBitmap); + expect(groundOverlay.anchor, const Offset(0.5, 0.5)); + expect(groundOverlay.bearing, 0.0); + expect(groundOverlay.transparency, 0.0); + expect(groundOverlay.zIndex, 0.0); + expect(groundOverlay.visible, true); + expect(groundOverlay.clickable, true); + expect(groundOverlay.onTap, null); + }); + + test('fromBounds construct with values', () { + final GroundOverlay groundOverlay = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + ); + + expect(groundOverlay.groundOverlayId, kID); + expect(groundOverlay.bounds, kBounds); + expect(groundOverlay.image, kMapBitmap); + expect(groundOverlay.anchor, kAnchor); + expect(groundOverlay.bearing, kBearing); + expect(groundOverlay.transparency, kTransparency); + expect(groundOverlay.zIndex, kZIndex); + expect(groundOverlay.visible, kVisible); + expect(groundOverlay.clickable, kClickable); + }); + + test('fromPosition constructor defaults', () { + final GroundOverlay groundOverlay = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: 100, + height: 100, + ); + + expect(groundOverlay.groundOverlayId, kID); + expect(groundOverlay.position, kPosition); + expect(groundOverlay.image, kMapBitmap); + expect(groundOverlay.width, 100); + expect(groundOverlay.height, 100); + expect(groundOverlay.anchor, const Offset(0.5, 0.5)); + expect(groundOverlay.bearing, 0.0); + expect(groundOverlay.transparency, 0.0); + expect(groundOverlay.zIndex, 0.0); + expect(groundOverlay.visible, true); + expect(groundOverlay.clickable, true); + expect(groundOverlay.onTap, null); + }); + + test('fromPosition construct with values', () { + final GroundOverlay groundOverlay = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: kWidth, + height: kHeight, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + zoomLevel: kZoomLevel, + ); + + expect(groundOverlay.groundOverlayId, kID); + expect(groundOverlay.position, kPosition); + expect(groundOverlay.image, kMapBitmap); + expect(groundOverlay.width, kWidth); + expect(groundOverlay.height, kHeight); + expect(groundOverlay.anchor, kAnchor); + expect(groundOverlay.bearing, kBearing); + expect(groundOverlay.transparency, kTransparency); + expect(groundOverlay.zIndex, kZIndex); + expect(groundOverlay.visible, kVisible); + expect(groundOverlay.clickable, kClickable); + expect(groundOverlay.zoomLevel, kZoomLevel); + }); + + test('copyWith fromPosition', () { + final GroundOverlay groundOverlay1 = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: 100, + height: 100, + ); + + final GroundOverlay groundOverlay2 = groundOverlay1.copyWith( + bearingParam: kBearing, + transparencyParam: kTransparency, + zIndexParam: kZIndex, + visibleParam: kVisible, + clickableParam: kClickable, + onTapParam: () {}, + ); + + expect(groundOverlay2.groundOverlayId, groundOverlay1.groundOverlayId); + expect(groundOverlay2.image, groundOverlay1.image); + expect(groundOverlay2.position, groundOverlay1.position); + expect(groundOverlay2.width, groundOverlay1.width); + expect(groundOverlay2.height, groundOverlay1.height); + expect(groundOverlay2.anchor, groundOverlay1.anchor); + expect(groundOverlay2.bearing, kBearing); + expect(groundOverlay2.transparency, kTransparency); + expect(groundOverlay2.zIndex, kZIndex); + expect(groundOverlay2.visible, kVisible); + expect(groundOverlay2.clickable, kClickable); + expect(groundOverlay2.zoomLevel, groundOverlay1.zoomLevel); + }); + + test('copyWith fromBounds', () { + final GroundOverlay groundOverlay1 = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + ); + + final GroundOverlay groundOverlay2 = groundOverlay1.copyWith( + bearingParam: kBearing, + transparencyParam: kTransparency, + zIndexParam: kZIndex, + visibleParam: kVisible, + clickableParam: kClickable, + onTapParam: () {}, + ); + + expect(groundOverlay2.groundOverlayId, groundOverlay1.groundOverlayId); + expect(groundOverlay2.image, groundOverlay1.image); + expect(groundOverlay2.position, groundOverlay1.position); + expect(groundOverlay2.width, groundOverlay1.width); + expect(groundOverlay2.height, groundOverlay1.height); + expect(groundOverlay2.anchor, groundOverlay1.anchor); + expect(groundOverlay2.bearing, kBearing); + expect(groundOverlay2.transparency, kTransparency); + expect(groundOverlay2.zIndex, kZIndex); + expect(groundOverlay2.visible, kVisible); + expect(groundOverlay2.clickable, kClickable); + expect(groundOverlay2.zoomLevel, groundOverlay1.zoomLevel); + }); + + test('fromPosition clone', () { + final GroundOverlay groundOverlay1 = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: 100, + height: 100, + ); + + final GroundOverlay groundOverlay2 = groundOverlay1.clone(); + + expect(groundOverlay2, groundOverlay1); + }); + + test('fromBounds clone', () { + final GroundOverlay groundOverlay1 = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + ); + + final GroundOverlay groundOverlay2 = groundOverlay1.clone(); + + expect(groundOverlay2, groundOverlay1); + }); + + test('==', () { + final GroundOverlay groundOverlayPosition1 = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: kWidth, + height: kHeight, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + zoomLevel: kZoomLevel, + ); + + final GroundOverlay groundOverlayPosition2 = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: kWidth, + height: kHeight, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + zoomLevel: kZoomLevel, + ); + + final GroundOverlay groundOverlayPosition3 = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: kWidth, + height: kHeight, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + zoomLevel: kZoomLevel + 1, + ); + + final GroundOverlay groundOverlayBounds1 = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + ); + + final GroundOverlay groundOverlayBounds2 = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex, + visible: kVisible, + clickable: kClickable, + ); + + final GroundOverlay groundOverlayBounds3 = GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + anchor: kAnchor, + bearing: kBearing, + transparency: kTransparency, + zIndex: kZIndex + 1, + visible: kVisible, + clickable: kClickable, + ); + + expect(groundOverlayPosition1, groundOverlayPosition2); + expect(groundOverlayPosition1, isNot(groundOverlayPosition3)); + expect(groundOverlayBounds1, groundOverlayBounds2); + expect(groundOverlayBounds1, isNot(groundOverlayBounds3)); + expect(groundOverlayPosition1, isNot(groundOverlayBounds1)); + }); + + test('hashCode', () { + final GroundOverlay groundOverlay = GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + ); + + expect(groundOverlay.hashCode, groundOverlay.clone().hashCode); + }); + + test('asserts in constructor', () { + // Transparency must be between 0.0 and 1.0. + expect( + () => GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + transparency: -0.1, + ), + throwsAssertionError, + ); + + // Transparency must be between 0.0 and 1.0. + expect( + () => GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + transparency: 1.1, + ), + throwsAssertionError, + ); + + // Bearing must be between 0.0 and 360.0. + expect( + () => GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + bearing: -1.0, + ), + throwsAssertionError, + ); + + // Bearing must be between 0.0 and 360.0. + expect( + () => GroundOverlay.fromBounds( + groundOverlayId: kID, + image: kMapBitmap, + bounds: kBounds, + bearing: 361.0, + ), + throwsAssertionError, + ); + + // Height must be greater than 0. + expect( + () => GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: 100, + height: -1, + ), + throwsAssertionError, + ); + + // Width must be greater than 0. + expect( + () => GroundOverlay.fromPosition( + groundOverlayId: kID, + image: kMapBitmap, + position: kPosition, + width: -1, + height: 100, + ), + throwsAssertionError, + ); + + // Image bitMapScaling must be MapBitmapScaling.none. + expect( + () => GroundOverlay.fromPosition( + groundOverlayId: kID, + image: AssetMapBitmap( + 'assets/asset.png', + imagePixelRatio: 1.0, + // ignore: avoid_redundant_argument_values + bitmapScaling: MapBitmapScaling.auto, + ), + position: kPosition, + width: 100, + height: 100, + ), + throwsAssertionError, + ); + }); + }); +} From f8df34105cb3f55a212e97126a05afa66d3bc062 Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:40:44 +0200 Subject: [PATCH 2/7] Update changelog --- .../google_maps_flutter_platform_interface/CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index 8638e83c6ce..d9b4cf4217d 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 2.9.6 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. +* Adds support for ground overlay. ## 2.9.5 From 14c6075bb8724a0611126996b177b87d3d75de46 Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Tue, 28 Jan 2025 14:57:38 +0200 Subject: [PATCH 3/7] Bump version code --- .../google_maps_flutter_platform_interface/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index 1ffc34717c9..abbf5a1fa21 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/google_maps_f issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.9.5 +version: 2.9.6 environment: sdk: ^3.4.0 From 753fbe1fb04eda3596f0c520c424e85cfb8832f7 Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:35:01 +0200 Subject: [PATCH 4/7] Change version to 2.10.0 --- .../google_maps_flutter_platform_interface/CHANGELOG.md | 2 +- .../google_maps_flutter_platform_interface/pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md index d9b4cf4217d..449f4707be8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.9.6 +## 2.10.0 * Updates minimum supported SDK version to Flutter 3.22/Dart 3.4. * Adds support for ground overlay. diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml index abbf5a1fa21..c41c1cb7b28 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml @@ -4,7 +4,7 @@ repository: https://github.com/flutter/packages/tree/main/packages/google_maps_f issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 # NOTE: We strongly prefer non-breaking changes, even at the expense of a # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes -version: 2.9.6 +version: 2.10.0 environment: sdk: ^3.4.0 From 396923b4ad61ee4f43c9fca3105388427418907e Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:43:51 +0200 Subject: [PATCH 5/7] Fix prepositions --- .../lib/src/events/map_event.dart | 2 +- .../lib/src/types/ground_overlay.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart index eda4dc198f4..9bfbdf789d7 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart @@ -154,7 +154,7 @@ class CircleTapEvent extends MapEvent { /// An event fired when a [GroundOverlay] is tapped. class GroundOverlayTapEvent extends MapEvent { - /// Build an GroundOverlayTap Event triggered from the map represented by `mapId`. + /// Build a GroundOverlayTap Event triggered from the map represented by `mapId`. /// /// The `value` of this event is a [GroundOverlayId] object that represents the tapped GroundOverlay. GroundOverlayTapEvent(super.mapId, super.croundOverlayId); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart index b06112a190f..cb727d76607 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart @@ -46,7 +46,7 @@ class GroundOverlayId extends MapsObjectId { /// Platform behavior for sizing can vary, and not all sizing or positioning /// options may be supported equally across all platforms. Combining both /// [width] and [zoomLevel] can help achieve the desired effect across -/// platforms. Using [bounds] is the most reliable way to position an ground +/// platforms. Using [bounds] is the most reliable way to position a ground /// overlay precisely. /// /// Use either [GroundOverlay.fromBounds] or [GroundOverlay.fromPosition] to From 5220082084d2280a8a0ce57bd9312255f41d7e6c Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Wed, 29 Jan 2025 09:56:25 +0200 Subject: [PATCH 6/7] Separate first sentence to its own paragraph --- .../lib/src/types/ground_overlay.dart | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart index cb727d76607..c9e84a632b1 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay.dart @@ -222,29 +222,35 @@ class GroundOverlay implements MapsObject { /// [MapBitmap.bitmapScaling] must be set to [MapBitmapScaling.none]. final MapBitmap image; - /// Geographical location to which the anchor will be fixed. The relative - /// location of the [position] on the overlay can be changed with the [anchor] - /// parameter, which is by default (0.5, 0.5) meaning that the [position] is - /// in the middle of the overlay image. + /// Geographical location to which the anchor will be fixed. + /// + /// The relative location of the [position] on the overlay can be changed + /// with the [anchor] parameter, which is by default (0.5, 0.5) meaning that + /// the [position] is in the middle of the overlay image. final LatLng? position; - /// Width of the ground overlay (in meters). This parameter is only available - /// with [position]. + /// Width of the ground overlay in meters. + /// + /// This parameter is only available with [position]. final double? width; - /// Height of the ground overlay (in meters). This parameter is only available - /// with [position]. If not provided, the image aspect ratio is automatically - /// preserved. + /// Height of the ground overlay in meters. + /// + /// This parameter is only available with [position]. If not provided, + /// the image aspect ratio is automatically preserved. final double? height; - /// Bounds which will contain the image. If [bounds] is specified, [position] - /// must be null. + /// Bounds which will contain the image. + /// + /// If [bounds] is specified, [position] must be null. final LatLngBounds? bounds; /// The [anchor] in normalized coordinates specifying the anchor point of the - /// overlay. When [position] is used, the overlay shifts so that this anchor - /// aligns with the given position. If [bounds] is specified, the anchor is - /// the internal anchor position inside the bounds. + /// overlay. + /// + /// When [position] is used, the overlay shifts so that this anchor aligns + /// with the given position. If [bounds] is specified, the anchor is the + /// internal anchor position inside the bounds. /// /// * An anchor of (0.0, 0.0) is the top-left corner. /// * An anchor of (1.0, 1.0) is the bottom-right corner. @@ -253,24 +259,32 @@ class GroundOverlay implements MapsObject { /// If [bearing] is set, the image rotates around this anchor. final Offset? anchor; - /// The amount that the image should be rotated in a clockwise direction. The - /// center of the rotation will be the image's [anchor]. + /// The amount that the image should be rotated in a clockwise direction. + /// + /// The center of the rotation will be the image's [anchor]. /// The default bearing is 0, i.e., the image is aligned so that up is north. final double bearing; - /// The transparency of the ground overlay. Defaults to 0 (opaque). + /// The transparency of the ground overlay. + /// + /// Defaults to 0 (opaque). final double transparency; - /// The ground overlay's zIndex, i.e., the order in which it will be drawn - /// where overlays with larger values are drawn above those with lower values. + /// The ground overlay's zIndex. + /// + /// It sets the order in which it will be drawn where overlays with larger + /// values are drawn above those with lower values. + /// /// Defaults to 0. final int zIndex; /// Whether the ground overlay is visible (true) or hidden (false). + /// /// Defaults to true. final bool visible; /// Controls if click events are handled for this ground overlay. + /// /// Defaults to true. final bool clickable; From d90aa3b11e03fa1567d82533956ffd0f87908a28 Mon Sep 17 00:00:00 2001 From: Alexander Troshkov <20989940+aednlaxer@users.noreply.github.com> Date: Mon, 3 Feb 2025 09:21:38 +0200 Subject: [PATCH 7/7] Fix nits --- .../lib/src/types/utils/ground_overlay.dart | 2 +- .../test/types/ground_overlay_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart index 22ee01d86fc..6e92d621138 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/utils/ground_overlay.dart @@ -5,7 +5,7 @@ import '../types.dart'; import 'maps_object.dart'; -/// Converts an [Iterable] of GroundOverlay in a Map of GroundOverlayId -> GroundOverlay. +/// Converts an [Iterable] of GroundOverlay to a Map of GroundOverlayId -> GroundOverlay. Map keyByGroundOverlayId( Iterable groundOverlays) { return keyByMapsObjectId(groundOverlays) diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart index 04cacda9822..f1c17a2e433 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/ground_overlay_test.dart @@ -8,7 +8,7 @@ import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platf void main() { TestWidgetsFlutterBinding.ensureInitialized(); - group('$GroundOverlay', () { + group('GroundOverlay', () { const GroundOverlayId kID = GroundOverlayId('groundOverlay'); final LatLngBounds kBounds = LatLngBounds( southwest: const LatLng(37.42, -122.08),