/* Copyright (C) 2001, 2008 United States Government as represented by the Administrator of the National Aeronautics and Space Administration. All Rights Reserved. */ package gov.nasa.worldwind.examples; import com.sun.opengl.util.j2d.*; import gov.nasa.worldwind.*; import gov.nasa.worldwind.avlist.*; import gov.nasa.worldwind.event.*; import gov.nasa.worldwind.exception.*; import gov.nasa.worldwind.geom.*; import gov.nasa.worldwind.globes.*; import gov.nasa.worldwind.layers.*; import gov.nasa.worldwind.render.*; import gov.nasa.worldwind.util.Logging; import gov.nasa.worldwind.util.*; import gov.nasa.worldwind.view.*; import org.w3c.dom.*; import org.xml.sax.*; import javax.media.opengl.*; import javax.swing.*; import javax.swing.Timer; import javax.swing.border.*; import javax.xml.parsers.*; import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.io.*; import java.net.*; import java.nio.*; import java.text.*; import java.util.*; import java.util.logging.Level; /** * Using the EarthFlat and FlatOrbitView to display USGS latest earthquakes rss feed. * * @author Patrick Murris * @version $Id: FlatWorldEarthquakes.java 5198 2008-04-28 21:37:49Z patrickmurris $ */ public class FlatWorldEarthquakes extends ApplicationTemplate { 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 Date lastUpdate; private Blinker blinker; private Timer updater; private Date lastUpdaterEvent; 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) ((WorldMapLayer)layers.get(i)).setMaxActiveAltitude(20e6); else if(layers.get(i) instanceof CompassLayer) ((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().setTextColor(Color.BLACK); this.tooltipAnnotation.getAttributes().setBorderColor(Color.BLACK); this.tooltipAnnotation.getAttributes().setBackgroundColor(new Color(1f, 1f, 1f, .8f)); this.tooltipAnnotation.getAttributes().setSize(new Dimension(270, 0)); this.tooltipAnnotation.getAttributes().setDistanceMinScale(1); this.tooltipAnnotation.getAttributes().setDistanceMaxScale(1); this.tooltipAnnotation.getAttributes().setVisible(false); this.tooltipAnnotation.setAlwaysOnTop(true); // Add non antialiased font to textrenderer cache Object sc = this.getWwd().getSceneController(); if( sc != null && sc instanceof BasicSceneController) { BasicSceneController bsc = (BasicSceneController)sc; TextRenderer tr = new TextRenderer(font, false, false); // no antialias and no decimal metrics tr.setUseVertexArrays(false); bsc.getTextRendererCache().add(font, tr); } // 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) { if (lastUpdaterEvent == null) lastUpdaterEvent = new Date(); Date now = new Date(); long delay = javax.management.timer.Timer.ONE_MINUTE * 5; long elapsed = now.getTime() - lastUpdaterEvent.getTime(); if (elapsed >= delay) { // Auto download every 5 minutes lastUpdaterEvent = new Date(); downloadButton.setText("Update"); startEarthquakeDownload(); } else { // Display remaining time in button text long remain = delay - elapsed; int min = (int)Math.floor((double)remain / javax.management.timer.Timer.ONE_MINUTE); int sec = (int)((remain - min * javax.management.timer.Timer.ONE_MINUTE) / javax.management.timer.Timer.ONE_SECOND); downloadButton.setText(String.format("Update (in %1$02d:%2$02d)", min, sec)); } } }); this.updater.start(); // Download earthquakes startEarthquakeDownload(); } 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("<p><b>" + this.mouseEq.earthquake.title + "</b></p>" + composeElapsedString(this.mouseEq) + "<br />" + this.mouseEq.earthquake.summary); this.tooltipAnnotation.setPosition(this.mouseEq.earthquake.position); this.tooltipAnnotation.getAttributes().setVisible(true); this.getWwd().repaint(); } } private void setBlinker(EqAnnotation ea) { if (this.blinker != null) { this.blinker.stop(); this.getWwd().repaint(); } if (ea == null) return; this.blinker = new Blinker(ea); } private void setLatestLabel(EqAnnotation ea) { this.latestLabel.setText(""); if (ea != null) { String htmlText = "<html>" + composeElapsedString(ea) + "<p><b>" + ea.earthquake.title + "</b></p>" + ea.earthquake.summary + "</html>"; htmlText = htmlText.replaceAll("(?i)<img\\s?.*?>", "\n"); // Remove <img> tags this.latestLabel.setText(htmlText); } } private String composeElapsedString(EqAnnotation ea) { String s = ""; if (ea.earthquake.date != null) { Date now = new Date(); long elapsed = now.getTime() - ea.earthquake.date.getTime(); long days = elapsed / javax.management.timer.Timer.ONE_DAY; elapsed -= days * javax.management.timer.Timer.ONE_DAY; long hours = elapsed / javax.management.timer.Timer.ONE_HOUR; elapsed -= hours * javax.management.timer.Timer.ONE_HOUR; long minutes = elapsed / javax.management.timer.Timer.ONE_MINUTE; if(days > 0) { s = days + (days > 1 ? " days" : " day") + (hours > 0 ? " and " + hours + (hours > 1 ? " hours" : " hour") : ""); } else { if (hours > 0) s = hours + (hours > 1 ? " hours" : " hour") + (hours < 12 ? " and " + minutes + (minutes > 1 ? " minutes" : " minute") : ""); else s = minutes + (minutes > 1 ? " minutes" : " minute"); } s += " ago"; } return s; } 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.earthquake.position; OrbitView view = (OrbitView) getWwd().getView(); Globe globe = getWwd().getModel().getGlobe(); // Use a PanToIterator zoom onto the latest eq postion view.applyStateIterator(FlyToOrbitViewStateIterator.createPanToIterator( // The elevation component of 'targetPos' here is not the surface elevation, // so we ignore it when specifying the view center position. view, globe, new Position(targetPos.getLatLon(), 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); OrbitView view = (OrbitView) getWwd().getView(); Globe globe = getWwd().getModel().getGlobe(); // Use a PanToIterator to go back to initial view postion view.applyStateIterator(FlyToOrbitViewStateIterator.createPanToIterator( // The elevation component of 'targetPos' here is not the surface elevation, // so we ignore it when specifying the view center position. view, globe, new Position(targetPos.getLatLon(), 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) { applyFilter(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, 140)); 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() { new Thread(new Runnable() { public void run() { downloadEarthquakes(); } }, "Earthquakes download").start(); } private void downloadEarthquakes() { // Disable download button and update status label if (this.downloadButton != null) this.downloadButton.setEnabled(false); if (this.statusLabel != null) this.statusLabel.setText("Downloading earthquakes..."); // Reset updater last event date lastUpdaterEvent = new Date(); // Download and parse RenderableLayer newLayer = (RenderableLayer)buildEarthquakeLayer(); // Update layer list and status if (newLayer.getRenderables().iterator().hasNext()) { 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.lastUpdate = new Date(); if (this.statusLabel != null) this.statusLabel.setText(this.lastUpdate.toString()); applyFilter(Double.parseDouble((String) magnitudeCombo.getSelectedItem())); } else { if (this.statusLabel != null) this.statusLabel.setText("Download failed!"); } if (this.downloadButton != null) this.downloadButton.setEnabled(true); } private Layer buildEarthquakeLayer() { final String USGS_EARTHQUAKES_M5_7DAYS = "http://earthquake.usgs.gov/eqcenter/catalogs/7day-M5.xml"; final String USGS_EARTHQUAKES_M25_7DAYS = "http://earthquake.usgs.gov/eqcenter/catalogs/7day-M2.5.xml"; RenderableLayer layer = new RenderableLayer(); layer.setName("Earthquakes"); try { // Get rss feed URL url = new URL(USGS_EARTHQUAKES_M25_7DAYS); ByteBuffer bb = WWIO.readURLContentToBuffer(url); // Parse feed and add renderables to layer parseFile(layer, WWIO.saveBufferToTempFile(bb, ".xml")); } catch(MalformedURLException e) {} catch(IOException e) {} return layer; } private void parseFile(RenderableLayer layer, File file) { if (file == null) { String message = Logging.getMessage("nullValue.FileIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } try { DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance(); docBuilderFactory.setNamespaceAware(false); DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder(); Document doc = docBuilder.parse(file); parseDoc(layer, doc); } catch (ParserConfigurationException e) { String message = Logging.getMessage("GeoRSS.ParserConfigurationException"); Logging.logger().log(Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } catch (IOException e) { String message = Logging.getMessage("GeoRSS.IOExceptionParsing", file.getPath()); Logging.logger().log(Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } catch (SAXException e) { String message = Logging.getMessage("GeoRSS.IOExceptionParsing", file.getPath()); Logging.logger().log(Level.SEVERE, message, e); throw new WWRuntimeException(message, e); } } private void parseDoc(RenderableLayer layer, Document xmlDoc) { if (xmlDoc == null) { String message = Logging.getMessage("nullValue.DocumentIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } // Entries NodeList nodes = xmlDoc.getElementsByTagName("entry"); if (nodes != null && nodes.getLength() > 0) { this.latestEq = null; for (int i = 0; i < nodes.getLength(); i++) { Node entry = nodes.item(i); Earthquake eq = new Earthquake(entry); addEarthquake(layer, eq); } } } private AnnotationAttributes eqAttributes; private BufferedImage eqIcons[] = { PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.RED), PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.ORANGE), PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.YELLOW), PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.GREEN), PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.BLUE), PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.GRAY), PatternFactory.createPattern(PatternFactory.PATTERN_CIRCLE, .8f, Color.BLACK), }; private Color eqColors[] = { Color.RED, Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.GRAY, Color.BLACK, }; private void addEarthquake(RenderableLayer layer, Earthquake earthquake) { if (eqAttributes == null) { // Init default attributes for all eq eqAttributes = new AnnotationAttributes(); eqAttributes.setLeader(FrameFactory.LEADER_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 ea = new EqAnnotation(earthquake, eqAttributes); int days = 6; if (earthquake.date != null) { // Compute days since Date now = new Date(); days = (int) ((now.getTime() - earthquake.date.getTime()) / javax.management.timer.Timer.ONE_DAY); // Update latestEq if (this.latestEq != null) { if (this.latestEq.earthquake.date.getTime() < earthquake.date.getTime()) this.latestEq = ea; } else this.latestEq = ea; } ea.getAttributes().setImageSource(eqIcons[Math.min(days, eqIcons.length - 1)]); ea.getAttributes().setTextColor(eqColors[Math.min(days, eqColors.length - 1)]); ea.getAttributes().setScale(earthquake.magnitude / 10); layer.addRenderable(ea); } private static Node findChildByName(Node parent, String localName) { NodeList children = parent.getChildNodes(); if (children == null || children.getLength() < 1) return null; for (int i = 0; i < children.getLength(); i++) { String ln = children.item(i).getNodeName(); if (ln != null && ln.equals(localName)) return children.item(i); } return null; } private void applyFilter(double minMagnitude) { this.latestEq = null; setBlinker(null); setLatestLabel(null); Iterable<Renderable> renderables = eqLayer.getRenderables(); for (Renderable r : renderables) { if (r instanceof EqAnnotation) { EqAnnotation ea = (EqAnnotation)r; ea.getAttributes().setVisible(ea.earthquake.magnitude >= minMagnitude); if (ea.getAttributes().isVisible()) { if (this.latestEq != null) { if (this.latestEq.earthquake.date != null && ea.earthquake.date != null) if (this.latestEq.earthquake.date.getTime() < ea.earthquake.date.getTime()) this.latestEq = ea; } else this.latestEq = ea; } } } setBlinker(this.latestEq); setLatestLabel(this.latestEq); this.getWwd().repaint(); } private class Earthquake { public String title; public String summary; public Position position; public double elevation; public Date date; public double magnitude; public String link; public Earthquake(Node entry) { Node node = findChildByName(entry, "title"); if(node != null) { this.title = node.getTextContent(); this.magnitude = Double.parseDouble(title.split(",")[0].substring(2)); } node = findChildByName(entry, "georss:point"); if (node != null) { String pointString = node.getTextContent(); String[] coord = pointString.split(" "); this.position = Position.fromDegrees(Double.parseDouble(coord[0]), Double.parseDouble(coord[1]), 0); } node = findChildByName(entry, "georss:elev"); if (node != null) this.elevation = Double.parseDouble(node.getTextContent()); node = findChildByName(entry, "summary"); if (node != null) this.summary = node.getTextContent(); node = findChildByName(entry, "link"); if (node != null) this.link = node.getAttributes().getNamedItem("href").getTextContent(); node = findChildByName(entry, "updated"); if (node != null) { DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); df.setCalendar(Calendar.getInstance(TimeZone.getTimeZone("UTC"))); try { this.date = df.parse(node.getTextContent().replaceAll("[TZ]", " ").trim()); } catch (Exception e) {} } } } private class EqAnnotation extends GlobeAnnotation { public Earthquake earthquake; public EqAnnotation(Earthquake earthquake, AnnotationAttributes defaults) { super ("", earthquake.position, defaults); this.earthquake = earthquake; } // Override annotation drawing for a simple circle private DoubleBuffer shapeBuffer; public void drawAnnotation(DrawContext dc, Point screenPoint, double drawScale, double drawAlpha, Position pickPosition) { // Adjust scale and opacity this.scaleFactor = attributes.getScale() * drawScale; this.alphaFactor = attributes.getOpacity() * drawAlpha * .6; // Highlight if (attributes.isHighlighted()) { // Factor in highlight scale and remove transparency if highlighted this.scaleFactor *= attributes.getHighlightScale(); this.alphaFactor = 1; } dc.getGL().glScaled(this.scaleFactor, this.scaleFactor, 1d); // Draw colored circle around screen point - use annotation's text color if (dc.isPickingMode()) { // Picking Color color = dc.getUniquePickColor(); int colorCode = color.getRGB(); getPickSupport().addPickableObject(colorCode, this.delegateOwner != null ? this.delegateOwner : this, pickPosition, false); dc.getGL().glColor3ub((byte) color.getRed(), (byte) color.getGreen(), (byte) color.getBlue()); } else { // Set background color Color backColor = getAttributes().getTextColor(); setDrawColor(dc, new Color(backColor.getRed(), backColor.getGreen(), backColor.getBlue(), (int) ((float) backColor.getAlpha() * this.alphaFactor))); } // Draw 32x32 shape from its bottom left corner if (this.shapeBuffer == null) this.shapeBuffer = FrameFactory.createShapeBuffer(FrameFactory.SHAPE_ELLIPSE, 32, 32, 0); dc.getGL().glTranslated(-16, -16, 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) { //System.out.println("Blink: " + annotation.earthquake.title + " " + step + " " + initialScale + " " + initialOpacity); 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().repaint(); } }); 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 instanciation 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); } }