src.gov.nasa.worldwindx.examples.kml.KMLViewer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of worldwind Show documentation
Show all versions of worldwind Show documentation
World Wind is a collection of components that interactively display 3D geographic information within Java applications or applets.
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwindx.examples.kml;
import gov.nasa.worldwind.WorldWind;
import gov.nasa.worldwind.avlist.AVKey;
import gov.nasa.worldwind.retrieve.RetrievalService;
import gov.nasa.worldwindx.examples.ApplicationTemplate;
import gov.nasa.worldwindx.examples.util.*;
import gov.nasa.worldwind.util.layertree.*;
import gov.nasa.worldwind.layers.RenderableLayer;
import gov.nasa.worldwind.ogc.kml.*;
import gov.nasa.worldwind.ogc.kml.impl.KMLController;
import gov.nasa.worldwind.render.Offset;
import gov.nasa.worldwind.util.*;
import javax.swing.*;
import javax.swing.filechooser.*;
import javax.xml.stream.XMLStreamException;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
import java.io.*;
import java.net.URL;
/**
* An example application that allows the user to import a KML or KMZ file as a layer. The contents of the file are
* displayed in a feature tree. Click on KML features in the tree to navigate the view to the feature. Clicking on
* features on the globe will open an info balloon for the feature, if the feature provides a description. Use the File
* menu to open a document from a local file or from a URL.
*
* @author tag
* @version $Id: KMLViewer.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class KMLViewer extends ApplicationTemplate
{
public static class AppFrame extends ApplicationTemplate.AppFrame
{
protected LayerTree layerTree;
protected RenderableLayer hiddenLayer;
protected HotSpotController hotSpotController;
protected KMLApplicationController kmlAppController;
protected BalloonController balloonController;
public AppFrame()
{
super(true, false, false); // Don't include the layer panel; we're using the on-screen layer tree.
// Add the on-screen layer tree, refreshing model with the WorldWindow's current layer list. We
// intentionally refresh the tree's model before adding the layer that contains the tree itself. This
// prevents the tree's layer from being displayed in the tree itself.
this.layerTree = new LayerTree(new Offset(20d, 160d, AVKey.PIXELS, AVKey.INSET_PIXELS));
this.layerTree.getModel().refresh(this.getWwd().getModel().getLayers());
// Set up a layer to display the on-screen layer tree in the WorldWindow. This layer is not displayed in
// the layer tree's model. Doing so would enable the user to hide the layer tree display with no way of
// bringing it back.
this.hiddenLayer = new RenderableLayer();
this.hiddenLayer.addRenderable(this.layerTree);
this.getWwd().getModel().getLayers().add(this.hiddenLayer);
// Add a controller to handle input events on the layer selector and on browser balloons.
this.hotSpotController = new HotSpotController(this.getWwd());
// Add a controller to handle common KML application events.
this.kmlAppController = new KMLApplicationController(this.getWwd());
// Add a controller to display balloons when placemarks are clicked. We override the method addDocumentLayer
// so that loading a KML document by clicking a KML balloon link displays an entry in the on-screen layer
// tree.
this.balloonController = new BalloonController(this.getWwd())
{
@Override
protected void addDocumentLayer(KMLRoot document)
{
addKMLLayer(document);
}
};
// Give the KML app controller a reference to the BalloonController so that the app controller can open
// KML feature balloons when feature's are selected in the on-screen layer tree.
this.kmlAppController.setBalloonController(balloonController);
// Size the World Window to take up the space typically used by the layer panel.
Dimension size = new Dimension(1400, 800);
this.setPreferredSize(size);
this.pack();
WWUtil.alignComponent(null, this, AVKey.CENTER);
makeMenu(this);
// Set up to receive SSLHandshakeExceptions that occur during resource retrieval.
WorldWind.getRetrievalService().setSSLExceptionListener(new RetrievalService.SSLExceptionListener()
{
public void onException(Throwable e, String path)
{
System.out.println(path);
System.out.println(e);
}
});
}
/**
* Adds the specified kmlRoot
to this app frame's WorldWindow
as a new
* Layer
, and adds a new KMLLayerTreeNode
for the kmlRoot
to this app
* frame's on-screen layer tree.
*
* This expects the kmlRoot
's AVKey.DISPLAY_NAME
field to contain a display name
* suitable for use as a layer name.
*
* @param kmlRoot the KMLRoot to add a new layer for.
*/
protected void addKMLLayer(KMLRoot kmlRoot)
{
// Create a KMLController to adapt the KMLRoot to the World Wind renderable interface.
KMLController kmlController = new KMLController(kmlRoot);
// Adds a new layer containing the KMLRoot to the end of the WorldWindow's layer list. This
// retrieves the layer name from the KMLRoot's DISPLAY_NAME field.
RenderableLayer layer = new RenderableLayer();
layer.setName((String) kmlRoot.getField(AVKey.DISPLAY_NAME));
layer.addRenderable(kmlController);
this.getWwd().getModel().getLayers().add(layer);
// Adds a new layer tree node for the KMLRoot to the on-screen layer tree, and makes the new node visible
// in the tree. This also expands any tree paths that represent open KML containers or open KML network
// links.
KMLLayerTreeNode layerNode = new KMLLayerTreeNode(layer, kmlRoot);
this.layerTree.getModel().addLayer(layerNode);
this.layerTree.makeVisible(layerNode.getPath());
layerNode.expandOpenContainers(this.layerTree);
// Listens to refresh property change events from KML network link nodes. Upon receiving such an event this
// expands any tree paths that represent open KML containers. When a KML network link refreshes, its tree
// node replaces its children with new nodes created from the refreshed content, then sends a refresh
// property change event through the layer tree. By expanding open containers after a network link refresh,
// we ensure that the network link tree view appearance is consistent with the KML specification.
layerNode.addPropertyChangeListener(AVKey.RETRIEVAL_STATE_SUCCESSFUL, new PropertyChangeListener()
{
public void propertyChange(final PropertyChangeEvent event)
{
if (event.getSource() instanceof KMLNetworkLinkTreeNode)
{
// Manipulate the tree on the EDT.
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
((KMLNetworkLinkTreeNode) event.getSource()).expandOpenContainers(layerTree);
getWwd().redraw();
}
});
}
}
});
}
}
/** A Thread
that loads a KML file and displays it in an AppFrame
. */
public static class WorkerThread extends Thread
{
/** Indicates the source of the KML file loaded by this thread. Initialized during construction. */
protected Object kmlSource;
/** Indicates the AppFrame
the KML file content is displayed in. Initialized during construction. */
protected AppFrame appFrame;
/**
* Creates a new worker thread from a specified kmlSource
and appFrame
.
*
* @param kmlSource the source of the KML file to load. May be a {@link File}, a {@link URL}, or an {@link
* java.io.InputStream}, or a {@link String} identifying a file path or URL.
* @param appFrame the AppFrame
in which to display the KML source.
*/
public WorkerThread(Object kmlSource, AppFrame appFrame)
{
this.kmlSource = kmlSource;
this.appFrame = appFrame;
}
/**
* Loads this worker thread's KML source into a new {@link gov.nasa.worldwind.ogc.kml.KMLRoot}
,
* then adds the new KMLRoot
to this worker thread's AppFrame
. The
* KMLRoot
's AVKey.DISPLAY_NAME
field contains a display name created from either the
* KML source or the KML root feature name.
*
* If loading the KML source fails, this prints the exception and its stack trace to the standard error stream,
* but otherwise does nothing.
*/
public void run()
{
try
{
KMLRoot kmlRoot = this.parse();
// Set the document's display name
kmlRoot.setField(AVKey.DISPLAY_NAME, formName(this.kmlSource, kmlRoot));
// Schedule a task on the EDT to add the parsed document to a layer
final KMLRoot finalKMLRoot = kmlRoot;
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
appFrame.addKMLLayer(finalKMLRoot);
}
});
}
catch (Exception e)
{
e.printStackTrace();
}
}
/**
* Parse the KML document.
*
* @return The parsed document.
*
* @throws IOException if the document cannot be read.
* @throws XMLStreamException if document cannot be parsed.
*/
protected KMLRoot parse() throws IOException, XMLStreamException
{
// KMLRoot.createAndParse will attempt to parse the document using a namespace aware parser, but if that
// fails due to a parsing error it will try again using a namespace unaware parser. Note that this second
// step may require the document to be read from the network again if the kmlSource is a stream.
return KMLRoot.createAndParse(this.kmlSource);
}
}
protected static String formName(Object kmlSource, KMLRoot kmlRoot)
{
KMLAbstractFeature rootFeature = kmlRoot.getFeature();
if (rootFeature != null && !WWUtil.isEmpty(rootFeature.getName()))
return rootFeature.getName();
if (kmlSource instanceof File)
return ((File) kmlSource).getName();
if (kmlSource instanceof URL)
return ((URL) kmlSource).getPath();
if (kmlSource instanceof String && WWIO.makeURL((String) kmlSource) != null)
return WWIO.makeURL((String) kmlSource).getPath();
return "KML Layer";
}
protected static void makeMenu(final AppFrame appFrame)
{
final JFileChooser fileChooser = new JFileChooser();
fileChooser.setMultiSelectionEnabled(true);
fileChooser.addChoosableFileFilter(new FileNameExtensionFilter("KML/KMZ File", "kml", "kmz"));
JMenuBar menuBar = new JMenuBar();
appFrame.setJMenuBar(menuBar);
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
JMenuItem openFileMenuItem = new JMenuItem(new AbstractAction("Open File...")
{
public void actionPerformed(ActionEvent actionEvent)
{
try
{
int status = fileChooser.showOpenDialog(appFrame);
if (status == JFileChooser.APPROVE_OPTION)
{
for (File file : fileChooser.getSelectedFiles())
{
new WorkerThread(file, appFrame).start();
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
fileMenu.add(openFileMenuItem);
JMenuItem openURLMenuItem = new JMenuItem(new AbstractAction("Open URL...")
{
public void actionPerformed(ActionEvent actionEvent)
{
try
{
String status = JOptionPane.showInputDialog(appFrame, "URL");
if (!WWUtil.isEmpty(status))
{
new WorkerThread(status.trim(), appFrame).start();
}
}
catch (Exception e)
{
e.printStackTrace();
}
}
});
fileMenu.add(openURLMenuItem);
}
public static void main(String[] args)
{
//noinspection UnusedDeclaration
final AppFrame af = (AppFrame) start("World Wind KML Viewer", AppFrame.class);
}
}