Skip to main content

Developing Cesium Applications in Java with Cesium-GWT

This is a guest post by Rich Kadel of Harmonia Holdings Group, LLC describing Cesium-GWT.
-jep

Maps are an important tool in the toolbox, and Cesium provides the best combination of cross platform compatibility, end-user accessibility (via web delivery), and powerful feature capabilities compared with the plug-in-based alternatives such as Google Earth or NASA WorldWind.

JavaScript has evolved into a powerful primary language in its own right, as demonstrated by Cesium, but for many of us, Java is still the preferred language choice for large, complex development efforts. There are always trade-offs between language choices—and so many to choose from—but putting aside the "religious" debates, Cesium developers now have a choice between using the JavaScript API directly or using the new Cesium-GWT API for Java-based Cesium development.

This article introduces Cesium-GWT, how to develop with the Cesium-GWT APIs, and a little about the patterns and practices used to create the GWT interface for Cesium—hopefully to encourage others to contribute as well.

GWT and JSNI

Firestorm

Google released its Google Web Toolkit way back in 2006 as a way to allow Java developers to create browser client-side user interfaces—in Java—optimized for the various browser configurations and performance differences that were prevalent at the time. For anyone that doesn't already know this, GWT includes a compiler that actually compiles Java code into minimized and optimized JavaScript; in fact it often produces multiple variants of JavaScript with subtle tweaks to maximize performance for each major browser. (For example, some JavaScript engines are faster at iterating through an array using one approach, while other browsers are significantly faster looping through arrays using a different approach.) Today, many JavaScript wrapper APIs embed some of these optimizations, but GWT actually does this at compile time, so only the code for the target browser has to be downloaded.

Not only did this make complex web application development more accessible to the plethora of Java developers, but a significant advantage of using Java was that existing tools could be used to do the development; and the benefits of Java development were extended to the web, such as: strong type checking, syntax-directed editing with automatic code completion and refactor automation, Java Generics, class packaging, and well-designed object-oriented design. (JavaScript aficionados may be quick to point out some of Java's or GWT's disadvantages as well, such as some degree of extra code verbosity, and a slightly slower develop-to-run cycle. All language choices have pros and cons and you have to pick the one that's best for you.)

Nevertheless, JavaScript is still the "language of the web," and now, in a browser-plug-in free world, there are numerous web-based components out there with some form of JavaScript API—Cesium being one of them. So how do you integrate with a JavaScript API from GWT Java? GWT provides a simple way to embed JavaScript code inside a Java program with what is called JavaScript Native Interface (JSNI). JSNI leverages Java's "native" keyword and a special comment syntax for hiding the JavaScript from a Java compiler or Integrated Development Environment (IDE), so the GWT compiler can scan for and include your JavaScript methods in the GWT JavaScript output. These methods can be called from Java, and can even call Java (GWT) methods when required. A sample JSNI method call looks something like the following:

public static native PolygonGeometry fromPositions(JsArray<Cartesian3> positions, Options options) /*-{
      options.positions = positions
      return Cesium.PolygonGeometry.fromPositions(options)
}-*/;

This brief sample demonstrates several important aspects of JSNI:

  • The method is declared as native, and the JavaScript portion of the code is encapsulated in /*-{ and }-*/;
  • JSNI methods can be static or instance methods. A static method is shown here, constructing a Cesium PolygonGeometry object. Not shown in this example, the Java PolygonGeometry class can have JSNI instance methods that invoke JavaScript on the Cesium PolygonGeometry, accessing the JavaScript instance object via the JavaScript keyword this.
  • JSNI method arguments and return values can be of type Object, String, boolean, or number primitive (e.g., double, int). However, off-the-shelf Java Objects are opaque to JSNI Java. You can have JavaScript reference to them, and pass them in or out of JSNI methods, but you should not rely on their internal JavaScript representation. And you cannot serialize them to JSON, because you will not get a valid Java Object back after deserialization.
  • GWT does include support for what they call JavaScript overlay types. This is a great way to work across both Java and JavaScript, and is heavily used in Cesium-GWT. The example shows three such classes: PolygonGeometry, Cartesian, and the GWT built-in type JsArray. JsArray is a JavaScript Array on the JavaScript side, so in the context of this example, you could write positions[0] in JavaScript to get the Cesium Cartesian object at that index (if the array contains at least one object). You can't construct a JavaScript overlay object through a Java Constructor—and you wouldn't want to. These really are JavaScript objects. But you can construct them in JavaScript, and return them through a JSNI method, as shown here.

Hello World in Cesium-GWT

Hello Cesium

Setting up your first GWT application is not necessarily as trivial as adding JavaScript to an HTML page, but there are plenty of online resources to get you started, such as the GWT Project's own documentation for creating a GWT application in Eclipse. You can also start from Cesium-GWT's sample project, included in the release.

In short, you will need to set up a GWT module file (in XML) to define your project and its Java "EntryPoint" (the Java class to invoke at web page initialization), and a basic GWT-enabled HTML file (which can be as short as about five lines). Once setup, here is a very simple Hello, World example for Cesium-GWT:

public class HelloCesium extends CesiumWidgetPanel implements EntryPoint {

      public HelloCesium() {
        super(new CesiumConfiguration().setCesiumPath(
          "Cesium-1.2/Build/CesiumUnminified"));
      }

      public void onModuleLoad() {
        RootPanel.get().add(new HelloCesium());
      }

      public CesiumWidget createCesiumWidget(Element element) {
        return CesiumWidget.create(element);
      }
}

Our primary GWT Widget is a CesiumWidgetPanel, which essentially becomes a <div> block to be managed by GWT in the Document Object Model (DOM) of the web page. CesiumWidgetPanel extends GWT's SimplePanel and adds logic to automatically initialize Cesium and load its required resources.

Note that GWT starts by invoking the EntryPoint's constructor, HelloCesium(), then GWT automatically invokes onModuleLoad(). At this point, it is safe to add your GWT component to the RootPanel (consuming the entire web page, by default).

When Cesium is ready, an abstract method that you define—createCesiumWidget(Element)—is invoked so you can create your CesiumWidget. This gives you the opportunity to save a handle to the CesiumWidget as a field in your Java class, for later use, and to perform other initialization actions with the CesiumWidget, prior to returning from the method.

The HelloCesium example and several other samples can be reviewed in the Cesium-GWT GitHub repository, along with a live demo of these samples.

A More Complex Example

CZML

As a Cesium JavaScript user, you may already know that CesiumWidget is the most basic implementation of a Cesium display object, and that Cesium also includes a more feature-rich display object called Viewer. Cesium-GWT also supports Viewer, which can be created by extending the ViewerPanel, instead of the CesiumWidgetPanel. This excerpt is from the bundled Cesium-GWT sample class CZMLDemo:

ViewerPanel cesiumPanel = new ViewerPanel(configuration) {
    public Viewer createViewer(Element element) {

    viewer = Viewer.create(element);
    viewerEntityMixin = ViewerEntityMixin.create(viewer);

    return viewer;
    }
};

This code segment creates the Viewer, adds a ViewerEntityMixin (providing access to methods from Cesium's viewerEntityMixin), and assigns these two objects to fields in the CZMLDemo class. We use these fields in the click-event handlers for GWT Button widgets, as in the following example for the two Satellite buttons:

addButton("Satellites").addClickHandler(new ClickHandler() {
  public void onClick(ClickEvent event) {
    final CzmlDataSource czmlDataSource = CzmlDataSource.create();
    czmlDataSource.loadUrl(SAMPLE_DATA_DIR + "simple.czml");
    viewer.getDataSources().add(czmlDataSource);
  }
});

addButton("Tracked Satellite").addClickHandler(new ClickHandler() {
  public void onClick(ClickEvent event) {
    final CzmlDataSource czmlDataSource = CzmlDataSource.create();
    czmlDataSource.loadUrl(SAMPLE_DATA_DIR + "simple.czml");
    viewer.getDataSources().add(czmlDataSource);
    czmlDataSource.onLoadingEvent().addEventListener(new NoArgsFunction() {
      public void callback() {
        if (!czmlDataSource.isLoading()) {
          EntityCollection entityCollection = czmlDataSource.getEntities();
          JsArray<Entity> entities = entityCollection.getEntities();
          int len = entities.length();
          for (int i = 0; i < len; i++) {
            Entity entity = entities.get(i);
            GWT.log("entity "+i+": "+entity.getId());
          }
          viewerEntityMixin.setTrackedEntity(
            czmlDataSource.getEntities().getById("Satellite/Geoeye1/Sensor/Sensor"));
        }
      }
    });
  }
});

In the click handlers for both buttons, the onClick() method starts by loading CZML data for satellites orbiting over time. The first simply loads the data and displays the orbiting satellites and their orbit tracks. The second extends the first by leveraging the viewerEntityMixin to connect the camera to (and follow) one of the satellites.

This example code also shows the Cesium-GWT implementation of Cesium's Event class. In JavaScript, we would simply pass in a function reference. GWT's support for native JavaScript function references is fairly weak, out of the box, so we created a mechanism for creating function references in Java such that they can be passed to JavaScript callback handlers. As of Cesium-GWT 0.2.0, this capability was greatly expanded, and extracted into its own GitHub project, the JsFunction-GWT library. The simplest example of this library is the NoArgsFunction class, used to declare a simple callback in response to the CzmlDataSource loading event. We need to wait for the CZML file to be loaded asynchronously before we can start tracking one of the entities built from the CZML file contents.

Besides no-arg function references, JsFunction-GWT also supports single-argument functions—modeling the standard pattern for event listeners (EventListener) and variable argument (VarArgFunction) functions (zero or more arguments). This pattern requires the Java anonymous class implementations for now. Rumor has it that GWT will be supporting Java 8 Lambda Expressions soon, which will make these types of calls less verbose and even more like their JavaScript equivalents.

Extending Cesium-GWT

glTF

With the exception of the "Tracked Satellite" button, the rest of the example closely matches the original JavaScript version of this demo, which can be compared by looking at CZML.html.

As you peruse the samples, you should notice that the Java code in the sandcastle package largely mirrors the equivalent code in each of the Cesium Sandcastle demos implemented thus far. A major tenet of the Cesium-GWT API design is to create an API that follows standard Java practices, but is still so closely aligned to the JavaScript APIs that a Cesium JavaScript application can be translated into a Cesium-GWT application in place, with very few (mostly just syntax) changes.

As a new contributor, please take the time to review the existing API implementations so you can follow the same general practices, such as:

  • How to implement Enums and Constants
  • Naming conventions for Event fields (implementing "onSomeEvent()" methods in Java)
  • Proper implementation of JsFunction callbacks (with a Java method wrapper to perform the conversion of the Java anonymous class into the JsFunction reference)
  • A strong preference for Java Generics when feasible
  • Using the static create() method with required parameters (if any) and a separate inner class Options for optional parameters; and
  • Returning the this object from set methods (when appropriate) to support chained method calls (a minor coding convenience that also helps mimic typical Cesium JavaScript implementations).

All of the Cesium-GWT APIs developed thus far were created by hand. As desirable as it is to implement the entire Cesium API in GWT, there is not a dedicated effort to do this. Cesium-GWT APIs are implemented opportunistically, either as a capability aligns with a project requirement, or as free time permits. The Cesium-GWT APIs are numerous, and there is still much to do. Also, be on the lookout for typos or other possible bugs, especially in the JSNI JavaScript code. Due to the loose syntax of JavaScript, it's far too easy to miss a return statement, or use the wrong parameter name.

Contributions are strongly encouraged, so if you are interested in contributing, please fork the GitHub project and submit a pull request when your additions are tested and commited to your fork.

Recommendations for Developing with Super Dev Mode

After writing your class, you would continue to follow the GWT processes—as needed—for compiling and deploying your GWT app, and then invoke this app by loading your GWT app's web page. Again, there are plenty of resources on the web describing the process, so I won't repeat them here; but I will make a couple of recommendations, for development:

  1. Learn about and use GWT's Super Dev Mode. GWT supports two ways to perform on-the-fly compiles and line-by-line debugging: Dev Mode and Super Dev Mode. Dev Mode requires browser support for a special two-way interaction between a Dev Mode runtime (typically running in your IDE, such as Eclipse) and the browser's execution of GWT-enabled JavaScript. This does allow you to debug a GWT app from the browser's debugger, just like a regular Java app, but it has several drawbacks:
    undefinedundefinedundefinedundefined
    Super Dev Mode actually re-loads new versions of your code into the browser as new JavaScript, as you develop, allowing you to perform all of your debugging in the browser—both the Java and the JavaScript.
  2. Consider using Chrome for most of your GWT development. Super Dev Mode uses a defacto standard for browsers known as "JavaScript source maps" to display the original source (in GWT's case, the Java code) in the browser's debugger, allowing you to set breakpoints (or break on exceptions) and step through interleaved Java and JavaScript. This is invaluable when developing a GWT app with a dependency on a JavaScript API, such as Cesium; however... source maps are not supported by all browsers. They are supported by Chrome and Firefox at the present time, but for a large GWT application, Firefox source maps can be slow. As a result, I now use Chrome for most of my development. But I can still do Super Dev Mode debugging with any other browser that supports JavaScriptDebugging. The Java GWT code typically appears a little obfuscated, but is readable, and file names and line numbers are included (if enabled in your GWT module) so you can find your way through a bug when source maps are not available.