Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Disable google maps tile fading or detect if map is fully rendered?

Question

Is there a way to disable the fading of google maps tiles? Or is there a way to detect if a map is fully rendered?

Problem

I'd like to get an event when a map is fully loaded (and rendered) and take a screenshot. I tried these events as suggested in several answers

google.maps.event.addListenerOnce(map, 'tilesloaded', function(){
  // screenshot
});

google.maps.event.addListener(map, 'idle', function(){
  // screenshot
});

window.onload = function(e){
  // screenshot
};

but the tiles are still fading, even after all is loaded and the above mentioned events fired.

It looks like this: left is google maps, right is an automated screenshot that is taken after the events fired:

enter image description here

Code

The code is in html and JavaFX

demo.html

<!DOCTYPE html>
<html>
<head>

    <script src="http://maps.google.com/maps/api/js?sensor=false"></script>

    <style>
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    #mapcanvas {
      /* height: 4000px; we need the value from java */
      width: 100%
    }
    </style>


</head>
<body>
<div id="mapcanvas"></div>

<script type="text/javascript">

        console.log("Loading map tiles");

        // set map canvas height
        document.getElementById('mapcanvas').style.height = window.mapCanvasHeight;

        document.map = new google.maps.Map(document.getElementById("mapcanvas"));

        // the global window object is used to set variables via java
        var latlng = new google.maps.LatLng(window.lat, window.lon);

        // https://developers.google.com/maps/documentation/javascript/reference?hl=en
        var Options = {
            zoom: 17,
            center: latlng,
            mapTypeId: google.maps.MapTypeId.SATELLITE,
            disableDefaultUI: true,
        };

        var map = new google.maps.Map(document.getElementById("mapcanvas"), Options);
        document.goToLocation = function(x, y) {

            console.log("goToLocation, lat: " + x +", lon:" + y);

            var latLng = new google.maps.LatLng(x, y);
            map.setCenter(latLng);

        }

        // this doesn't work properly because some tiles fade in and hence you get a snapshot with faded tiles
        google.maps.event.addListenerOnce(map, 'tilesloaded', function(){

            console.log("tilesloaded");

            java.onMapLoadingFinished();

        });

        // This event is fired when the map becomes idle after panning or zooming.
        // it works after all tiles were first loaded and you zoom afterwards ( but doesn't work a 100% all the time )
        // initially you still get faded tiles
        google.maps.event.addListener(map, 'idle', function(){

            console.log("idle");

            // java.onMapLoadingFinished();

        });

        window.onload = function(e){

            console.log("window.onload");

            // java.onMapLoadingFinished();

        };

</script>
</body>
</html>

GoogleApp.java

import java.net.URL;

import javafx.application.Application;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker.State;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import netscape.javascript.JSObject;

/**
 * Load google maps map and take a snapshot from it
 */
public class GoogleApp extends Application {

    // initial lat/lon
    double lat = 38.897676;
    double lon = -77.036483;

    double browserWidth = 1024; // this is set to 100% in the html css for the map canvas
    double browserHeight = 8000; // this is used in the html map canvas; IMPORTANT: when the app freezes during zoom in, then the problem is probably the height; it works without problems with height of e. g. 360

    MyBrowser webBrowser;
    TextField latitudeTextField;
    TextField longitudeTextField;

    private ScrollPane snapshotScrollPane;

    @Override
    public void start(Stage stage) throws Exception {

        webBrowser = new MyBrowser(browserWidth, browserHeight);

        ScrollPane browserScrollPane = new ScrollPane(webBrowser);
        snapshotScrollPane = new ScrollPane();

        SplitPane splitPane = new SplitPane(browserScrollPane, snapshotScrollPane);

        BorderPane borderPane = new BorderPane();
        borderPane.setCenter(splitPane);
        borderPane.setRight(snapshotScrollPane);

        Scene scene = new Scene(borderPane, 1024, 768);
        stage.setScene(scene);
        stage.show();

    }

    private void createSnapshot() {

        SnapshotParameters parameters = new SnapshotParameters();
        parameters.setFill(Color.TRANSPARENT);

        // new image from clipped image
        WritableImage wim = null;
        wim = webBrowser.snapshot(parameters, wim);

        snapshotScrollPane.setContent(new ImageView(wim));
    }

    public class JavaBridge {

        public void onMapLoadingFinished() {

            System.out.println("[javascript] onMapLoadingFinished");

            createSnapshot();

        }

        public void log(String text) {
            System.out.println("[javascript] " + text);
        }

    }

    private class MyBrowser extends Pane {

        WebView webView;
        WebEngine webEngine;

        public MyBrowser(double width, double height) {

            webView = new WebView();
            webView.setPrefWidth(width);
            webView.setPrefHeight(height);

            webEngine = webView.getEngine();

            webEngine.getLoadWorker().stateProperty().addListener((ChangeListener<State>) (observable, oldState, newState) -> {

                System.out.println("[State] " + observable);

                if (newState == State.SCHEDULED) {

                    System.out.println("Webpage loaded");

                    // inject "java" object
                    JSObject window = (JSObject) webEngine.executeScript("window");
                    JavaBridge bridge = new JavaBridge();
                    window.setMember("java", bridge);

                    // console.log
                    webEngine.executeScript("console.log = function(message)\n" + "{\n" + "    java.log(message);\n" + "};");

                    // initialize variables

                    // canvas height
                    webEngine.executeScript("window.mapCanvasHeight = '" + browserHeight + "px'");

                    System.out.println("Latitude = " + lat + ", Longitude = " + lon);

                    webEngine.executeScript("window.lat = " + lat + ";" + "window.lon = " + lon + ";");

                }

                if (newState == State.SUCCEEDED) {
                    // createSnapshot();
                }
            });

            // logging other properties
            webEngine.getLoadWorker().exceptionProperty().addListener((ChangeListener<Throwable>) (ov, t, t1) -> System.out.println("[Exception] " + t1.getMessage()));
            webEngine.getLoadWorker().progressProperty().addListener((ChangeListener<Number>) (observable, oldState, newState) -> System.out.println( "[Progress] " + newState));
            webEngine.getLoadWorker().workDoneProperty().addListener((ChangeListener<Number>) (observable, oldState, newState) -> System.out.println( "[WorkDone] " + newState));
            webEngine.getLoadWorker().runningProperty().addListener((ChangeListener<Boolean>) (observable, oldState, newState) -> System.out.println( "[Running] " + newState));
            webEngine.getLoadWorker().messageProperty().addListener((ChangeListener<String>) (observable, oldState, newState) -> System.out.println( "[Message] " + newState));


            final URL urlGoogleMaps = getClass().getResource("demo.html");
            webEngine.load(urlGoogleMaps.toExternalForm());

            // TODO: how should that work? it doesn't do anything when we invoke an alert
            webEngine.setOnAlert(e -> System.out.println("Alert: " + e.toString()));

            getChildren().add(webView);

        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}

console output

[WorkDone] 0.0
[Progress] 0.0
[State] ReadOnlyObjectProperty [bean: javafx.scene.web.WebEngine$LoadWorker@6954b271, name: state, value: SCHEDULED]
Webpage loaded
Latitude = 38.897676, Longitude = -77.036483
[Running] true
[State] ReadOnlyObjectProperty [bean: javafx.scene.web.WebEngine$LoadWorker@6954b271, name: state, value: RUNNING]
[WorkDone] 50.0
[Progress] 0.5
[WorkDone] 66.66666666666667
[Progress] 0.6666666666666667
[WorkDone] 76.59016927083334
[Progress] 0.7659016927083334
[javascript] Loading map tiles
[WorkDone] 79.69424280262338
[Progress] 0.7969424280262337
[WorkDone] 82.91479192680356
[Progress] 0.8291479192680357
[WorkDone] 86.13534105098375
[Progress] 0.8613534105098376
[WorkDone] 87.9566062307981
[Progress] 0.879566062307981
[WorkDone] 89.554165026828
[Progress] 0.89554165026828
[WorkDone] 89.62836770069038
[Progress] 0.8962836770069037
[javascript] idle
[WorkDone] 89.70492380394815
[Progress] 0.8970492380394814
[WorkDone] 89.78964107398804
[Progress] 0.8978964107398804
[WorkDone] 89.85311355504936
[Progress] 0.8985311355504936
[WorkDone] 89.91528395017954
[Progress] 0.8991528395017955
[WorkDone] 89.9528416875862
[Progress] 0.899528416875862
[javascript] window.onload
[Message] Loading complete
[WorkDone] 100.0
[Progress] 1.0
[State] ReadOnlyObjectProperty [bean: javafx.scene.web.WebEngine$LoadWorker@6954b271, name: state, value: SUCCEEDED]
[Running] false
[javascript] tilesloaded
[javascript] onMapLoadingFinished
taking screenshot

Of course one could wait a second and take the screenshot then, but that's not a proper solution.

like image 587
Roland Avatar asked Jan 11 '16 06:01

Roland


1 Answers

The google maps events are tied very individually. The tilesloaded listener particularly fires even while the map may be panning. This can be seen by trying to stress the map in this listeners example here

Additionally, the idle listener gets fired several times during a pan. It may be best to place a timer with a set amount of time that would determine that the user has completely stopped panning/zooming and then take the screenshot. Here's a basic example:

google.maps.event.addListener(map, "idle", function () {
    var mapMoveTimer, tileLoadedListener;
    var moveDetect = google.maps.event.addListener(map, "bounds_changed", function () {
        clearTimeout(mapMoveTimer);
    });

    mapMoveTimer = setTimeout(function () {
        google.maps.event.removeListener(moveDetect);

        // here is your code
        tileLoadedListener = google.maps.event.addListenerOnce(map, 'tilesloaded', function(){
            console.log("tilesloaded");
            java.onMapLoadingFinished();

            // make sure to remove the listener so it isn't fired multiple times.
            google.maps.event.removeListener(tileLoadedListener);
        });
    });

    }, 1000);
});
like image 63
Tah Avatar answered Oct 24 '22 19:10

Tah