NativeScript / plugins

@nativescript plugins to help with your developments.
https://docs.nativescript.org/plugins/index.html
Apache License 2.0
187 stars 104 forks source link

Unable to change transparency on TileOverlay on Android #565

Open dlcole opened 5 months ago

dlcole commented 5 months ago

I'm migrating from the kefahB/nativescript-google-maps plugin to @nativescript/google-maps.
My app animates weather radar by composing an array of TileOverlays and then iteratively changing the TileOverlay's transparency to hide one overlay and display the next.

While transparency can be specified in the TileOverlayOptions when creating a TileOverlay, it can't be modified directly afterwards. Instead, you have to use native methods, such as on iOS:

tileOverlay.native.opacity = 1.0;  // display tileOverlay

On Android, there's:

tileOverlay.native.setTransparency(0.0);  // display tileOverlay

But this seems to have no effect. I can see in the debugger and via tileOverlay.native.getTransparency() that the native TileOverlay's transparency property has indeed been changed, but there is no change in the display.

dlcole commented 3 months ago

I've had a chance to look at this further... setting the tileOverlay transparency via tileOverlay.native.setTransparency() does indeed work. The problem in my case is with the tileProvider. All the tiles were the same, thus alternating the transparencies of the tileOverlays had no visible effect, since there was no change in the image.

What I'm doing is animating weather radar on a map, getting the 6 most recent radar images and alternating the transparencies to create the animation. My code was based on this question - note the different tileProvider implementations between iOS and Android.

This code works just fine on iOS, and is similar to the code referenced above:

import { GoogleMap, MapView, MarkerOptions, CameraUpdate, UrlTileProvider } from '@nativescript/google-maps';

for (let timestamp of timestamps) {

      const tileProvider = new UrlTileProvider((x, y, z) => {
        return `https://tilecache.rainviewer.com/v2/radar/${timestamp}/256/${z}/${x}/${y}/4/1_0.png`;
      }, 256);

    ...
}

The same code on Android returns images that are all identical. I believe it has to do with how the URL is composed in the tileProvider callback, and that the same timestamp is used for all images. I suspect this because it would explain all the images being the same, and because the sample code for Android referenced above has a specific method for setting the url to be returned, setUrl() and which is then referenced by the getTileUrl(x,y,z) method.

When I try using this code with the @nativescript/google-maps plugin, I get the error

Error: java.lang.NullPointerException: tileProvider must not be null.

when I try to add the tileOverlay:

let newTileOverlay = map.addTileOverlay(tileOverlayOptions);

So, the question now becomes is there a way to modify the url returned by the tileProvider callback on Android?

dlcole commented 3 months ago

In an effort to answer my question above, I'm attempting to patch the UrlTileProvider class in @nativescript/google-maps/index.android.js by adding a setUrl method and then referencing the specified url in the getTileUrl method, similar to what I was doing on Android with the prior plugin. Here's the code:

export class UrlTileProvider extends TileProvider {
  constructor(callback, size = 256) {
      super(null);
      this._callback = callback;
      this._url = "";
      const ref = new WeakRef(this);
      const provider = com.google.android.gms.maps.model.UrlTileProvider.extend({
          setUrl(url) {
              const owner = ref.get();  // returns undefined 
              owner._url = url;
            },
          getTileUrl(x, y, zoom) {
              const owner = ref.get();
              if (owner) {
                  if (owner._url.length > 0) {
                      var url = owner._url.replace('{x}', x).replace('{y}', y).replace('{z}', zoom);
                      return new java.net.URL(url);
                  }
                  else {
                      return new java.net.URL(owner._callback(x, y, zoom) || null);
                  }

              }
              return null;
          },
      });
      this._native = new provider(size, size);
  }
  get native() {
      return this._native;
  }
}

The problem is that in setUrl, ref.get() returns undefined, even though it resolves successfully in getTileUrl. I suspect this is simply a WeakRef issue, but I'm not sure how to proceed.