2

I've been trying to create a Java desktop app using Java, JavaScript and JavaFX and one part of the app should be able to display a location on the map using the location of the venue taken from the database (street + city, no postal code) and once I run the app it seems to show the location correctly for a second or two and then it completely disapears like it's shown in the images below. Before

After

I used Maps JavaScript API and Geocoding API. I'm trying to get the app to consistently show the location without it disappearing without it running into an issue. This is the method that I'm currently using to display the map:

public void showMap(Venue venue) {
  WebEngine webEngine   = mapView.getEngine();

  Location location     = venue.getLocation();
  String address        = location.toString();
  String encodedAddress = URLEncoder.encode(address, StandardCharsets.UTF_8);

  String mapHTML = "<html>" +
    "<head>" +
    "<script src=\"https://maps.googleapis.com/maps/api/js?key=API_KEY\"></script>" +
    "<script>" +
    "function initMap() {" +
    "  var geocoder = new google.maps.Geocoder();" +
    "  geocoder.geocode({'address': '" + encodedAddress + "'}, function(results, status) {" +
    "    if (status === 'OK') {" +
    "      var mapOptions = {" +
    "        zoom: 15," +
    "        center: results[0].geometry.location," +
    "        mapTypeId: google.maps.MapTypeId.ROADMAP" +
    "      };" +
    "      var map = new google.maps.Map(document.getElementById('map'), mapOptions);" +
    "      new google.maps.Marker({" +
    "        position: results[0].geometry.location," +
    "        map: map" +
    "      });" +
    "    } else {" +
    "      alert('Geocode was not successful for the following reason: ' + status);" +
    "    }" +
    "  });" +
    "}" +
    "</script>" +
    "</head>" +
    "<body onload='initMap()'>" +
    "<div id='map' style='width:100%;height:100%;'></div>" +
    "</body>" +
    "</html>";

  webEngine.loadContent(mapHTML);
}

I used WebView on JavaFX to add the map as well. Anybody knows what could I add to the code to fix this. Should I maybe add another class that would do the geocoding for the given location or is there something wrong with the displayed code. Anything helps

2
  • 4
    There've been numerous reports of Google Maps not working in JavaFX WebView. There's a Gluon maps project that can be used to display Open Street Maps directly (without WebView) in JavaFX, which may work better for your purposes.
    – James_D
    Commented Aug 28, 2024 at 17:05
  • 2
    See: JavaFX not embedding google maps html. The answer there mentions some old versions of JavaFX that were able to display Google Maps at that time (and might still be able to do so today).
    – jewelsea
    Commented Aug 28, 2024 at 20:08

2 Answers 2

3

There are several questions on this site describing Google Maps not working in JavaFX WebView. It seems the Google Maps API does some fairly sophisticated browser checking which causes it to fail running in that context.

Another solution is to use the Gluon Maps Project, which provides a "native" (i.e. not relying on WebView and HTML/Javascript embedding) JavaFX component displaying Open Street Maps.

Gluon maps requires specifying the latitude and longitude of the location to display, so you will need a separate geocoding service if you want to search addresses. Nominatim from Open Street Maps seems to work well enough.

Here's a Geocoding implementation based on Nominatim:

Geocoder.java

package org.jamesd.examples.maps;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Executor;
import java.util.function.Consumer;

public class Geocoder {
    public record LatLon(double lat, double lon) {}

    private final static String GEOCODE_URL = "https://nominatim.openstreetmap.org/search";

    public LatLon geocode(String address) throws URISyntaxException, IOException, InterruptedException {
        HttpClient client = HttpClient.newBuilder().build();
        HttpRequest request = buildRequest(address);
        HttpResponse.BodyHandler<String> handler = HttpResponse.BodyHandlers.ofString();
        HttpResponse<String> response = client.send(request, handler);
        return latLonFromResponse(response);
    }
    public void geocodeAsync(String address, Consumer<LatLon> onRetrieved, Executor exec) throws URISyntaxException {
        HttpClient client = HttpClient.newBuilder().build();
        HttpResponse.BodyHandler<String> handler = HttpResponse.BodyHandlers.ofString();
        client.sendAsync(buildRequest(address), handler)
                .thenApply(this::latLonFromResponse)
                .thenAcceptAsync(onRetrieved, exec);
    }
    private HttpRequest buildRequest(String address) throws URISyntaxException {
        URI uri = buildURI(address);
        return HttpRequest.newBuilder()
                .uri(uri)
                .GET()
                .build();
    }

    private LatLon latLonFromResponse(HttpResponse<String> response) {
        try {
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode root = objectMapper.readTree(response.body());
            JsonNode result = root.elements().next();
            double lat = result.at("/lat").asDouble();
            double lon = result.at("/lon").asDouble();
            return new LatLon(lat, lon);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
    

    private URI buildURI(String address) throws URISyntaxException {
        String encodedAddress = URLEncoder.encode(address, StandardCharsets.UTF_8);
        StringBuilder uri = new StringBuilder(GEOCODE_URL)
                .append("?q=").append(encodedAddress)
                .append("&format=json")
                .append("&addressdetails=1")
                .append("&limit=1");
        return new URI(uri.toString());
    }
}

And a simple demo app displaying the address you displayed in Google maps (or close to it), along with a location marker, using Gluon maps:

package org.jamesd.examples.maps;

import com.gluonhq.maps.MapLayer;
import com.gluonhq.maps.MapPoint;
import com.gluonhq.maps.MapView;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;

public class HelloApplication extends Application {



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

        MapView map = new MapView();
        map.setZoom(19);

        ObjectProperty<Geocoder.LatLon> latlon = new SimpleObjectProperty<>(new Geocoder.LatLon(0, 0));
        latlon.subscribe(ll -> map.setCenter(ll.lat(), ll.lon()));

        Geocoder geocoder = new Geocoder();
        geocoder.geocodeAsync(
                "Ribnjak 2, 10000, Zagreb, Croatia",
                latlon::set,
                Platform::runLater
        );


        MapLayer poiLayer = new MapLayer() {
            private Circle marker = new Circle(5, Color.RED);
            {
                getChildren().add(marker);
                latlon.subscribe(this::markDirty);
            }

            @Override
            protected void layoutLayer() {
                super.layoutLayer();
                Geocoder.LatLon ll = latlon.get();
                if (ll == null) {
                    marker.setVisible(false);
                } else {
                    MapPoint mapPoint = new MapPoint(latlon.get().lat(), latlon.get().lon());
                    Point2D point = getMapPoint(mapPoint.getLatitude(), mapPoint.getLongitude());
                    marker.setCenterX(point.getX());
                    marker.setCenterY(point.getY());
                    marker.setVisible(true);
                }
            }

        };
        map.addLayer(poiLayer);

        Scene scene = new Scene(map, 800, 500);
        stage.setScene(scene);
        stage.show();
    }

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

enter image description here

For completeness, the pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.jamesd.examples</groupId>
    <artifactId>maps</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>maps</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <javafx.version>22.0.1</javafx.version>
        <junit.version>5.8.2</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>22.0.1</version>
        </dependency>
        <dependency>
            <groupId>com.gluonhq</groupId>
            <artifactId>maps</artifactId>
            <version>2.0.0-ea+6</version>
            <exclusions>
                <exclusion>
                    <groupId>org.openjfx</groupId>
                    <artifactId>javafx-controls</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.openjfx</groupId>
                    <artifactId>javafx-graphics</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.openjfx</groupId>
                    <artifactId>javafx-base</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.1</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-engine</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.10.1</version>
                <configuration>
                    <source>22</source>
                    <target>22</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

and module-info.java:

module org.jamesd.examples.maps {
    requires javafx.controls;
    requires com.gluonhq.maps;
    requires java.net.http;
    requires com.fasterxml.jackson.core;
    requires com.fasterxml.jackson.databind;

    exports org.jamesd.examples.maps;
}
0

I had the same issue a while ago and my (maybe only temporary) solution was to add the api version 3.55 to the script call src="http://maps.google.com/maps/api/js?v=3.55&key=API_KEY&libraries=maps,geometry,visualization,marker" As everythings works when using mozilla I think it is a problem with the WebView ar the WebView Support. I am looking forward to a permanent solution, because I don't know how long this version will be available. Any advice is welcome...

1
  • 1
    It looks (to me) like you are asking a question in your answer. Since you have the Informed badge I assume that you know that SO is not a forum and that answers should be just that, i.e. they should just answer the question. If you have a question (even if it is similar to this question) then post a question. I feel that your answer is more suitable as a comment and you should not post comments as answers simply because you don't have the privilege.
    – Abra
    Commented Aug 30, 2024 at 6:55

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.