Skip to content

[google_maps_flutter] Support for Ground Overlay #8432

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

jokerttu
Copy link
Contributor

@jokerttu jokerttu commented Jan 15, 2025

Adds Ground Overlay support for google_maps_flutter plugin.

Fixes flutter/flutter#26479

Android:

iOS:

Web:

Documentation for groundOverlays parameter on app-facing plugin:
documentation

Pre-launch Checklist

If you need help, consider asking for advice on the #hackers-new channel on Discord.

@@ -22,7 +21,7 @@ void main() {
}

void runTests() {
const double floatTolerance = 1e-8;
const double floatTolerance = 1e-6;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added bigger tolerance as on Android and iOS tests failed as diff was bigger than 1e-8

Color.fromARGB(255, 0, 0, 191),
0.6,
);
await tester.pumpAndSettle(const Duration(seconds: 3));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Without this line tests timeouted on Web platform, and we were not able to test ground overlay below.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Were they timing out in all tests, or wasm specifically? I know I had to revisit a bunch of plugins and add a few pumps and pumpAndSettle when enabling the wasm tests. See:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ditman
Note that google_maps_flutter tests are currently excluded for web platform because failing tests:
https://github.com/flutter/packages/blob/main/script/configs/exclude_integration_web.yaml

New implemented tests pass with and without wasm enabled, but only if I disable other test sets (that fail because other reasons). The new tests are located in tiles_inspector.dart and I just run this set, which includes tests for tiles, heatmaps, and ground overlays. This file should probably be renamed or split?

image

Fixing other tests is out of scope of this PR, but this must include working integration tests for web for new features and functionality.

So when only tiles_inspector.dart tests are enabled tests pass with following commands:

Without wasm

dart run ./script/tool/bin/flutter_plugin_tools.dart drive-examples --web --run-chromedriver --packages=google_maps_flutter/google_maps_flutter

and with wasm

dart run ./script/tool/bin/flutter_plugin_tools.dart drive-examples --web --wasm --run-chromedriver --packages=google_maps_flutter/google_maps_flutter

expect(wll1.weight, wll2.weight);
expect(wll1.point.latitude, moreOrLessEquals(wll2.point.latitude));
expect(wll1.point.longitude, moreOrLessEquals(wll2.point.longitude));
group('Heatmaps', () {
Copy link
Contributor Author

@jokerttu jokerttu Jan 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added Heatmap tests to own group, like tile overlay tests and ground overlay tests.

@jokerttu jokerttu marked this pull request as ready for review January 16, 2025 11:38
@reidbaker reidbaker requested a review from jesswrd January 17, 2025 16:28
Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks really good! I did a pass over everything apart from web, and mostly just have minor comments and questions.

}

- (void)didTapGroundOverlayWithIdentifier:(NSString *)identifier {
if (!identifier) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here and in the next method: the identifier params were annotated as non-nullable in the interface, so the implementation code shouldn't be handling nil.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. Removed the null check.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still here in this method.

NS_ASSUME_NONNULL_BEGIN

@interface FGMUtils : NSObject
+ (UIImage *)iconFromBitmap:(FGMPlatformBitmap *)platformBitmap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new APIs need declaration comments.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be given a more specific name? It's already the case that this plugin has at least one other file of utils (the conversion function file).

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I renamed it FGMImageUtils.

final PlatformBitmap image;
final PlatformLatLng? position;
final PlatformLatLngBounds? bounds;
final PlatformDoublePair? anchor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't this conceptually a point? We already have PlatformPoint, so shouldn't need a new PlatformDoublePair for this unless there's a conceptual difference in meaning here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this to PlatformPoint.

Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some scattered minor issues remaining. Feel free to split out the first sub-PR at this point!

if (west <= east) {
longitudeOffset = position.longitude - west;
} else {
longitudeOffset = position.longitude - west;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same in both branches; is that a copy/paste mistake?

If not, it should be factored out of the if/else.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(And if so, that suggests missing test coverage for the behavior in these branches.)

@import GoogleMaps;

#import <OCMock/OCMock.h>
#import <google_maps_flutter_ios/FGMGroundOverlayController_Test.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


#import <google_maps_flutter_ios/FGMGroundOverlayController_Test.h>

#import <OCMock/OCMock.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be grouped with the other framework imports. Grouping is by type of thing import, not by @ vs #.

/// Returns true if a ground overlay with the given identifier exists on the map.
- (bool)hasGroundOverlaysWithIdentifier:(NSString *)identifier;

/// Returns FGMPlatformGroundOverlay for identifier.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Returns the ground overlay with the given identifier."

registrar:(NSObject<FlutterPluginRegistrar> *)registrar
screenScale:(CGFloat)screenScale {
[self setConsumeTapEvents:groundOverlay.clickable];
[self setVisible:groundOverlay.visible];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on investigation of similar methods in a recent PR (which hasn't landed), this needs to be last to avoid potential flickering (likely due to Google Maps using its own render thread), so should be at the end of the method, and have a comment saying that nothing should be added after it.

}

- (void)didTapGroundOverlayWithIdentifier:(NSString *)identifier {
if (!identifier) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still here in this method.

static UIImage *scaleImage(UIImage *image, double scale);
static UIImage *scaledImageWithScale(UIImage *image, CGFloat scale);
static UIImage *scaledImageWithSize(UIImage *image, CGSize size);
static UIImage *scaledImage(UIImage *image, NSNumber *width, NSNumber *height, CGFloat screenScale);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The declaration comments for all of these functions should be here, on the declarations, rather than on the definitions below.

#import "FGMImageUtils.h"
#import "Foundation/Foundation.h"

static UIImage *scaleImage(UIImage *image, double scale);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be scaled, not scale, for consistency in naming.

NSNumber *zoomLevel) {
/// Dummy image is used as image is required field of FGMPlatformGroundOverlay and converting
/// image back to bitmap image is not currently supported.
FGMPlatformBitmap *mockImage =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be called placeholderImage rather than mockImage, as "mock" generally refers to a specific test construct.

auto-submit bot pushed a commit that referenced this pull request Feb 3, 2025
…#8518)

This PR contains platform interface for the upcoming ground overlays support (#8432). Original PR with all the changes hasn't been approved yet but it was [okayed](#8432 (review)) to create the first sub-PR.

I'm not the author of the original PR but helping @jokerttu while he's on vacation.

Linked issue: flutter/flutter#26479
@jokerttu jokerttu force-pushed the feat/google_maps_flutter_ground_overlay_support branch from 9f427b8 to cda9938 Compare February 4, 2025 13:57
@jokerttu
Copy link
Contributor Author

jokerttu commented Feb 5, 2025

I've addressed the last remaining review comments and split the platform-specific implementations into a separate PR: #8563.
Apologies for the delay!

* @return the identifier of the ground overlay.
* @throws IllegalArgumentException if required fields are missing or invalid.
*/
static String interpretGroundOverlayOptions(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add non null or nullable annotations to the objects in this method.

* asset files stored in the application's assets directory.
* @param density the density of the display, used to calculate pixel dimensions.
* @param wrapper the BitmapDescriptorFactoryWrapper to create BitmapDescriptor.
* @return the identifier of the ground overlay.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you know can you add more about the mutability of the ground overlay identifier. For example how long it is a valid id.

sink.setClickable(groundOverlay.getClickable());
sink.setImage(toBitmapDescriptor(groundOverlay.getImage(), assetManager, density, wrapper));
if (groundOverlay.getPosition() != null) {
assert groundOverlay.getWidth() != null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asserts I believe are compiled out so the else condition can still NPE here. Can you convert this assert to a check or handle null width?

sink.setImage(toBitmapDescriptor(groundOverlay.getImage(), assetManager, density, wrapper));
if (groundOverlay.getPosition() != null) {
assert groundOverlay.getWidth() != null;
if (groundOverlay.getHeight() != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe these are the same except the width value. Can you minimize the scope by either making the check cover width only?

new Messages.PlatformGroundOverlay.Builder()
.setGroundOverlayId(groundOverlayId)
.setImage(dummyImage)
.setWidth((double) groundOverlay.getWidth())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use doubles here but floats above for width?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pigeon uses doubles and google maps uses floats, so conversion between these need to be done while converting values between PlatformGroundOverlay and GoogleMaps SDK GroundOverlay object

.setVisible(groundOverlay.isVisible())
.setClickable(groundOverlay.isClickable());

if (isCreatedWithBounds) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is possible to determine this instead of having it be passed in? For example could a non null getBounds or getPosition indicate that one of those should be used?

The code above in interpretGroundOverlayOptions seem to imply that position is more important that bounds. This code seem to imply that if bounds are used then position should not be.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Google maps object internally have both, bounds and position when the object is created using either of them. When bounds are used, the position representes the achor point in LatLng format for the ground overlay. And if position (with width/height) the bounds are calculated automatically as well.
For integration testing purposes we need track which creation method was used, to be able to check that conversions are handled correctly on each platform.


/** Receiver of GroundOverlayOptions configuration. */
interface GroundOverlaySink {
void setTransparency(float transparency);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add javadocs to these methods for where someone can learn more about the inputs either here or in groundOverlayController

For example what are the min and max values for transparency. Can zindex be negative? what does setClickable do? etc.

GroundOverlayController groundOverlayController =
groundOverlayIdToController.get(groundOverlayId);
if (groundOverlayController != null) {
groundOverlayController.remove();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the order of these removals matter? if so please add a comment explaining the order.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order does not matter here

String groundOverlayId = platformGroundOverlay.getGroundOverlayId();
GroundOverlayController groundOverlayController =
groundOverlayIdToController.get(groundOverlayId);
if (groundOverlayController != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is an error to call this with something that cant get a groundOverlayController? If so whos error is it (plugin or host app)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Api has updateGroundOverlays method takes GroundOverlayUpdates object as parameter:
https://github.com/flutter/packages/blob/ff7724c18a803542fc709f942c3bf1cecf7e4864/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/ground_overlay_updates.dart
This should be handled by the dart code so that changes only contains items that are available on map.
Not sure would it be good to throw error here if change is triggered for groundoverlay that is not added to the map beforehand. It is indeed misuse of the api, and this is tested in integration tests.

@jokerttu jokerttu force-pushed the feat/google_maps_flutter_ground_overlay_support branch 2 times, most recently from dcf54a1 to caf5ead Compare March 17, 2025 10:09
auto-submit bot pushed a commit that referenced this pull request Mar 18, 2025
This PR contains platform implementations for the ground overlays support (#8432).

Linked issue: flutter/flutter#26479
@jokerttu jokerttu force-pushed the feat/google_maps_flutter_ground_overlay_support branch from caf5ead to 89366c8 Compare March 18, 2025 15:33
@jokerttu
Copy link
Contributor Author

@stuartmorgan This PR now contains only app-facing plugin changes.
Had to update minimum supported SDK versions to Flutter 3.27/Dart 3.6 to match the android package:

environment:
sdk: ^3.6.0
flutter: ">=3.27.0"

@jokerttu jokerttu requested a review from stuartmorgan-g March 18, 2025 15:51
Copy link
Contributor

@stuartmorgan-g stuartmorgan-g left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@jokerttu jokerttu added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 18, 2025
@auto-submit auto-submit bot merged commit f2e2987 into flutter:main Mar 18, 2025
82 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/flutter that referenced this pull request Mar 19, 2025
github-merge-queue bot pushed a commit to flutter/flutter that referenced this pull request Mar 19, 2025
flutter/packages@d450e1b...dd781d4

2025-03-19 [email protected] [camera_avfoundation] Tests
backfilling - part 4 (flutter/packages#8854)
2025-03-18 [email protected] [camera_avfoundation] Tests
backfilling - part 5 (flutter/packages#8873)
2025-03-18 [email protected] [video_player]: reduce video player
position update interval from 500ms to 100ms (flutter/packages#8346)
2025-03-18 [email protected] [google_maps_flutter] Support
for Ground Overlay (flutter/packages#8432)
2025-03-18 [email protected] [google_maps_flutter] Ground
overlay support - platform impls (flutter/packages#8563)

If this roll has caused a breakage, revert this CL and stop the roller
using the controls here:
https://autoroll.skia.org/r/flutter-packages-flutter-autoroll
Please CC [email protected] on the revert to ensure that a
human
is aware of the problem.

To file a bug in Flutter:
https://github.com/flutter/flutter/issues/new/choose

To report a problem with the AutoRoller itself, please file a bug:
https://issues.skia.org/issues/new?component=1389291&template=1850622

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
autosubmit Merge PR when tree becomes green via auto submit App p: google_maps_flutter
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[google_maps_flutter] Support for Ground Overlay
5 participants