Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refreshing GoogleMaps tile server works in JavaScript but not in GWT

I'm displaying a weather map in a GWT application. I'm using GWT 2.7 and the GWT wrapper of the GoogleMaps JavaScript API available here (gwt-maps-3.8.0-pre1.zip).

I use a tile server to fetch the weather, which updates every 5 minutes. On the 5 minute mark, I refresh the map by setting the zoom to 1 and then back to the original, by triggering a resize, and by removing and then adding the weather layer again.

This worked fine. However, recently I noticed that this no longer works: the refresh never even goes to the tile server, so no new weather is displayed. If you leave my map up for 12 hours, you'll be looking at weather that's 12 hours old. Previously, the map would automatically stay updated. I haven't changed any of my code. So my guess is that something changed in the underlying GoogleMaps JavaScript API.

However, I then created this simple pure-JavaScript example:

<!DOCTYPE html>
<html>
    <head>
        <title>Map Test</title>
        <style type="text/css">
            html, body { height: 100%; margin: 0; padding: 0; }
            #map {
                width:90%;
                height: 90%;
                display:inline-block;
            }
        </style>
    </head>
<body>
<div id="map"></div>
<button type="button" onClick="refreshMap()">Refresh</button>
<script type="text/javascript">

    var map;
    var tileNEX;

    function initMap() {

        var mapOptions = {
            zoom: 8,
            center: new google.maps.LatLng(42.5, -95.5),
            mapTypeId: google.maps.MapTypeId.ROADMAP
        };

        map = new google.maps.Map(document.getElementById('map'), mapOptions);

        tileNEX = new google.maps.ImageMapType({
            getTileUrl: function(tile, zoom) {
                return "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/" + zoom + "/" + tile.x + "/" + tile.y +".png?"+ (new Date()).getTime(); 
            },
            tileSize: new google.maps.Size(256, 256),
            opacity:0.60,
            name : 'NEXRAD',
            isPng: true
        });

        map.overlayMapTypes.setAt("0",tileNEX);
    }

    function refreshMap(){
        var zoom = map.getZoom();
        map.setZoom(1);
        map.setZoom(zoom);

        //google.maps.event.trigger(map, 'resize');

        //var layer = map.overlayMapTypes.getAt(0);
    //map.overlayMapTypes.setAt(0, null);
    //map.overlayMapTypes.setAt(0, layer);
    }

</script>
<script async defer src="https://maps.googleapis.com/maps/api/js?callback=initMap">
</script>
</body>
</html>

To my surprise, this still works fine. Whenever you click the Refresh button, the map goes to the tile server and fetches new tiles.

So I tried doing the exact same thing in GWT:

package com.example.client;

import com.google.gwt.ajaxloader.client.AjaxLoader;
import com.google.gwt.ajaxloader.client.AjaxLoader.AjaxLoaderOptions;
import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.dom.client.Document;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.maps.gwt.client.GoogleMap;
import com.google.maps.gwt.client.LatLng;
import com.google.maps.gwt.client.MapOptions;
import com.google.maps.gwt.client.MapType;
import com.google.maps.gwt.client.MapTypeId;

public class GwtMapTest implements EntryPoint {

    @Override
    public void onModuleLoad() {
        AjaxLoaderOptions options = AjaxLoaderOptions.newInstance();
        options.setOtherParms("sensor=false");
        Runnable callback = new Runnable() {
            public void run() {
                createMap();
            }
        };
        AjaxLoader.loadApi("maps", "3", callback, options);
    }

    public void createMap() {

        MapOptions mapOpts = MapOptions.create();
        mapOpts.setZoom(4);
        mapOpts.setCenter(LatLng.create(37.09024, -95.712891));
        mapOpts.setMapTypeId(MapTypeId.TERRAIN);
        mapOpts.setStreetViewControl(false);

        final GoogleMap map = GoogleMap.create(Document.get().getElementById("map_canvas"), mapOpts);
        addWeatherLayer(map);   

        Button button = new Button("Gwt Refresh");
        button.addClickHandler(new ClickHandler(){

            @Override
            public void onClick(ClickEvent event) {
                refreshWeatherLayer(map);
            }
        });

        Button nativeButton = new Button("Native Refresh");
        nativeButton.addClickHandler(new ClickHandler(){

            @Override
            public void onClick(ClickEvent event) {
                nativeRefreshWeatherLayer(map);
            }
        });

        RootPanel.get().add(button);
        RootPanel.get().add(nativeButton);
    }

    public native void addWeatherLayer(GoogleMap map) /*-{
        var imageMapType = new $wnd.google.maps.ImageMapType({
            getTileUrl: function(coord, zoom) {
                return "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/"+ zoom + "/" + coord.x + "/" + coord.y + ".png";
            },
            tileSize: new $wnd.google.maps.Size(256, 256),
            opacity:.50,
            isPng: true
        });

        map.overlayMapTypes.setAt("0", imageMapType);

    }-*/;

    private void refreshWeatherLayer(GoogleMap map){
        double zoom = map.getZoom();
        map.setZoom(1);
        map.setZoom(zoom);

        MapType layer = map.getOverlayMapTypes().getAt(0);
        map.getOverlayMapTypes().setAt(0, null);
        map.getOverlayMapTypes().setAt(0, layer);
    }

    private native void nativeRefreshWeatherLayer(GoogleMap map) /*-{
        var zoom = map.getZoom();
        map.setZoom(1);
        map.setZoom(zoom);

        $wnd.google.maps.event.trigger(map, 'resize');

        var layer = map.overlayMapTypes.getAt(0);
        map.overlayMapTypes.setAt(0, null);
        map.overlayMapTypes.setAt(0, layer);
    }-*/;

}

This should do the same thing as the pure-JavaScript example. Instead, when I click the button, the map does refresh (I see the weather layer blink), but it doesn't actually go to the tile server for new tiles.

Weirder still, this "sorta" works in Internet Explorer: maybe 1 out of 3 times I click the button, the map actually goes to the tile server. But in Chrome, it never goes to the tile server when I click the button.

To determine this, I am looking at the network tab of the browser tools. I would expect the map to hit the tile server every time I click the button. That's what it does in pure JavaScript, and that's what it used to do in GWT, but sometime in the last couple months the GWT behavior has changed.

I don't think this is a browser caching issue. The problem is not that the map tries to fetch new tiles but gets old ones, it's that it never tries to fetch new tiles. I click the button, and I see nothing happening in the network tab of the developer tools.

  • Why do I see a different behavior in JavaScript vs GWT?
  • Why do I see a different behavior in different browsers?
  • Is the GWT GoogleMaps library doing some kind of internal caching? Is there a way to disable this?
  • Why has this behavior apparently changed?
  • How can I make the GWT map refresh by going to the tile server for new tiles?
like image 509
Kevin Workman Avatar asked May 12 '16 17:05

Kevin Workman


1 Answers

This is not the final answer. But contains significant information.

To manage http calls GWT have a set of classes that act like a browser, and is where i belive the problem is.

If the same code worked before is possible that the tile server include the Cache header (Cache-Control:max-age=300) recently. And for some reason the GWT code are not managing this header properly.

If i am right, a way to evict the problem is to append a parameter with the current time in the GWT call (like the browser version of your code).

public native void addWeatherLayer(GoogleMap map) /*-{
    var imageMapType = new $wnd.google.maps.ImageMapType({
        getTileUrl: function(coord, zoom) {
            return "http://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/"+ zoom + "/" + coord.x + "/" + coord.y + ".png?" + new Date().getTime();
        },
        tileSize: new $wnd.google.maps.Size(256, 256),
        opacity:.50,
        isPng: true
    });

    map.overlayMapTypes.setAt("0", imageMapType);

}-*/;
like image 51
fhofmann Avatar answered Oct 22 '22 20:10

fhofmann