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:
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.
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);
});
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With