Use JavaFX to Add Google Maps to your NetBeans RCP Application

By utilizing the GMapsFX library which provides a JavaFX API for Google Maps, it is relatively straightforward to add a map component to a desktop application built on the NetBeans Rich Client Platform (RCP).

Assume we would like to add a map to a new TopComponent in the Editor mode in the frame, or for those who are not as familiar with NetBeans jargon, we would like to add a Map to a new tab within the main portion of the application frame.

First create a new NetBeans Module project which will utilize the GMapsFX component.

enter image description here

Enter project details
enter image description here

Use RELEASE80 of the NetBeans Platform. Please also note that the application will require Java 8 in order to run.
enter image description here

Once the project has been created, add the GMapsFX library as a dependency to the project.
The binaries are available on Maven Central and the source is available at GitHub: https://github.com/rterp/GMapsFX
enter image description here

Once the dependency has been added, right click on the project in NetBeans and select “New” -> “Other”. Select the “Module Development” category and then select “Window” file type. This will create a new TopComponent class which will be used to host the map component.

 

enter image description here

Enter a prefix for the new TopComponent class.
enter image description here

Once this is finished a new GMapTopCompoent class will be created. The GoogleMapView component can then be added to this class in order to display the map.

Below is the code for the entire TopCompoment class including code in which I added the map component as well as a couple of map markers and an info window, all without having to interact with the underlying Google Maps JavaScript API.

package com.lynden.gmapsfx.module;

import com.lynden.gmapsfx.GoogleMapView;
import com.lynden.gmapsfx.MapComponentInitializedListener;
import com.lynden.gmapsfx.javascript.object.Animation;
import com.lynden.gmapsfx.javascript.object.GoogleMap;
import com.lynden.gmapsfx.javascript.object.InfoWindow;
import com.lynden.gmapsfx.javascript.object.InfoWindowOptions;
import com.lynden.gmapsfx.javascript.object.LatLong;
import com.lynden.gmapsfx.javascript.object.MapOptions;
import com.lynden.gmapsfx.javascript.object.MapTypeIdEnum;
import com.lynden.gmapsfx.javascript.object.Marker;
import com.lynden.gmapsfx.javascript.object.MarkerOptions;
import java.awt.BorderLayout;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
import org.netbeans.api.settings.ConvertAsProperties;
import org.openide.awt.ActionID;
import org.openide.awt.ActionReference;
import org.openide.windows.TopComponent;
import org.openide.util.NbBundle.Messages;

/**
 * Top component which displays something.
 */
@ConvertAsProperties(
        dtd = "-//com.lynden.gmapsfx.module//GMap//EN",
        autostore = false
)
@TopComponent.Description(
        preferredID = "GMapTopComponent",
        //iconBase="SET/PATH/TO/ICON/HERE", 
        persistenceType = TopComponent.PERSISTENCE_ALWAYS
)
@TopComponent.Registration(mode = "editor", openAtStartup = true)
@ActionID(category = "Window", id = "com.lynden.gmapsfx.module.GMapTopComponent")
@ActionReference(path = "Menu/Window" /*, position = 333 */)
@TopComponent.OpenActionRegistration(
        displayName = "#CTL_GMapAction",
        preferredID = "GMapTopComponent"
)
@Messages({
    "CTL_GMapAction=GMap",
    "CTL_GMapTopComponent=GMap Window",
    "HINT_GMapTopComponent=This is a GMap window"
})


public final class GMapTopComponent extends TopComponent implements MapComponentInitializedListener {

    protected GoogleMapView mapComponent;
protected GoogleMap map;

public GMapTopComponent() {
    initComponents();
    setName(Bundle.CTL_GMapTopComponent());
    setToolTipText(Bundle.HINT_GMapTopComponent());
    setLayout(new BorderLayout());
    JFXPanel panel = new JFXPanel();
    Platform.setImplicitExit(false);

    Platform.runLater(() -> {
        mapComponent = new GoogleMapView();
        mapComponent.addMapInializedListener(this);
        BorderPane root = new BorderPane(mapComponent);
        Scene scene = new Scene(root);
        panel.setScene(scene);
    });

        add(panel, BorderLayout.CENTER);        

    }


  @Override
    public void mapInitialized() {
        //Once the map has been loaded by the Webview, initialize the map details.
        LatLong center = new LatLong(47.606189, -122.335842);



MapOptions options = new MapOptions();
        options.center(center)
                .mapMarker(true)
                .zoom(9)
                .overviewMapControl(false)
                .panControl(false)
                .rotateControl(false)
                .scaleControl(false)
                .streetViewControl(false)
                .zoomControl(false)
                .mapType(MapTypeIdEnum.ROADMAP);

        map = mapComponent.createMap(options);

        //Add a couple of markers to the map.
        MarkerOptions markerOptions = new MarkerOptions();
        LatLong markerLatLong = new LatLong(47.606189, -122.335842);
        markerOptions.position(markerLatLong)
                .title("My new Marker")
                .animation(Animation.DROP)
                .visible(true);

        Marker myMarker = new Marker(markerOptions);

        MarkerOptions markerOptions2 = new MarkerOptions();
        LatLong markerLatLong2 = new LatLong(47.906189, -122.335842);
        markerOptions2.position(markerLatLong2)
                .title("My new Marker")
                .visible(true);

        Marker myMarker2 = new Marker(markerOptions2);

        map.addMarker(myMarker);
        map.addMarker(myMarker2);

        //Add an info window to the Map.
        InfoWindowOptions infoOptions = new InfoWindowOptions();
        infoOptions.content("<h2>Center of the Universe</h2>")
                .position(center);

        InfoWindow window = new InfoWindow(infoOptions);
        window.open(map, myMarker);

    }    

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">                          
    private void initComponents() {

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 400, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 300, Short.MAX_VALUE)
        );
    }// </editor-fold>                        

    // Variables declaration - do not modify                     
    // End of variables declaration                   
    @Override
    public void componentOpened() {
        // TODO add custom code on component opening
    }

    @Override
    public void componentClosed() {
        // TODO add custom code on component closing
    }

    void writeProperties(java.util.Properties p) {
        // better to version settings since initial version as advocated at
        // http://wiki.apidesign.org/wiki/PropertyFiles
        p.setProperty("version", "1.0");
        // TODO store your settings
    }

    void readProperties(java.util.Properties p) {
        String version = p.getProperty("version");
        // TODO read your settings according to their version


       }
    }

What is important to note is that no map related classes can be instantiated until the GoogleMapView has been initialized. This is because the underlying JavaScript peer objects can’t be created until the JavaScript runtime has been initialized. The TopComponent is added as a MapComponentInitializedListener so that it can determine when it is safe to begin manipulating the map and its associated objects.

This module is now ready to be included as a dependency in a NetBeans application in development, or be added as a plug-in to an existing Netbeans RCP application at runtime, or even added to the IDE itself.

One word of caution: The underlying JavaFX WebView and Javascript runtime which the GoogleMapView component is making use of to render the map appears to be a memory hog. I have had to play with memory settings in order to avoid OutOfMemoryErrors, so something to keep in mind as you play with this.

Below is the final product of running the GMapsFX plug-in within a NetBeans RCP application.

enter image description here

The GMapsFX project is open source with the project home at GitHub as mentioned above and can be accessed at:
http://rterp.github.io/GMapsFX/

twitter: @RobTerp

25 thoughts on “Use JavaFX to Add Google Maps to your NetBeans RCP Application

  1. I am getting javascript error. Map is show but markers are not shown, please help.

    I have included version 1.1.1 src in my project.
    Regards
    asutosh
    ========
    GoogleMapView.mapResized: triggering resize event
    netscape.javascript.JSException: RangeError: Maximum call stack size exceeded.
    at com.sun.webkit.dom.JSObject.fwkMakeException(JSObject.java:128)
    at com.sun.webkit.WebPage.twkExecuteScript(Native Method)
    at com.sun.webkit.WebPage.executeScript(WebPage.java:1410)
    at javafx.scene.web.WebEngine.executeScript(WebEngine.java:934)
    at com.lynden.gmapsfx.javascript.JavaFxWebEngine.executeScript(JavaFxWebEngine.java:39)
    at com.lynden.gmapsfx.GoogleMapView.mapResized(GoogleMapView.java:101)
    at com.lynden.gmapsfx.GoogleMapView.lambda$createMap$5(GoogleMapView.java:127)
    at com.lynden.gmapsfx.GoogleMapView$$Lambda$7/5052518.handle(Unknown Source)
    at com.lynden.gmapsfx.javascript.event.EventHandlers.handleStateEvent(EventHandlers.java:107)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at sun.reflect.misc.Trampoline.invoke(MethodUtil.java:71)
    at sun.reflect.GeneratedMethodAccessor9.invoke(Unknown Source)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at sun.reflect.misc.MethodUtil.invoke(MethodUtil.java:275)
    at com.sun.webkit.Utilities$1.run(Utilities.java:75)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.webkit.Utilities.fwkInvokeWithContext(Utilities.java:72)
    at com.sun.webkit.Timer.twkFireTimerEvent(Native Method)
    at com.sun.webkit.Timer.fireTimerEvent(Timer.java:66)
    at com.sun.webkit.Timer.notifyTick(Timer.java:47)
    at javafx.scene.web.WebEngine$PulseTimer$2.pulse(WebEngine.java:1154)
    at com.sun.javafx.tk.Toolkit$3.run(Toolkit.java:322)
    at com.sun.javafx.tk.Toolkit$3.run(Toolkit.java:320)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:320)
    at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:349)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:479)
    at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:460)
    at com.sun.javafx.tk.quantum.QuantumToolkit$13.run(QuantumToolkit.java:327)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.access$300(WinApplication.java:39)
    at com.sun.glass.ui.win.WinApplication$4$1.run(WinApplication.java:112)
    at java.lang.Thread.run(Thread.java:744)

  2. Pingback: JavaFX links of the week, June 9 // JavaFX News, Demos and Insight // FX Experience

  3. Pingback: Java desktop links of the week, June 9 « Jonathan Giles

  4. The only available version for Java is Dev-SNAPSHOT (in NB 8.1), and then the module cannot find its libraries.

    Could not transfer metadata org.netbeans.api:org-netbeans-api-annotations-common:dev-SNAPSHOT/maven-metadata.xml from/to netbeans-snapshot (http://bits.netbeans.org/nexus/content/repositories/snapshots): Failed to transfer file: http://bits.netbeans.org/nexus/content/repositories/snapshots/org/netbeans/api/org-netbeans-api-annotations-common/dev-SNAPSHOT/maven-metadata.xml. Return code is: 502 , ReasonPhrase:Bad Gateway.
    Failure to transfer org.netbeans.api:org-netbeans-api-annotations-common:dev-SNAPSHOT/maven-metadata.xml from http://bits.netbeans.org/nexus/content/repositories/snapshots was cached in the local repository, resolution will not be reattempted until the update interval of netbeans-snapshot has elapsed or updates are forced. Original error: Could not transfer metadata org.netbeans.api:org-netbeans-api-annotations-common:dev-SNAPSHOT/maven-metadata.xml from/to netbeans-snapshot (http://bits.netbeans.org/nexus/content/repositories/snapshots): Failed to transfer file: http://bits.netbeans.org/nexus/content/repositories/snapshots/org/netbeans/api/org-netbeans-api-annotations-common/dev-SNAPSHOT/maven-metadata.xml. Return code is: 502 , ReasonPhrase:Bad Gateway.
    Downloading: http://bits.netbeans.org/nexus/content/repositories/snapshots/org/netbeans/api/org-netbeans-api-annotations-common/dev-SNAPSHOT/org-netbeans-api-annotations-common-dev-SNAPSHOT.pom

  5. Hey, thanks for the help. I’m graduating in Computer Science, and my final project is supposed to use Google Maps API with JavaFx… Ayway, i just did everything that you said, but this part of the code displays error:

    Platform.runLater(() -> {
    mapComponent = new GoogleMapView();
    mapComponent.addMapInializedListener(this);
    BorderPane root = new BorderPane(mapComponent);
    Scene scene = new Scene(root);
    panel.setScene(scene);
    });
    The error I got is this one: illegal start of expression

    Do you have any clue of what it can be?

    • Platform.runLater(() ->
      {mapComponent = new GoogleMapView();
      mapComponent.addMapInializedListener(this);
      BorderPane root = new BorderPane(mapComponent);
      Scene scene = new Scene(root);
      panel.setScene(scene);
      });
      The error I got is this one: lambda expression not expected here

      lambda expressions are not supported in -source 1.6 and I am already using the 1.8jdk.

      Would appreciate if you responded as soon as you can since I have to finish this project today

      • What IDE are you using? It sounds like your project has been configured to use version 1.6 source, but GMapsFX requires 1.8.

  6. I’d also like instructions on how to make this plot appear in a jPanel or a JFrame. I did get GMapsFX running as an application.

  7. I have never used a pom file before. I replaced the pom.xml file in my project with the above pom.xml, but still get
    The POM for org.netbeans.api:org-netbeans-api-annotations-common:jar:RELEASE81 is missing, no dependency information available
    The POM for org.netbeans.api:org-openide-windows:jar:RELEASE81 is missing, no dependency information available
    The POM for org.netbeans.api:org-openide-util:jar:RELEASE81 is missing, no dependency information available
    The POM for org.netbeans.api:org-openide-util-ui:jar:RELEASE81 is missing, no dependency information available
    The POM for org.netbeans.api:org-openide-util-lookup:jar:RELEASE81 is missing, no dependency information available
    The POM for org.netbeans.api:org-openide-awt:jar:RELEASE81 is missing, no dependency information available
    The POM for org.netbeans.api:org-netbeans-modules-settings:jar:RELEASE81 is missing, no dependency information available

    Do I need to do something else?

    Thanks for the help

  8. hey… i have a problem when load that infoWindow. My application get stuck if i load that infoWindow. but, if i don’t add that, my application is okay. I use same procedure like what you give at infoWindow example..
    may u would like to solve my problem..
    thanks..

Leave a Reply to ZANELECancel reply