NASA WorldWind is an open-source virtual globe and geospatial SDK that enables developers to create interactive 3D geographic applications. This comprehensive guide covers integration, customization, and advanced features.
Overview and Capabilities
What is NASA WorldWind?
- Open-source virtual globe API developed by NASA
- High-performance 3D visualization of geographic data
- Supports multiple data sources and formats
- Cross-platform Java implementation
Key Features:
- 3D Terrain Visualization with elevation data
- Multiple Data Layers (imagery, vectors, placenames)
- Geospatial Analysis tools
- Custom Object Placement (points, lines, polygons)
- Animation and Interaction capabilities
- Extensible architecture
Dependencies and Setup
Maven Dependencies
<properties>
<worldwind.version>2.2.0</worldwind.version>
</properties>
<dependencies>
<!-- NASA WorldWind Core -->
<dependency>
<groupId>gov.nasa</groupId>
<artifactId>worldwind</artifactId>
<version>${worldwind.version}</version>
</dependency>
<dependency>
<groupId>gov.nasa</groupId>
<artifactId>worldwindx</artifactId>
<version>${worldwind.version}</version>
</dependency>
<!-- UI Components -->
<dependency>
<groupId>org.jogamp.gluegen</groupId>
<artifactId>gluegen-rt-main</artifactId>
<version>2.4.0</version>
</dependency>
<dependency>
<groupId>org.jogamp.jogl</groupId>
<artifactId>jogl-all-main</artifactId>
<version>2.4.0</version>
</dependency>
<!-- Utilities -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
System Requirements Setup
public class WorldWindSetup {
static {
// Configure JOGL native libraries
System.setProperty("java.library.path", "/path/to/native/libraries");
}
public static void setupNativeLibraries() {
// This ensures JOGL native libraries are loaded correctly
try {
Class<?> clazz = Class.forName("org.jogamp.gluegen.runtime.ProcAddressHelper");
java.lang.reflect.Method method = clazz.getDeclaredMethod("resetNativeLibs");
method.setAccessible(true);
method.invoke(null);
} catch (Exception e) {
System.err.println("Failed to reset native libraries: " + e.getMessage());
}
}
}
Basic WorldWind Application
1. Simple WorldWind Viewer
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.awt.WorldWindowGLCanvas;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.util.*;
import javax.swing.*;
import java.awt.*;
public class BasicWorldWindApp extends JFrame {
private WorldWindowGLCanvas wwd;
public BasicWorldWindApp() {
initializeWindow();
createWorldWindComponent();
setupLayers();
setupUI();
}
private void initializeWindow() {
setTitle("NASA WorldWind Java Application");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1200, 800);
setLocationRelativeTo(null);
}
private void createWorldWindComponent() {
wwd = new WorldWindowGLCanvas();
// Configure the model
Model m = (Model) WorldWind.createConfigurationComponent(AVKey.MODEL_CLASS_NAME);
wwd.setModel(m);
// Add basic controllers
wwd.addSelectListener(new BasicSelectListener());
}
private void setupLayers() {
LayerList layers = wwd.getModel().getLayers();
// Add basic layers
layers.add(new StarsLayer());
layers.add(new CompassLayer());
layers.add(new ViewControlsLayer());
layers.add(new ScalebarLayer());
// Add imagery layers
layers.add(new BMNGOneImageLayer()); // Blue Marble
layers.add(new LandsatI3WMSLayer()); // Landsat imagery
// Add political boundaries
layers.add(new CountryBoundariesLayer());
// Add placenames
layers.add(new GeonamesLayer());
}
private void setupUI() {
setLayout(new BorderLayout());
add(wwd, BorderLayout.CENTER);
// Add layer panel for control
JPanel controlPanel = createControlPanel();
add(controlPanel, BorderLayout.WEST);
}
private JPanel createControlPanel() {
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
JLabel titleLabel = new JLabel("WorldWind Controls");
titleLabel.setFont(new Font("Arial", Font.BOLD, 14));
panel.add(titleLabel);
panel.add(Box.createVerticalStrut(10));
// Layer visibility controls
panel.add(createLayerControls());
panel.add(Box.createVerticalStrut(10));
// Navigation controls
panel.add(createNavigationControls());
return panel;
}
private JPanel createLayerControls() {
JPanel panel = new JPanel(new GridLayout(0, 1));
panel.setBorder(BorderFactory.createTitledBorder("Layers"));
LayerList layers = wwd.getModel().getLayers();
for (Layer layer : layers) {
JCheckBox checkBox = new JCheckBox(layer.getName(), layer.isEnabled());
checkBox.addActionListener(e -> {
layer.setEnabled(checkBox.isSelected());
wwd.redraw();
});
panel.add(checkBox);
}
return panel;
}
private JPanel createNavigationControls() {
JPanel panel = new JPanel(new GridLayout(0, 1));
panel.setBorder(BorderFactory.createTitledBorder("Navigation"));
JButton resetView = new JButton("Reset View");
resetView.addActionListener(e -> resetViewToDefault());
panel.add(resetView);
JButton goToLocation = new JButton("Go to New York");
goToLocation.addActionListener(e -> goToLocation(40.7128, -74.0060, 100000));
panel.add(goToLocation);
JButton goToParis = new JButton("Go to Paris");
goToParis.addActionListener(e -> goToLocation(48.8566, 2.3522, 50000));
panel.add(goToParis);
return panel;
}
private void resetViewToDefault() {
wwd.getView().goTo(new Position(Position.fromDegrees(0, 0), 10000000));
}
private void goToLocation(double lat, double lon, double elevation) {
wwd.getView().goTo(new Position(Position.fromDegrees(lat, lon), elevation));
}
public static void main(String[] args) {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeel());
} catch (Exception e) {
e.printStackTrace();
}
SwingUtilities.invokeLater(() -> {
new BasicWorldWindApp().setVisible(true);
});
}
}
2. Advanced WorldWind Application with Custom Layers
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwindx.examples.ApplicationTemplate;
import java.awt.*;
import java.util.*;
public class AdvancedWorldWindApp extends ApplicationTemplate.AppFrame {
private RenderableLayer customLayer;
private ArrayList<PointPlacemark> placemarks;
public AdvancedWorldWindApp() {
super(true, true, false);
this.customLayer = new RenderableLayer();
this.customLayer.setName("Custom Features");
this.placemarks = new ArrayList<>();
setupCustomLayers();
setupAdvancedControls();
addSampleData();
}
private void setupCustomLayers() {
// Add custom layer to the model
insertBeforeCompass(getWwd(), customLayer);
// Create and add additional custom layers
RenderableLayer vectorLayer = createVectorLayer();
insertBeforeCompass(getWwd(), vectorLayer);
RenderableLayer polygonLayer = createPolygonLayer();
insertBeforeCompass(getWwd(), polygonLayer);
}
private RenderableLayer createVectorLayer() {
RenderableLayer layer = new RenderableLayer();
layer.setName("Vector Data");
// Create sample paths
ArrayList<Position> pathPositions = new ArrayList<>();
pathPositions.add(Position.fromDegrees(40.0, -100.0, 0));
pathPositions.add(Position.fromDegrees(41.0, -90.0, 0));
pathPositions.add(Position.fromDegrees(42.0, -80.0, 0));
Path path = new Path(pathPositions);
path.setAttributes(getPathAttributes());
layer.addRenderable(path);
return layer;
}
private RenderableLayer createPolygonLayer() {
RenderableLayer layer = new RenderableLayer();
layer.setName("Polygon Data");
// Create a sample polygon
ArrayList<Position> positions = new ArrayList<>();
positions.add(Position.fromDegrees(35.0, -120.0, 0));
positions.add(Position.fromDegrees(35.0, -110.0, 0));
positions.add(Position.fromDegrees(45.0, -110.0, 0));
positions.add(Position.fromDegrees(45.0, -120.0, 0));
positions.add(Position.fromDegrees(35.0, -120.0, 0));
Polygon polygon = new Polygon(positions);
polygon.setAttributes(getPolygonAttributes());
layer.addRenderable(polygon);
return layer;
}
private ShapeAttributes getPathAttributes() {
ShapeAttributes attrs = new BasicShapeAttributes();
attrs.setOutlineMaterial(Material.RED);
attrs.setOutlineWidth(3.0);
attrs.setDrawOutline(true);
attrs.setDrawInterior(false);
return attrs;
}
private ShapeAttributes getPolygonAttributes() {
ShapeAttributes attrs = new BasicShapeAttributes();
attrs.setInteriorMaterial(new Material(Color.BLUE));
attrs.setOutlineMaterial(Material.BLACK);
attrs.setOutlineWidth(2.0);
attrs.setDrawOutline(true);
attrs.setDrawInterior(true);
attrs.setInteriorOpacity(0.5);
return attrs;
}
private void addSampleData() {
// Add sample placemarks for major cities
addPlacemark("New York", 40.7128, -74.0060);
addPlacemark("London", 51.5074, -0.1278);
addPlacemark("Tokyo", 35.6762, 139.6503);
addPlacemark("Sydney", -33.8688, 151.2093);
addPlacemark("Rio de Janeiro", -22.9068, -43.1729);
// Add a sample surface shape
addSurfaceCircle(Position.fromDegrees(34.0522, -118.2437), 100000); // Los Angeles
}
private void addPlacemark(String name, double lat, double lon) {
PointPlacemark placemark = new PointPlacemark(Position.fromDegrees(lat, lon, 0));
placemark.setLabelText(name);
placemark.setValue(AVKey.DISPLAY_NAME, name);
placemark.setLineEnabled(false);
placemark.setScale(1.2);
// Customize appearance based on location
if (name.equals("New York")) {
placemark.setLabelColor("FF0000"); // Red
} else if (name.equals("London")) {
placemark.setLabelColor("0000FF"); // Blue
}
customLayer.addRenderable(placemark);
placemarks.add(placemark);
}
private void addSurfaceCircle(Position center, double radius) {
SurfaceCircle circle = new SurfaceCircle(
new BasicShapeAttributes(),
center,
radius
);
ShapeAttributes attrs = new BasicShapeAttributes();
attrs.setInteriorMaterial(Material.GREEN);
attrs.setOutlineMaterial(Material.BLACK);
attrs.setInteriorOpacity(0.3);
attrs.setOutlineWidth(2);
circle.setAttributes(attrs);
circle.setValue(AVKey.DISPLAY_NAME, "Area of Interest");
customLayer.addRenderable(circle);
}
private void setupAdvancedControls() {
JPanel advancedPanel = new JPanel(new GridLayout(0, 1));
advancedPanel.setBorder(BorderFactory.createTitledBorder("Advanced Controls"));
// Placemark controls
JButton toggleLabels = new JButton("Toggle Labels");
toggleLabels.addActionListener(e -> togglePlacemarkLabels());
advancedPanel.add(toggleLabels);
JButton changeAltitude = new JButton("Fly to High Altitude");
changeAltitude.addActionListener(e -> getWwd().getView().setEyePosition(
Position.fromDegrees(0, 0, 5000000)));
advancedPanel.add(changeAltitude);
JButton animateFlight = new JButton("Animate Flight");
animateFlight.addActionListener(e -> animateFlightPath());
advancedPanel.add(animateFlight);
// Add to existing control panel
getLayerPanel().add(advancedPanel);
}
private void togglePlacemarkLabels() {
for (PointPlacemark pm : placemarks) {
pm.setLabelText(pm.getLabelText() == null ?
pm.getValue(AVKey.DISPLAY_NAME).toString() : null);
}
getWwd().redraw();
}
private void animateFlightPath() {
// Create an animation that flies over multiple locations
ArrayList<Position> positions = new ArrayList<>();
positions.add(Position.fromDegrees(40.7128, -74.0060, 50000)); // New York
positions.add(Position.fromDegrees(51.5074, -0.1278, 50000)); // London
positions.add(Position.fromDegrees(48.8566, 2.3522, 50000)); // Paris
positions.add(Position.fromDegrees(35.6762, 139.6503, 50000)); // Tokyo
FlyToFlyViewAnimator animator = FlyToFlyViewAnimator.createFlyToFlyViewAnimator(
getWwd(),
getWwd().getView(),
positions,
null,
10000
);
getWwd().getView().stopAnimations();
getWwd().getView().addAnimator(animator);
}
public static void main(String[] args) {
start("NASA WorldWind Advanced Application", AdvancedWorldWindApp.class);
}
}
Custom Renderable Objects
1. Custom 3D Models
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.geom.Position;
import gov.nasa.worldwind.ogc.collada.ColladaLoader;
import gov.nasa.worldwind.ogc.collada.impl.ColladaRoot;
import java.net.URL;
public class ModelLayer extends RenderableLayer {
public void add3DModel(String modelPath, Position position, double scale) {
try {
URL modelUrl = new URL("file:" + modelPath);
ColladaRoot colladaRoot = ColladaLoader.createColladaRoot(modelUrl, null);
ColladaModel model = new ColladaModel(colladaRoot, position, scale);
model.setPosition(position);
this.addRenderable(model);
} catch (Exception e) {
System.err.println("Error loading 3D model: " + e.getMessage());
}
}
}
public class CustomAirplane extends AbstractRenderable {
private Position position;
private double heading;
private double scale;
public CustomAirplane(Position position, double heading, double scale) {
this.position = position;
this.heading = heading;
this.scale = scale;
}
@Override
protected void doRender(DrawContext dc) {
// Custom rendering logic for airplane
// This is a simplified version - real implementation would use actual 3D models
// Set up transformation
dc.getGL().glPushMatrix();
// Translate to position
Vec4 point = dc.getGlobe().computePointFromPosition(position);
dc.getGL().glTranslated(point.x, point.y, point.z);
// Apply rotation based on heading
dc.getGL().glRotated(heading, 0, 0, 1);
// Apply scale
dc.getGL().glScaled(scale, scale, scale);
// Render simple airplane geometry (simplified)
renderSimpleAirplane(dc);
dc.getGL().glPopMatrix();
}
private void renderSimpleAirplane(DrawContext dc) {
// Simplified airplane rendering using immediate mode
// In practice, you'd use VBOs or display lists
GL2 gl = dc.getGL().getGL2();
gl.glColor3d(1.0, 1.0, 1.0); // White
// Simple airplane body
gl.glBegin(GL2.GL_QUADS);
// Fuselage
gl.glVertex3d(-1, -0.1, -0.1);
gl.glVertex3d(1, -0.1, -0.1);
gl.glVertex3d(1, 0.1, -0.1);
gl.glVertex3d(-1, 0.1, -0.1);
gl.glEnd();
// Wings
gl.glBegin(GL2.GL_TRIANGLES);
gl.glVertex3d(0, 0, 0);
gl.glVertex3d(-2, 0, 0);
gl.glVertex3d(0, 0.5, 0);
gl.glEnd();
}
// Getters and setters
public Position getPosition() { return position; }
public void setPosition(Position position) { this.position = position; }
public double getHeading() { return heading; }
public void setHeading(double heading) { this.heading = heading; }
}
2. Advanced Placemark Implementation
public class AdvancedPlacemark extends PointPlacemark {
private boolean isSelected = false;
private String description;
private String category;
public AdvancedPlacemark(Position position) {
super(position);
setupDefaultAttributes();
}
public AdvancedPlacemark(Position position, String name, String description, String category) {
super(position);
this.setLabelText(name);
this.description = description;
this.category = category;
setupDefaultAttributes();
applyCategoryStyling();
}
private void setupDefaultAttributes() {
this.setLineEnabled(false);
this.setScale(1.5);
this.setLabelColor("FFFFFF");
this.setValue(AVKey.DISPLAY_NAME, getLabelText());
}
private void applyCategoryStyling() {
if (category != null) {
switch (category.toLowerCase()) {
case "city":
setScale(2.0);
setLabelColor("FF0000");
break;
case "airport":
setScale(1.8);
setLabelColor("00FF00");
break;
case "landmark":
setScale(1.3);
setLabelColor("FFFF00");
break;
default:
setScale(1.5);
setLabelColor("FFFFFF");
}
}
}
public void setSelected(boolean selected) {
this.isSelected = selected;
if (selected) {
this.setScale(this.getScale() * 1.5); // Enlarge when selected
this.setLabelColor("00FFFF"); // Cyan for selection
} else {
applyCategoryStyling(); // Reset to category styling
}
}
public boolean isSelected() { return isSelected; }
public String getDescription() { return description; }
public String getCategory() { return category; }
public void showInfoWindow(WorldWindow wwd) {
String htmlContent = String.format(
"<html><body style='width: 300px; padding: 10px;'>" +
"<h3>%s</h3>" +
"<p><strong>Category:</strong> %s</p>" +
"<p>%s</p>" +
"<p><strong>Position:</strong> %.4f, %.4f</p>" +
"</body></html>",
getLabelText(), category, description,
getPosition().getLatitude().getDegrees(),
getPosition().getLongitude().getDegrees()
);
JOptionPane.showMessageDialog(
(Component) wwd,
htmlContent,
"Placemark Information",
JOptionPane.INFORMATION_MESSAGE
);
}
}
Data Visualization Layers
1. Earthquake Data Visualization
public class EarthquakeLayer extends RenderableLayer {
private List<Earthquake> earthquakes;
public EarthquakeLayer() {
super();
this.setName("Earthquakes");
this.earthquakes = new ArrayList<>();
}
public void loadEarthquakeData(List<Earthquake> quakes) {
this.earthquakes.clear();
this.removeAllRenderables();
this.earthquakes.addAll(quakes);
for (Earthquake quake : quakes) {
addEarthquakePlacemark(quake);
}
}
private void addEarthquakePlacemark(Earthquake quake) {
AdvancedPlacemark placemark = new AdvancedPlacemark(
Position.fromDegrees(quake.getLatitude(), quake.getLongitude(), 0),
"M " + quake.getMagnitude(),
String.format("Depth: %.1f km", quake.getDepth()),
"earthquake"
);
// Scale based on magnitude
double scale = Math.max(1.0, quake.getMagnitude());
placemark.setScale(scale);
// Color based on depth
String color = getColorForDepth(quake.getDepth());
placemark.setLabelColor(color);
this.addRenderable(placemark);
}
private String getColorForDepth(double depth) {
if (depth < 10) return "FF0000"; // Red for shallow
if (depth < 50) return "FFFF00"; // Yellow for medium
return "00FF00"; // Green for deep
}
public void filterByMagnitude(double minMagnitude) {
this.removeAllRenderables();
for (Earthquake quake : earthquakes) {
if (quake.getMagnitude() >= minMagnitude) {
addEarthquakePlacemark(quake);
}
}
}
}
public class Earthquake {
private double latitude;
private double longitude;
private double depth;
private double magnitude;
private Date timestamp;
public Earthquake(double lat, double lon, double depth, double mag, Date time) {
this.latitude = lat;
this.longitude = lon;
this.depth = depth;
this.magnitude = mag;
this.timestamp = time;
}
// Getters
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
public double getDepth() { return depth; }
public double getMagnitude() { return magnitude; }
public Date getTimestamp() { return timestamp; }
}
2. Weather Data Layer
public class WeatherLayer extends RenderableLayer {
private Map<String, WeatherStation> stations;
public WeatherLayer() {
super();
this.setName("Weather Stations");
this.stations = new HashMap<>();
}
public void addWeatherStation(WeatherStation station) {
stations.put(station.getId(), station);
addStationPlacemark(station);
}
private void addStationPlacemark(WeatherStation station) {
PointPlacemark placemark = new PointPlacemark(
Position.fromDegrees(station.getLatitude(), station.getLongitude(), 0)
);
placemark.setLabelText(station.getName() + ": " + station.getTemperature() + "°C");
placemark.setValue(AVKey.DISPLAY_NAME, station.getName());
// Color based on temperature
String color = getColorForTemperature(station.getTemperature());
placemark.setLabelColor(color);
placemark.setScale(1.2);
placemark.setLineEnabled(false);
this.addRenderable(placemark);
}
private String getColorForTemperature(double temp) {
if (temp < 0) return "0000FF"; // Blue for cold
if (temp < 10) return "00FFFF"; // Cyan for cool
if (temp < 20) return "00FF00"; // Green for mild
if (temp < 30) return "FFFF00"; // Yellow for warm
return "FF0000"; // Red for hot
}
public void updateWeatherData(String stationId, double temperature, String conditions) {
WeatherStation station = stations.get(stationId);
if (station != null) {
station.update(temperature, conditions);
// Refresh the layer
this.removeAllRenderables();
stations.values().forEach(this::addStationPlacemark);
}
}
}
public class WeatherStation {
private String id;
private String name;
private double latitude;
private double longitude;
private double temperature;
private String conditions;
public WeatherStation(String id, String name, double lat, double lon) {
this.id = id;
this.name = name;
this.latitude = lat;
this.longitude = lon;
}
public void update(double temperature, String conditions) {
this.temperature = temperature;
this.conditions = conditions;
}
// Getters
public String getId() { return id; }
public String getName() { return name; }
public double getLatitude() { return latitude; }
public double getLongitude() { return longitude; }
public double getTemperature() { return temperature; }
public String getConditions() { return conditions; }
}
Interaction and Event Handling
1. Advanced Selection Listener
public class AdvancedSelectListener implements SelectListener {
private final WorldWindow wwd;
private AdvancedPlacemark selectedPlacemark;
public AdvancedSelectListener(WorldWindow wwd) {
this.wwd = wwd;
}
@Override
public void selected(SelectEvent event) {
if (event.getEventAction().equals(SelectEvent.LEFT_CLICK)) {
handleLeftClick(event);
} else if (event.getEventAction().equals(SelectEvent.RIGHT_CLICK)) {
handleRightClick(event);
} else if (event.getEventAction().equals(SelectEvent.HOVER)) {
handleHover(event);
}
}
private void handleLeftClick(SelectEvent event) {
Object topObject = event.getTopObject();
// Deselect previous selection
if (selectedPlacemark != null) {
selectedPlacemark.setSelected(false);
selectedPlacemark = null;
}
if (topObject instanceof AdvancedPlacemark) {
AdvancedPlacemark placemark = (AdvancedPlacemark) topObject;
placemark.setSelected(true);
selectedPlacemark = placemark;
// Center view on selected placemark
Position pos = placemark.getPosition();
wwd.getView().goTo(new Position(pos, 100000), 1000);
// Show information
placemark.showInfoWindow(wwd);
}
wwd.redraw();
}
private void handleRightClick(SelectEvent event) {
if (event.getTopObject() instanceof AdvancedPlacemark) {
showContextMenu((AdvancedPlacemark) event.getTopObject(), event.getMouseEvent());
}
}
private void handleHover(SelectEvent event) {
// Change cursor when hovering over interactive objects
if (event.getTopObject() instanceof PointPlacemark) {
((Component) wwd).setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
} else {
((Component) wwd).setCursor(Cursor.getDefaultCursor());
}
}
private void showContextMenu(AdvancedPlacemark placemark, java.awt.event.MouseEvent mouseEvent) {
JPopupMenu contextMenu = new JPopupMenu();
JMenuItem infoItem = new JMenuItem("Show Info");
infoItem.addActionListener(e -> placemark.showInfoWindow(wwd));
contextMenu.add(infoItem);
JMenuItem centerItem = new JMenuItem("Center View");
centerItem.addActionListener(e -> {
Position pos = placemark.getPosition();
wwd.getView().goTo(new Position(pos, 100000));
});
contextMenu.add(centerItem);
contextMenu.show((Component) wwd, mouseEvent.getX(), mouseEvent.getY());
}
}
2. Custom Navigation Controls
public class CustomNavigationControls {
private final WorldWindow wwd;
public CustomNavigationControls(WorldWindow wwd) {
this.wwd = wwd;
}
public JPanel createNavigationPanel() {
JPanel panel = new JPanel(new GridLayout(0, 1, 5, 5));
panel.setBorder(BorderFactory.createTitledBorder("Navigation"));
// Preset locations
String[] locations = {
"New York,USA", "London,UK", "Tokyo,Japan", "Sydney,Australia",
"Paris,France", "Beijing,China", "Cairo,Egypt", "Rio,Brazil"
};
for (String location : locations) {
JButton btn = new JButton(location);
btn.addActionListener(e -> flyToLocation(location));
panel.add(btn);
}
// Altitude controls
panel.add(createAltitudeControls());
return panel;
}
private JPanel createAltitudeControls() {
JPanel panel = new JPanel(new GridLayout(1, 3));
JButton lowAlt = new JButton("Low");
lowAlt.addActionListener(e -> setAltitude(100000));
panel.add(lowAlt);
JButton medAlt = new JButton("Medium");
medAlt.addActionListener(e -> setAltitude(1000000));
panel.add(medAlt);
JButton highAlt = new JButton("High");
highAlt.addActionListener(e -> setAltitude(10000000));
panel.add(highAlt);
return panel;
}
private void flyToLocation(String location) {
Position position = getPositionForLocation(location);
if (position != null) {
wwd.getView().goTo(new Position(position, 100000), 2000);
}
}
private void setAltitude(double altitude) {
Position current = wwd.getView().getEyePosition();
wwd.getView().goTo(new Position(current, altitude), 1000);
}
private Position getPositionForLocation(String location) {
switch (location) {
case "New York,USA": return Position.fromDegrees(40.7128, -74.0060);
case "London,UK": return Position.fromDegrees(51.5074, -0.1278);
case "Tokyo,Japan": return Position.fromDegrees(35.6762, 139.6503);
case "Sydney,Australia": return Position.fromDegrees(-33.8688, 151.2093);
case "Paris,France": return Position.fromDegrees(48.8566, 2.3522);
case "Beijing,China": return Position.fromDegrees(39.9042, 116.4074);
case "Cairo,Egypt": return Position.fromDegrees(30.0444, 31.2357);
case "Rio,Brazil": return Position.fromDegrees(-22.9068, -43.1729);
default: return Position.fromDegrees(0, 0);
}
}
}
Performance Optimization
1. Level of Detail Management
public class LODManager {
private final WorldWindow wwd;
private double detailThreshold = 1000000; // meters
public LODManager(WorldWindow wwd) {
this.wwd = wwd;
}
public void optimizeLOD(RenderableLayer layer) {
double eyeAltitude = wwd.getView().getEyePosition().getElevation();
for (Renderable renderable : layer.getRenderables()) {
if (renderable instanceof PointPlacemark) {
optimizePlacemarkLOD((PointPlacemark) renderable, eyeAltitude);
} else if (renderable instanceof AbstractShape) {
optimizeShapeLOD((AbstractShape) renderable, eyeAltitude);
}
}
}
private void optimizePlacemarkLOD(PointPlacemark placemark, double eyeAltitude) {
if (eyeAltitude > detailThreshold) {
placemark.setScale(placemark.getScale() * 0.5);
placemark.setLabelText(null); // Hide labels at high altitude
} else {
placemark.setScale(placemark.getScale() * 1.0);
// Restore label text if needed
}
}
private void optimizeShapeLOD(AbstractShape shape, double eyeAltitude) {
if (eyeAltitude > detailThreshold * 2) {
// Simplify geometry for distant viewing
if (shape instanceof Path) {
((Path) shape).setNumSubsegments(10);
}
} else {
// Use full detail
if (shape instanceof Path) {
((Path) shape).setNumSubsegments(50);
}
}
}
public void setDetailThreshold(double threshold) {
this.detailThreshold = threshold;
}
}
Complete Example Application
public class CompleteWorldWindDemo extends ApplicationTemplate.AppFrame {
private EarthquakeLayer earthquakeLayer;
private WeatherLayer weatherLayer;
private LODManager lodManager;
public CompleteWorldWindDemo() {
super(true, true, true);
this.earthquakeLayer = new EarthquakeLayer();
this.weatherLayer = new WeatherLayer();
this.lodManager = new LODManager(getWwd());
setupApplication();
loadSampleData();
setupAdvancedInteractions();
}
private void setupApplication() {
// Add custom layers
insertBeforeCompass(getWwd(), earthquakeLayer);
insertBeforeCompass(getWwd(), weatherLayer);
// Set up advanced selection
getWwd().addSelectListener(new AdvancedSelectListener(getWwd()));
// Add custom navigation
getLayerPanel().add(new CustomNavigationControls(getWwd()).createNavigationPanel());
}
private void loadSampleData() {
// Load sample earthquake data
List<Earthquake> quakes = Arrays.asList(
new Earthquake(34.0522, -118.2437, 8.0, 4.5, new Date()), // LA
new Earthquake(35.6762, 139.6503, 12.0, 5.2, new Date()), // Tokyo
new Earthquake(48.8566, 2.3522, 5.0, 3.8, new Date()) // Paris
);
earthquakeLayer.loadEarthquakeData(quakes);
// Load sample weather data
weatherLayer.addWeatherStation(new WeatherStation("NYC", "New York", 40.7128, -74.0060));
weatherLayer.addWeatherStation(new WeatherStation("LON", "London", 51.5074, -0.1278));
weatherLayer.addWeatherStation(new WeatherStation("SYD", "Sydney", -33.8688, 151.2093));
}
private void setupAdvancedInteractions() {
// Add periodic LOD optimization
Timer lodTimer = new Timer(1000, e -> lodManager.optimizeLOD(earthquakeLayer));
lodTimer.start();
// Add view change listener for dynamic LOD
getWwd().addViewListener(new ViewListener() {
@Override
public void viewChanged(View view) {
lodManager.optimizeLOD(earthquakeLayer);
}
});
}
public static void main(String[] args) {
start("NASA WorldWind Complete Demo", CompleteWorldWindDemo.class);
}
}
Best Practices
- Memory Management: Dispose of resources properly
- Performance: Use LOD and culling for large datasets
- Threading: Perform heavy operations off the EDT
- Error Handling: Handle network and file errors gracefully
- User Experience: Provide feedback for long operations
// Example of proper resource cleanup
public class WorldWindResourceManager {
public static void cleanup(WorldWindow wwd) {
if (wwd != null) {
wwd.shutdown();
}
// Clear any static caches
MemoryCache.getInstance().clear();
BasicWWObjectImpl.clearCache();
}
}
Conclusion
NASA WorldWind provides a powerful platform for building sophisticated 3D geographic applications. Key capabilities include:
- High-performance 3D globe rendering
- Extensive geospatial data support
- Custom visualization and interaction
- Advanced animation and navigation
- Professional-grade performance optimization
This implementation demonstrates how to create everything from basic viewers to advanced applications with custom data visualization, interaction handling, and performance optimization.