src.gov.nasa.worldwindx.examples.FlatWorldEarthquakes 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;
import gov.nasa.worldwind.*;
import gov.nasa.worldwind.avlist.*;
import gov.nasa.worldwind.event.*;
import gov.nasa.worldwind.formats.geojson.GeoJSONPoint;
import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.globes.EarthFlat;
import gov.nasa.worldwind.layers.*;
import gov.nasa.worldwind.render.*;
import gov.nasa.worldwind.util.*;
import gov.nasa.worldwind.view.orbit.*;
import javax.media.opengl.*;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.*;
import java.nio.DoubleBuffer;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.logging.Level;
/**
* Using the EarthFlat and FlatOrbitView to display USGS latest earthquakes rss feed.
*
* @author Patrick Murris
* @version $Id: FlatWorldEarthquakes.java 1624 2013-09-19 21:12:00Z dcollins $
*/
public class FlatWorldEarthquakes extends ApplicationTemplate
{
// See the USGS GeoJSON feed documentation for information on this earthquake data feed:
// http://earthquake.usgs.gov/earthquakes/feed/v1.0/geojson.php
protected static final String USGS_EARTHQUAKE_FEED_URL
= "http://earthquake.usgs.gov/earthquakes/feed/v1.0/summary/2.5_week.geojson";
protected static final String USGS_EARTHQUAKE_MAGNITUDE = "mag";
protected static final String USGS_EARTHQUAKE_PLACE = "place";
protected static final String USGS_EARTHQUAKE_TIME = "time";
protected static final long UPDATE_INTERVAL = 300000; // 5 minutes
protected static final long MILLISECONDS_PER_MINUTE = 60000;
protected static final long MILLISECONDS_PER_HOUR = 60 * MILLISECONDS_PER_MINUTE;
protected static final long MILLISECONDS_PER_DAY = 24 * MILLISECONDS_PER_HOUR;
@SuppressWarnings("unchecked")
public static class AppFrame extends ApplicationTemplate.AppFrame
{
private RenderableLayer eqLayer;
private EqAnnotation mouseEq, latestEq;
private GlobeAnnotation tooltipAnnotation;
private JButton downloadButton;
private JLabel statusLabel, latestLabel;
private Blinker blinker;
private Timer updater;
private long updateTime;
private JComboBox magnitudeCombo;
public AppFrame()
{
super(true, true, false);
// Change atmosphere SkyGradientLayer for SkyColorLayer
// and set worldmap and compass max active altitude
LayerList layers = this.getWwd().getModel().getLayers();
for (int i = 0; i < layers.size(); i++)
{
if (layers.get(i) instanceof SkyGradientLayer)
layers.set(i, new SkyColorLayer());
else if (layers.get(i) instanceof WorldMapLayer)
(layers.get(i)).setMaxActiveAltitude(20e6);
else if (layers.get(i) instanceof CompassLayer)
(layers.get(i)).setMaxActiveAltitude(20e6);
}
// Init tooltip annotation
this.tooltipAnnotation = new GlobeAnnotation("", Position.fromDegrees(0, 0, 0));
Font font = Font.decode("Arial-Plain-16");
this.tooltipAnnotation.getAttributes().setFont(font);
this.tooltipAnnotation.getAttributes().setSize(new Dimension(400, 0));
this.tooltipAnnotation.getAttributes().setDistanceMinScale(1);
this.tooltipAnnotation.getAttributes().setDistanceMaxScale(1);
this.tooltipAnnotation.getAttributes().setVisible(false);
this.tooltipAnnotation.setPickEnabled(false);
this.tooltipAnnotation.setAlwaysOnTop(true);
// Add control panels
JPanel controls = new JPanel();
controls.setLayout(new BoxLayout(controls, BoxLayout.Y_AXIS));
// Add earthquakes view control panel
controls.add(makeEarthquakesPanel());
// Add flat world projection control panel
controls.add(new FlatWorldPanel(this.getWwd()));
this.getLayerPanel().add(controls, BorderLayout.SOUTH);
// Add select listener for earthquake picking
this.getWwd().addSelectListener(new SelectListener()
{
public void selected(SelectEvent event)
{
if (event.getEventAction().equals(SelectEvent.ROLLOVER))
highlight(event.getTopObject());
}
});
// Add click-and-go select listener for earthquakes
this.getWwd().addSelectListener(new ClickAndGoSelectListener(
this.getWwd(), EqAnnotation.class, 1000e3));
// Add updater timer
this.updater = new Timer(1000, new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
long now = System.currentTimeMillis();
long elapsed = now - updateTime;
if (elapsed >= UPDATE_INTERVAL)
{
updateTime = now;
downloadButton.setText("Update");
startEarthquakeDownload();
}
else
{
// Display remaining time in button text
long remaining = UPDATE_INTERVAL - elapsed;
int min = (int) Math.floor((double) remaining / MILLISECONDS_PER_MINUTE);
int sec = (int) ((remaining - min * MILLISECONDS_PER_MINUTE) / 1000);
downloadButton.setText(String.format("Update (in %1$02d:%2$02d)", min, sec));
}
}
});
this.updater.start();
}
private void highlight(Object o)
{
if (this.mouseEq == o)
return; // same thing selected
if (this.mouseEq != null)
{
this.mouseEq.getAttributes().setHighlighted(false);
this.mouseEq = null;
this.tooltipAnnotation.getAttributes().setVisible(false);
}
if (o != null && o instanceof EqAnnotation)
{
this.mouseEq = (EqAnnotation) o;
this.mouseEq.getAttributes().setHighlighted(true);
this.tooltipAnnotation.setText(this.composeEarthquakeText(this.mouseEq));
this.tooltipAnnotation.setPosition(this.mouseEq.getPosition());
this.tooltipAnnotation.getAttributes().setVisible(true);
this.getWwd().redraw();
}
}
private void setBlinker(EqAnnotation ea)
{
if (this.blinker != null)
{
this.blinker.stop();
this.getWwd().redraw();
}
if (ea == null)
return;
this.blinker = new Blinker(ea);
}
private void setLatestLabel(EqAnnotation ea)
{
if (ea != null)
{
this.latestLabel.setText(this.composeEarthquakeText(ea));
}
else
{
this.latestLabel.setText("");
}
}
private String composeEarthquakeText(EqAnnotation eqAnnotation)
{
StringBuilder sb = new StringBuilder();
sb.append("");
Number magnitude = (Number) eqAnnotation.getValue(USGS_EARTHQUAKE_MAGNITUDE);
String place = (String) eqAnnotation.getValue(USGS_EARTHQUAKE_PLACE);
if (magnitude != null || !WWUtil.isEmpty(place))
{
sb.append("");
if (magnitude != null)
sb.append("M ").append(magnitude).append(" - ");
if (place != null)
sb.append(place);
sb.append("");
sb.append("
");
}
Number time = (Number) eqAnnotation.getValue(USGS_EARTHQUAKE_TIME);
if (time != null)
{
long elapsed = this.updateTime - time.longValue();
sb.append(this.timePassedToString(elapsed));
sb.append("
");
}
sb.append(String.format("%.2f", eqAnnotation.getPosition().elevation)).append(" km deep");
sb.append("");
return sb.toString();
}
protected String timePassedToString(long duration)
{
if (duration > MILLISECONDS_PER_DAY)
{
long days = duration / MILLISECONDS_PER_DAY;
return days + (days > 1 ? " days ago" : " day ago");
}
else if (duration > MILLISECONDS_PER_HOUR)
{
long hours = duration / MILLISECONDS_PER_HOUR;
return hours + (hours > 1 ? " hours ago" : " hour ago");
}
else if (duration > MILLISECONDS_PER_MINUTE)
{
long minutes = duration / MILLISECONDS_PER_MINUTE;
return minutes + (minutes > 1 ? " minutes ago" : " minute ago");
}
else
{
return "moments ago";
}
}
private JPanel makeEarthquakesPanel()
{
JPanel controlPanel = new JPanel();
controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));
// Zoom on latest button
JPanel zoomPanel = new JPanel(new GridLayout(0, 1, 0, 0));
zoomPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
JButton btZoom = new JButton("Zoom on latest");
btZoom.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if (latestEq != null)
{
Position targetPos = latestEq.getPosition();
BasicOrbitView view = (BasicOrbitView) getWwd().getView();
view.addPanToAnimator(
// The elevation component of 'targetPos' here is not the surface elevation,
// so we ignore it when specifying the view center position.
new Position(targetPos, 0),
Angle.ZERO, Angle.ZERO, 1000e3);
}
}
});
zoomPanel.add(btZoom);
controlPanel.add(zoomPanel);
// View reset button
JPanel viewPanel = new JPanel(new GridLayout(0, 1, 0, 0));
viewPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
JButton btReset = new JButton("Reset Global View");
btReset.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
Double lat = Configuration.getDoubleValue(AVKey.INITIAL_LATITUDE);
Double lon = Configuration.getDoubleValue(AVKey.INITIAL_LONGITUDE);
Double elevation = Configuration.getDoubleValue(AVKey.INITIAL_ALTITUDE);
Position targetPos = Position.fromDegrees(lat, lon, 0);
BasicOrbitView view = (BasicOrbitView) getWwd().getView();
view.addPanToAnimator(
// The elevation component of 'targetPos' here is not the surface elevation,
// so we ignore it when specifying the view center position.
new Position(targetPos, 0),
Angle.ZERO, Angle.ZERO, elevation);
}
});
viewPanel.add(btReset);
controlPanel.add(viewPanel);
// Update button
JPanel downloadPanel = new JPanel(new GridLayout(0, 1, 0, 0));
downloadPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
this.downloadButton = new JButton("Update");
this.downloadButton.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
startEarthquakeDownload();
}
});
this.downloadButton.setEnabled(false);
downloadPanel.add(this.downloadButton);
controlPanel.add(downloadPanel);
// Status label
JPanel statusPanel = new JPanel(new GridLayout(0, 1, 0, 0));
statusPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
this.statusLabel = new JLabel();
this.statusLabel.setPreferredSize(new Dimension(200, 20));
this.statusLabel.setVerticalAlignment(SwingConstants.CENTER);
statusPanel.add(this.statusLabel);
controlPanel.add(statusPanel);
// Magnitude filter combo
JPanel magnitudePanel = new JPanel(new GridLayout(0, 2, 0, 0));
magnitudePanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
magnitudePanel.add(new JLabel("Min Magnitude:"));
magnitudeCombo = new JComboBox(new String[] {"2.5", "3", "4", "5", "6", "7"});
magnitudeCombo.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
applyMagnitudeFilter(Double.parseDouble((String) magnitudeCombo.getSelectedItem()));
}
});
magnitudePanel.add(magnitudeCombo);
controlPanel.add(magnitudePanel);
// Blink latest checkbox
JPanel blinkPanel = new JPanel(new GridLayout(0, 2, 0, 0));
blinkPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
blinkPanel.add(new JLabel("Latest:"));
final JCheckBox jcb = new JCheckBox("Animate");
jcb.setSelected(true);
jcb.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
if (jcb.isSelected())
{
setBlinker(latestEq);
}
else
{
setBlinker(null);
}
}
});
blinkPanel.add(jcb);
controlPanel.add(blinkPanel);
// Latest label
JPanel latestPanel = new JPanel(new GridLayout(0, 1, 0, 0));
latestPanel.setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
this.latestLabel = new JLabel();
this.latestLabel.setPreferredSize(new Dimension(200, 60));
this.latestLabel.setVerticalAlignment(SwingConstants.TOP);
latestPanel.add(this.latestLabel);
controlPanel.add(latestPanel);
controlPanel.setBorder(
new CompoundBorder(BorderFactory.createEmptyBorder(9, 9, 9, 9), new TitledBorder("Earthquakes")));
controlPanel.setToolTipText("Earthquakes controls.");
return controlPanel;
}
// Earthquake layer ------------------------------------------------------------------
private void startEarthquakeDownload()
{
WorldWind.getScheduledTaskService().addTask(new Runnable()
{
public void run()
{
downloadEarthquakes(USGS_EARTHQUAKE_FEED_URL);
}
});
}
private void downloadEarthquakes(String earthquakeFeedUrl)
{
// Disable download button and update status label
if (this.downloadButton != null)
this.downloadButton.setEnabled(false);
if (this.statusLabel != null)
this.statusLabel.setText("Updating earthquakes...");
RenderableLayer newLayer = (RenderableLayer) buildEarthquakeLayer(earthquakeFeedUrl);
if (newLayer.getNumRenderables() > 0)
{
LayerList layers = this.getWwd().getModel().getLayers();
if (this.eqLayer != null)
layers.remove(this.eqLayer);
this.eqLayer = newLayer;
this.eqLayer.addRenderable(this.tooltipAnnotation);
insertBeforePlacenames(this.getWwd(), this.eqLayer);
this.getLayerPanel().update(this.getWwd());
this.applyMagnitudeFilter(Double.parseDouble((String) magnitudeCombo.getSelectedItem()));
if (this.statusLabel != null)
this.statusLabel.setText("Updated " + new SimpleDateFormat("EEE h:mm aa").format(new Date())); // now
}
else
{
if (this.statusLabel != null)
this.statusLabel.setText("No earthquakes");
}
if (this.downloadButton != null)
this.downloadButton.setEnabled(true);
}
private Layer buildEarthquakeLayer(String earthquakeFeedUrl)
{
GeoJSONLoader loader = new GeoJSONLoader()
{
@Override
protected void addRenderableForPoint(GeoJSONPoint geom, RenderableLayer layer, AVList properties)
{
try
{
addEarthquake(geom, layer, properties);
}
catch (Exception e)
{
Logging.logger().log(Level.WARNING, "Exception adding earthquake", e);
}
}
};
RenderableLayer layer = new RenderableLayer();
layer.setName("Earthquakes");
loader.addSourceGeometryToLayer(earthquakeFeedUrl, layer);
return layer;
}
private AnnotationAttributes eqAttributes;
private Color eqColors[] =
{
Color.RED,
Color.ORANGE,
Color.YELLOW,
Color.GREEN,
Color.BLUE,
Color.GRAY,
Color.BLACK,
};
private void addEarthquake(GeoJSONPoint geom, RenderableLayer layer, AVList properties)
{
if (eqAttributes == null)
{
// Init default attributes for all eq
eqAttributes = new AnnotationAttributes();
eqAttributes.setLeader(AVKey.SHAPE_NONE);
eqAttributes.setDrawOffset(new Point(0, -16));
eqAttributes.setSize(new Dimension(32, 32));
eqAttributes.setBorderWidth(0);
eqAttributes.setCornerRadius(0);
eqAttributes.setBackgroundColor(new Color(0, 0, 0, 0));
}
EqAnnotation eq = new EqAnnotation(geom.getPosition(), eqAttributes);
eq.setAltitudeMode(WorldWind.CLAMP_TO_GROUND); // GeoJON point's 3rd coordinate indicates depth
eq.setValues(properties);
Number eqMagnitude = (Number) eq.getValue(USGS_EARTHQUAKE_MAGNITUDE);
Number eqTime = (Number) eq.getValue(USGS_EARTHQUAKE_TIME);
int elapsedDays = 6;
if (eqTime != null)
{
// Compute days elapsed since earthquake event
elapsedDays = (int) ((this.updateTime - eqTime.longValue()) / MILLISECONDS_PER_DAY);
// Update latest earthquake event
if (this.latestEq != null)
{
Number latestEqTime = (Number) this.latestEq.getValue(USGS_EARTHQUAKE_TIME);
if (latestEqTime.longValue() < eqTime.longValue())
this.latestEq = eq;
}
else
{
this.latestEq = eq;
}
}
eq.getAttributes().setTextColor(eqColors[Math.min(elapsedDays, eqColors.length - 1)]);
eq.getAttributes().setScale(eqMagnitude.doubleValue() / 10);
layer.addRenderable(eq);
}
private void applyMagnitudeFilter(double minMagnitude)
{
this.latestEq = null;
setBlinker(null);
setLatestLabel(null);
Iterable renderables = eqLayer.getRenderables();
for (Renderable r : renderables)
{
if (r instanceof EqAnnotation)
{
EqAnnotation eq = (EqAnnotation) r;
Number eqMagnitude = (Number) eq.getValue(USGS_EARTHQUAKE_MAGNITUDE);
Number eqTime = (Number) eq.getValue(USGS_EARTHQUAKE_TIME);
boolean meetsMagnitudeCriteria = eqMagnitude.doubleValue() >= minMagnitude;
eq.getAttributes().setVisible(meetsMagnitudeCriteria);
if (meetsMagnitudeCriteria)
{
if (this.latestEq != null)
{
Number latestEqTime = (Number) this.latestEq.getValue(USGS_EARTHQUAKE_TIME);
if (latestEqTime != null && eqTime != null && latestEqTime.longValue() < eqTime.longValue())
this.latestEq = eq;
}
else
{
this.latestEq = eq;
}
}
}
}
setBlinker(this.latestEq);
setLatestLabel(this.latestEq);
this.getWwd().redraw();
}
private class EqAnnotation extends GlobeAnnotation
{
public EqAnnotation(Position position, AnnotationAttributes defaults)
{
super("", position, defaults);
}
protected void applyScreenTransform(DrawContext dc, int x, int y, int width, int height, double scale)
{
double finalScale = scale * this.computeScale(dc);
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glTranslated(x, y, 0);
gl.glScaled(finalScale, finalScale, 1);
}
// Override annotation drawing for a simple circle
private DoubleBuffer shapeBuffer;
protected void doDraw(DrawContext dc, int width, int height, double opacity, Position pickPosition)
{
// Draw colored circle around screen point - use annotation's text color
if (dc.isPickingMode())
{
this.bindPickableObject(dc, pickPosition);
}
this.applyColor(dc, this.getAttributes().getTextColor(), 0.6 * opacity, true);
// Draw 32x32 shape from its bottom left corner
int size = 32;
if (this.shapeBuffer == null)
this.shapeBuffer = FrameFactory.createShapeBuffer(AVKey.SHAPE_ELLIPSE, size, size, 0, null);
GL2 gl = dc.getGL().getGL2(); // GL initialization checks for GL2 compatibility.
gl.glTranslated(-size / 2, -size / 2, 0);
FrameFactory.drawBuffer(dc, GL.GL_TRIANGLE_FAN, this.shapeBuffer);
}
}
private class Blinker
{
private EqAnnotation annotation;
private double initialScale, initialOpacity;
private int steps = 10;
private int step = 0;
private int delay = 100;
private Timer timer;
private Blinker(EqAnnotation ea)
{
this.annotation = ea;
this.initialScale = this.annotation.getAttributes().getScale();
this.initialOpacity = this.annotation.getAttributes().getOpacity();
this.timer = new Timer(delay, new ActionListener()
{
public void actionPerformed(ActionEvent event)
{
annotation.getAttributes().setScale(initialScale * (1f + 7f * ((float) step / (float) steps)));
annotation.getAttributes().setOpacity(initialOpacity * (1f - ((float) step / (float) steps)));
step = step == steps ? 0 : step + 1;
getWwd().redraw();
}
});
start();
}
private void stop()
{
timer.stop();
step = 0;
this.annotation.getAttributes().setScale(initialScale);
this.annotation.getAttributes().setOpacity(initialOpacity);
}
private void start()
{
timer.start();
}
}
} // End AppFrame
// --- Main -------------------------------------------------------------------------
public static void main(String[] args)
{
// Adjust configuration values before instantiation
Configuration.setValue(AVKey.INITIAL_LATITUDE, 0);
Configuration.setValue(AVKey.INITIAL_LONGITUDE, 0);
Configuration.setValue(AVKey.INITIAL_ALTITUDE, 50e6);
Configuration.setValue(AVKey.GLOBE_CLASS_NAME, EarthFlat.class.getName());
Configuration.setValue(AVKey.VIEW_CLASS_NAME, FlatOrbitView.class.getName());
ApplicationTemplate.start("World Wind USGS Earthquakes M 2.5+ - 7 days", AppFrame.class);
}
}