Skip to content
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

[google_maps_flutter_web] Initial support for custom overlays #3538

Merged
merged 8 commits into from
Jul 31, 2023

Conversation

elitree
Copy link
Contributor

@elitree elitree commented Mar 23, 2023

This is a resubmission of flutter/plugins#6982 from the now archived flutter plugins repo. I'm submitting the changes from the original author, @AsturaPhoenix. The original description is below.


Saves tile bytes to blobs and uses img elements to decode and render. Does not implement opacity, perform caching, or serve placeholder images.

Issue: Fixes flutter/flutter#98596

Known issues:

Pre-launch Checklist

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I read and followed the relevant style guides and ran the auto-formatter. (Unlike the flutter/flutter repo, the flutter/packages repo does use dart format.)
  • I signed the CLA.
  • The title of the PR starts with the name of the package surrounded by square brackets, e.g. [shared_preferences]
  • I listed at least one issue that this PR fixes in the description above.
  • I updated pubspec.yaml with an appropriate new version according to the pub versioning philosophy, or this PR is exempt from version changes.
  • I updated CHANGELOG.md to add a description of the change, following repository CHANGELOG style.
  • I updated/added relevant documentation (doc comments with ///).
  • I added new tests to check the change I am making, or this PR is test-exempt.
  • All existing and new tests are passing.

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

@elitree elitree force-pushed the gmaps-flutter-ap-fix branch 3 times, most recently from 5e536d5 to 026f4e4 Compare March 24, 2023 14:28
Comment on lines -354 to -357
expect(capturedPolylines.first.polylineId.value, 'polyline-1');
});

testWidgets('empty infoWindow does not create InfoWindow instance.',
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Original comment by @AsturaPhoenix:

Leaving a comment here because it wasn't obvious to me the second time I looked at it:

This test was removed because it became trivial once using Mockito verification; it essentially tests to make sure that Marker.infoWindow defaults to InfoWindow.noText. On the other hand, the argument could be made to keep it anyway (e.g. to construct the verification with an explicit infoWindow: InfoWindow.noText); let me know if you'd prefer that.

@elitree elitree marked this pull request as ready for review March 27, 2023 13:26
@elitree elitree requested a review from ditman as a code owner March 27, 2023 13:26
@elitree
Copy link
Contributor Author

elitree commented Mar 27, 2023

FYI @ditman, I think you were the reviewer on the original PR when it was in the plugins repo, and I see that you're the reviewer here as well. I'm also interested in seeing custom tiles work for the web side of google maps flutter, so I asked @AsturaPhoenix if I could move his PR to the new repo and he's cool with that (thanks AP!). That's the background for this one.

Let me know if you have any questions and I can try to answer them or solicit AP's help if needed. Thanks!

@elitree elitree force-pushed the gmaps-flutter-ap-fix branch from f71eaf0 to b668a6f Compare April 3, 2023 17:31
@elitree
Copy link
Contributor Author

elitree commented Apr 3, 2023

Resolved a changelog conflict and rebased on main.

@AsturaPhoenix
Copy link
Contributor

FYI I just moved over my local repo as well, so that's one less hurdle if we do end up needing to collaborate. Thanks for taking over @elitree!

@stuartmorgan
Copy link
Contributor

Update from triage: waiting for @ditman to have review bandwidth for maps PRs.

@elitree elitree force-pushed the gmaps-flutter-ap-fix branch from 3435e6f to 1ba5ef7 Compare May 17, 2023 19:09
@elitree
Copy link
Contributor Author

elitree commented May 17, 2023

Rebased on main to resolve conflicts.

@elitree elitree force-pushed the gmaps-flutter-ap-fix branch from 1ba5ef7 to b7e167e Compare June 8, 2023 15:07
@stuartmorgan
Copy link
Contributor

@ditman Is this still in your review queue?

@ditman
Copy link
Member

ditman commented Jul 14, 2023

Absolutely

Copy link
Member

@ditman ditman left a comment

Choose a reason for hiding this comment

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

I want to tinker with this PR, is there any example that uses the tile overlays feature so I can see it working? Maybe the google_maps_flutter example app?

@ditman
Copy link
Member

ditman commented Jul 15, 2023

PS: There seems some tests breaking, it seems that the Mocks need to be regenerated (and committed):

https://cirrus-ci.com/task/5045655211606016?logs=drive#L55-L73

(Mocks can be (re)generated with: dart run build_runner build --delete-conflicting-outputs inside the example directory IIRC; there should be a build.yaml file with a little bit of config for the build runner)

@elitree elitree force-pushed the gmaps-flutter-ap-fix branch 3 times, most recently from 179bff4 to 0b5faf3 Compare July 17, 2023 16:27
@elitree
Copy link
Contributor Author

elitree commented Jul 17, 2023

PS: There seems some tests breaking, it seems that the Mocks need to be regenerated (and committed):

https://cirrus-ci.com/task/5045655211606016?logs=drive#L55-L73

(Mocks can be (re)generated with: dart run build_runner build --delete-conflicting-outputs inside the example directory IIRC; there should be a build.yaml file with a little bit of config for the build runner)

I updated the mocks today as well, thanks for the reminder.

@ditman
Copy link
Member

ditman commented Jul 19, 2023

I did some small changes to the _getTile method that should make it slightly faster, I'll publish this ASAP (I got a little bit distracted with the _DebugTileProvider on the example app that looked quite bad in the implementation (it wasn't respecting the 256px logical tile size, and it looked all stretched/scaled when testing on the web... I made it dpi-aware so it kinda looks sharper!)

@ditman
Copy link
Member

ditman commented Jul 19, 2023

I had some code ready to remove package:collection, but I just noticed that it's already used in the plugin here, so I guess it can stay 😭

Comment on lines 62 to 65
expect(img.naturalWidth, 0);
expect(img.naturalHeight, 0);

Copy link
Member

@ditman ditman Jul 19, 2023

Choose a reason for hiding this comment

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

This PR used to expect that img.hidden was true before load, and false afterwards. Is hidden really required? In my testing I didn't see any difference so I removed the attribute. @AsturaPhoenix / @elitree do you remember why was the attribute added in the first place, is it required?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it was to prevent the image decoding from blocking the main thread, but I can't find a version without hidden + decode so I'm not sure I ever ran a comparison. In any case we still have the bottleneck of populating the image data on the Dart/Flutter side, which has to happen on the main thread on web today.

I do think there's a difference in the case of bad image data though; if Tile.data is not a valid image, I think adding the img straight and setting the src will eventually make it display a browser-dependent broken image whereas hiding and decoding wouldn't, but I don't think that was the primary motivation.

Copy link
Member

Choose a reason for hiding this comment

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

I guess it can be added back if it becomes a problem. I think a "broken image" (maybe on windows?) is even useful for you to see that there's something fishy going on with your TileProvider!

As for rendering on the main thread, you're right. The only way to avoid that on the web, however, is to have a web-only TileProvider that knows how to delegate the getTile call to a service worker and an offscreen canvas (in that case the drawing might get more complicated, or you end up with a very big service worker that also includes flutter :P)

Comment on lines 9 to 11
/// For consistency with Android, this is not configurable.
// TODO(AsturaPhoenix): Verify consistency with iOS.
static const int logicalTileSize = 256;
Copy link
Member

@ditman ditman Jul 19, 2023

Choose a reason for hiding this comment

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

I am very curious about this logicalTileSize. From the API it seems that both a TileOverlay and each Tile can specify their size, so this implementation can do away with this logicalTileSize const.

Why is this fixed to 256, and do we need it?

(If this is 256, how come the example is creating 100x100 tiles? They look blurry on the web as they're scaled up to fit!)

((Also: can tiles be non-square?))

Copy link
Contributor

Choose a reason for hiding this comment

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

logicalTileSize is used to communicate to Google Maps the size of the tile for use in coordinate conversions and scaling. This is configurable in the JS API and can be rectangular, but I don't think it's configurable in the Android API. At a logical size of 256 x 256, the web impl appears consistent with the Android impl. I haven't tried it on iOS.

JS API: https://developers.google.com/maps/documentation/javascript/maptypes#MapTypeInterface
Android:
https://developers.google.com/maps/documentation/android-sdk/reference/com/google/android/libraries/maps/model/TileOverlay#tile-coordinates
https://developers.google.com/maps/documentation/android-sdk/tileoverlay#coordinates

The physical size can be different and will be scaled on web. I used to use this to display different levels of detail on NOAA charts from a WMS server, effectively requesting higher DPIs to show more features.

Copy link
Member

Choose a reason for hiding this comment

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

@AsturaPhoenix yes, I used the physical size being logicalSize * dpi to generate high DPI tiles too. Let's go with consistency for now, having a fixed logical size.

(PS: In iOS they also mention 256x256 tiles or 512x512 for "retina" displays: https://developers.google.com/maps/documentation/ios-sdk/tiles#high_dpi_tiles_for_retina_devices, I'm not sure if the iOS SDK supports sizes any other than those two (like a 3x tile))

@ditman ditman force-pushed the gmaps-flutter-ap-fix branch from 18003b2 to a324854 Compare July 20, 2023 05:38
@ditman
Copy link
Member

ditman commented Jul 20, 2023

When testing with the example app, the real performance hit happened here:

Not so much on this side of the implementation :)

(toImage and toByteData being very expensive)

@AsturaPhoenix
Copy link
Contributor

Yeah, unfortunately the other performance changes probably won't pass muster since they involve changing the API to pass canvases as tiles instead of byte arrays. (There are also unrelated optimizations around serialization of polylines and friends, but unfortunately eventually I ran into an expensive serialization we didn't seem able to avoid within GMS core, so that was the end of that.)

This LGTM too; I have one nit comment that I'll add, but it's probably not of consequence.

@AsturaPhoenix
Copy link
Contributor

When testing with the example app, the real performance hit happened here:

Not so much on this side of the implementation :)

(toImage and toByteData being very expensive)

Yes, it's quite painful. We can get a few ms back by using bmps instead of pngs, but we don't get jank-free until we pass canvases afaict. (flutter/flutter#116132)


controller.update(tileOverlay);

if (wasVisible || moved) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm the tiniest bit concerned that this might also cause any overlays on top of the overlay being updated to reload, but I haven't exercised this case, and my concern might be premature optimization.

Copy link
Member

Choose a reason for hiding this comment

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

This little bit of code concerns me a little bit as well, but we'll see in the real world :) I simplified the original if-else a little bit and ended up with this, and I might have over-simplified. I'll tag you whenever the first bug comes about this so you can tell me: "I TOLD YOU SO!" :p

Copy link
Contributor

@AsturaPhoenix AsturaPhoenix Jul 22, 2023

Choose a reason for hiding this comment

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

Oh actually you know what, rereading this more carefully I think it does warrant another pass. For example, there actually seems to be a bug (that I almost doubt anyone would hit) where if you change the Z index of a layer that's not visible, it becomes visible. (Purely based on my reading of the code; I haven't run a test, so I might be wrong.)

It'd probably also be worthwhile to add a regression test for this case. (I can also take care of this if nobody gets around to it over the weekend.)

And, since this is in a loop, the impact of remove/insert vs. set for the stable z-index case, although probably still a premature optimization, could be compounded if it were ever actually an issue.

Copy link
Contributor

Choose a reason for hiding this comment

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

Basically the fix if we want to do it this way is just to remove the moved condition entirely, but you know what I'd do if it were up to me.

@ditman
Copy link
Member

ditman commented Jul 21, 2023

@AsturaPhoenix in this case I see two optimizations:

  • Do not generate the Uint8List in the main thread on the web (that means something like this: https://developer.chrome.com/blog/offscreen-canvas/)
  • Make Tile.data an Object?, so each platform can pass what's most efficient to its implementation (in mobile it might be a byte array, but as you said on the web it might be a straight-up canvas element)

Copy link
Contributor

@AsturaPhoenix AsturaPhoenix left a comment

Choose a reason for hiding this comment

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

Okay, I don't think I have permission to push to the remote, but here's the patch (also adding some additional assertions in related tests to try to catch similar regressions):

diff --git a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart
index 4b95d8bbe..8b6b34694 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/example/integration_test/overlays_test.dart
@@ -68,6 +68,9 @@ void main() {
         tileProviders[1].getTile(any, any, any),
         tileProviders[2].getTile(any, any, any),
       ]);
+      verifyNoMoreInteractions(tileProviders[0]);
+      verifyNoMoreInteractions(tileProviders[1]);
+      verifyNoMoreInteractions(tileProviders[2]);
     });

     testWidgets('changeTileOverlays', (WidgetTester tester) async {
@@ -87,6 +90,8 @@ void main() {
         tileProviders[1].getTile(any, any, any),
       ]);
       verifyZeroInteractions(tileProviders[0]);
+      verifyNoMoreInteractions(tileProviders[1]);
+      verifyNoMoreInteractions(tileProviders[2]);

       // Re-enable overlay 0.
       controller.changeTileOverlays(
@@ -99,6 +104,22 @@ void main() {
         tileProviders[0].getTile(any, any, any),
         tileProviders[1].getTile(any, any, any),
       ]);
+      verifyNoMoreInteractions(tileProviders[0]);
+      verifyNoMoreInteractions(tileProviders[1]);
+      verifyNoMoreInteractions(tileProviders[2]);
+    });
+
+    testWidgets(
+        'updating the z index of a hidden layer does not make it visible',
+        (WidgetTester tester) async {
+      controller.addTileOverlays(<TileOverlay>{...tileOverlays});
+
+      controller.changeTileOverlays(<TileOverlay>{
+        tileOverlays[0].copyWith(zIndexParam: -1, visibleParam: false),
+      });
+
+      probeTiles();
+      verifyZeroInteractions(tileProviders[0]);
     });

     testWidgets('removeTileOverlays', (WidgetTester tester) async {
diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart
index 464fb319a..aa6c19173 100644
--- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/overlays.dart
@@ -63,14 +63,13 @@ class TileOverlaysController extends GeometryController {

     final bool wasVisible = controller.tileOverlay.visible;
     final bool isVisible = tileOverlay.visible;
-    final bool moved = tileOverlay.zIndex != controller.tileOverlay.zIndex;

     controller.update(tileOverlay);

-    if (wasVisible || moved) {
+    if (wasVisible) {
       _remove(controller);
     }
-    if (isVisible || moved) {
+    if (isVisible) {
       _insertZSorted(controller);
     }
   }


controller.update(tileOverlay);

if (wasVisible || moved) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Basically the fix if we want to do it this way is just to remove the moved condition entirely, but you know what I'd do if it were up to me.

@elitree elitree force-pushed the gmaps-flutter-ap-fix branch from a324854 to bae3ab7 Compare July 24, 2023 14:52
@elitree
Copy link
Contributor Author

elitree commented Jul 24, 2023

Hi @AsturaPhoenix, I sent an invite to collaborate, so maybe that will allow you to upload. I did just update the branch to resolve some conflicts, but didn't add your changes yet (I'm not sure I was looking at it right, but the integration test looks slightly different than what I'm seeing in my branch edit: oops, I was looking at overlay_test instead of overlays_test, ha).

Please feel free to update as you see fit!

@ditman
Copy link
Member

ditman commented Jul 24, 2023

@AsturaPhoenix, @elitree thanks for the fix over the weekend!

Copy link
Contributor

@stuartmorgan stuartmorgan left a comment

Choose a reason for hiding this comment

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

LGTM

@stuartmorgan stuartmorgan added the autosubmit Merge PR when tree becomes green via auto submit App label Jul 31, 2023
@auto-submit auto-submit bot merged commit 9e21922 into flutter:main Jul 31, 2023
auto-submit bot pushed a commit to flutter/flutter that referenced this pull request Aug 1, 2023
flutter/packages@60e9a54...3dc00c1

2023-08-01 engine-flutter-autoroll@skia.org Manual roll Flutter from 1d44fbd to 1d59196 (18 revisions) (flutter/packages#4621)
2023-07-31 32242716+ricardoamador@users.noreply.github.com Update the cirrus key jul-31-2023 (flutter/packages#4618)
2023-07-31 jpnurmi@gmail.com [path_provider_platform_interface] Add getApplicationCachePath() (flutter/packages#4614)
2023-07-31 elitree@gmail.com [google_maps_flutter_web] Initial support for custom overlays (flutter/packages#3538)
2023-07-31 34410554+maciejbrzezinski@users.noreply.github.com [camera] Removed the microphone permission request from availableCameras on Web (flutter/packages#4263)

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 flutter-ecosystem@google.com,rmistry@google.com 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://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
vashworth pushed a commit to vashworth/flutter that referenced this pull request Aug 2, 2023
flutter/packages@60e9a54...3dc00c1

2023-08-01 engine-flutter-autoroll@skia.org Manual roll Flutter from 1d44fbd to 1d59196 (18 revisions) (flutter/packages#4621)
2023-07-31 32242716+ricardoamador@users.noreply.github.com Update the cirrus key jul-31-2023 (flutter/packages#4618)
2023-07-31 jpnurmi@gmail.com [path_provider_platform_interface] Add getApplicationCachePath() (flutter/packages#4614)
2023-07-31 elitree@gmail.com [google_maps_flutter_web] Initial support for custom overlays (flutter/packages#3538)
2023-07-31 34410554+maciejbrzezinski@users.noreply.github.com [camera] Removed the microphone permission request from availableCameras on Web (flutter/packages#4263)

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 flutter-ecosystem@google.com,rmistry@google.com 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://bugs.chromium.org/p/skia/issues/entry?template=Autoroller+Bug

Documentation for the AutoRoller is here:
https://skia.googlesource.com/buildbot/+doc/main/autoroll/README.md
@elitree elitree deleted the gmaps-flutter-ap-fix branch August 24, 2023 19:28
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 platform-web
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[google_maps_flutter_web] Add Support for Custom Map Tile Overlays
4 participants