39

I have some dynamic tile content to display on top of a map (specifically, weather images -- radar, satellite, temperatures, etc.). I'm using Google Maps API for Android v2.

The problem I'm having is that apparently the only way to update the tile images (i.e. when new data arrives, or when the frame advances in a time lapse animation) is to call TileOverlay.clearImageCache. Unfortunately, when I do that, the tile overlay flickers off for a moment. This is because clearImageCache immediately removes the existing tile images from the display, but there's a delay before it can decode and display new tile images.

I'm using a custom TileProvider that caches the tile images, rather than fetching them from the server each time. But even when it's only feeding cached tiles (i.e. there's no significant delay imposed by my TileProvider.getTile implementation), there's still enough of a delay in the process that the user can see a flicker.

Does anyone know of a way to avoid this flicker? Is there some way I can double-buffer the tile overlay? I tried to double-buffer it with two TileOverlays attached to the map, one of which is invisible. But the invisible TileOverlay does not start fetching any tiles from my TileProvider -- even after I call clearImageCache.

Saggy
  • 624
  • 8
  • 23
erickj00001
  • 590
  • 4
  • 8
  • 1
    Have you tried adding the new ones first before deleting the old ones? – yarian Feb 28 '13 at 18:06
  • 2
    Wouldn't help. The tiles are semi-transparent, so displaying two sets of tiles together would darken everything. I'd just be trading one visual artifact for another. Thanks for the suggestion, though. – erickj00001 Mar 06 '13 at 17:22
  • How about adding the new ones under (z-order) your background mapping, then shoving-them-in-front-and-simultaneously-shoving-the-old-ones-backwards. Essentially double-buffering but using z-order instead of visibility? – GHC Mar 08 '13 at 01:06
  • That doesn't work either. TileOverlay remains visible even if I set the Z index to a very low negative number, which suggests that it's hard-coded to always display overlays above the map. The Z index just controls which overlays are drawn on top with respect to other overlays. – erickj00001 Mar 11 '13 at 22:26
  • @erickj00001 have you found any workarounds? This is a difficult problem for anyone trying to "animate" overlays. – aez Mar 08 '14 at 12:48
  • 2
    I haven't found any good workarounds. What we ended up doing is using GroundOverlay instances instead, which is a pain because we have to explicitly code the logic to keep track of which tiles are needed. What's worse, they have to be destroyed and re-created during animation, which is slow. (There's a setImage method, but it has a huge memory leak: http://code.google.com/p/gmaps-api-issues/issues/detail?id=6286) – erickj00001 Mar 24 '14 at 16:46

4 Answers4

1

Would it be possible to load the upcoming tile image however have the visibility set to false? Tile.setVisible(false);

Then when you want to change (after the upcoming tile has loaded), set the upcoming tile to be visible and the current tile to be invisible?

CurrentTile.setVisible(false);
NewTile.setVisible(true);

This way the change all occurs within the same render frame, and there is no delay waiting for cached images to load.

Ice Phoenix
  • 1,001
  • 3
  • 16
  • 34
  • Hmm, if you mean setting the TileOverlay itself to invisible, then I already tried that. See my original post -- the problem is that it does not start fetching any tiles while it's invisible. If you mean setting a property of individual tiles, well, I don't think you can get access to individual tile graphic objects when using TileOverlay. – erickj00001 Mar 11 '13 at 22:27
0

A fairly good solution would be to separate tile downloading and caching from TileProvider. This way you can fully control when they are downloaded and only replace byte[] references after downloading all.

This might be a bit more complex, because you have to take care of the current visible region and zoom not to download them all, but only those that will be visible.

Edit

Tested with the following code:

try {
    InputStream is = getAssets().open("tile1.jpg");
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    int b = is.read();
    while (b != -1) {
        baos.write(b);
        b = is.read();
    }
    byte[] array = baos.toByteArray();
    cache = new Tile(256, 256, array);
    is = getAssets().open("tile2.jpg");
    baos = new ByteArrayOutputStream();
    b = is.read();
    while (b != -1) {
        baos.write(b);
        b = is.read();
    }
    array = baos.toByteArray();
    nextCache = new Tile(256, 256, array);
} catch (IOException ex) {
    Log.e("tag", "error reading tiles", ex);
}

tileOverlay = map.addTileOverlay(new TileOverlayOptions().tileProvider(new TileProvider() {
    @Override
    public Tile getTile(int x, int y, int zoom) {
        return cache;
    }
}));

and somewhere later:

Tile temp = cache;
cache = nextCache;
nextCache = temp;
tileOverlay.clearTileCache();

"Fastest" possible code still fails.

If you cannot switch to GroundOverlay or Markers, another idea is trying to use third party map tiles, your current weather tiles above and next tiles below, so they can load and switch them (using zOrder) after few seconds.

MaciejGórski
  • 22,187
  • 7
  • 70
  • 94
  • That's what I was originally hoping I could do -- but like I said in the original post, there's a flicker even when all the tiles are already cached and no significant delay is imposed by TileProvider.getTile. This is because clearImageCache immediately removes the tiles from the display, but TileOverlay still has some work to do before it can display the new tiles. – erickj00001 Mar 24 '13 at 16:03
  • 1
    Added code I tested with and you are right. There seems to be no way to remove flicker. Maybe it would be better to consider using GroundOverlay or Markers with onMarkerClick returning false, so it is not centered and no info window is shown. – MaciejGórski Mar 24 '13 at 20:44
0

The solution I found for this is to make the layer have a transparency of 1. This makes them hidden but still requesting tiles. You can then switch which layers have a transparency of 1 and 0.

There is still possibility of some flicker due to having a render call in between the changing of the transparency of the two layers. There is no way to make this an atomic operation.

Also found that there's not a great way to know when the layer has fully loaded the tile. Even after you return the tile from the TileProvider, Google Maps has to do some processing on it, so you need some delay before switching tiles.

Michael Krussel
  • 2,586
  • 1
  • 14
  • 16
0

The solution that worked for me (I tried refreshing tiles every second):

    // Adding "invisible" overlay
    val newTileOverlay =  mMap?.addTileOverlay(
        TileOverlayOptions()
            .tileProvider(getTileProvider()).transparency(1f).visible(true)
    )

    mTileOverlay?.transparency = 0.5f // making previous overlay visible
    mOldTileOverlay?.remove() // removing previously displayed visible overlay
    mOldTileOverlay = mTileOverlay
    mTileOverlay = newTileOverlay

So we have two layers at a time (one visible and one invisible). I'm not sure how it influences the performance.

mturnau
  • 1
  • 1