/*
* $Id: TAOMap.java,v 1.15 2001/02/06 00:14:36 dwd Exp $
*
* This software is provided by NOAA for full, free and open release. It is
* understood by the recipient/user that NOAA assumes no liability for any
* errors contained in the code. Although this software is released without
* conditions or restrictions in its use, it is expected that appropriate
* credit be given to its author and to the National Oceanic and Atmospheric
* Administration should the software be included by the recipient as an
* element in other product development.
*/
package gov.noaa.pmel.sgt.demo;
import gov.noaa.pmel.sgt.JPane;
import gov.noaa.pmel.sgt.Layer;
import gov.noaa.pmel.sgt.PlainAxis;
import gov.noaa.pmel.sgt.LinearTransform;
import gov.noaa.pmel.sgt.Graph;
import gov.noaa.pmel.sgt.CartesianGraph;
import gov.noaa.pmel.sgt.SGLabel;
import gov.noaa.pmel.sgt.PointAttribute;
import gov.noaa.pmel.sgt.PointCollectionKey;
import gov.noaa.pmel.sgt.PointCartesianRenderer;
import gov.noaa.pmel.sgt.StackedLayout;
import gov.noaa.pmel.sgt.Axis;
import gov.noaa.pmel.sgt.swing.JClassTree;
import gov.noaa.pmel.sgt.Logo;
import gov.noaa.pmel.sgt.swing.ValueIcon;
import gov.noaa.pmel.sgt.swing.ValueIconFormat;
import gov.noaa.pmel.sgt.swing.prop.PointAttributeDialog;
import gov.noaa.pmel.sgt.dm.Collection;
import gov.noaa.pmel.sgt.dm.SimplePoint;
import gov.noaa.pmel.sgt.dm.SGTLine;
import gov.noaa.pmel.sgt.LineAttribute;
import gov.noaa.pmel.sgt.dm.SimpleLine;
import gov.noaa.pmel.sgt.dm.SGTMetaData;
import gov.noaa.pmel.util.Point2D;
import gov.noaa.pmel.util.Range2D;
import gov.noaa.pmel.util.Dimension2D;
import java.awt.*;
import java.awt.event.ActionEvent;
import javax.swing.*;
import java.io.*;
import java.text.DecimalFormat;
import java.util.StringTokenizer;
import java.io.FileReader;
import java.io.BufferedReader;
import java.io.IOException;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeEvent;
/**
* Example demonstrating the creation of a graph that displays point
* data from files and includes a coastline. TAOMap
* constructs the plot from basic sgt
objects and uses
* the JPane
PropertyChangeEvent
s to notify
* TAOMap
of zoom requests and object selections.
*
* @author Donald Denbo
* @version $Revision: 1.15 $, $Date: 2001/02/06 00:14:36 $
* @since 2.0
*/
public class TAOMap extends JApplet implements PropertyChangeListener {
JButton tree_;
JButton space_ = null;
JButton reset_;
JPane mainPane_;
SGTLine coastLine_ = null;
CartesianGraph graph_;
LinearTransform xt_, yt_;
Layer layer_;
PlainAxis xbot_;
PlainAxis yleft_;
Range2D xrange_, yrange_;
PointAttributeDialog pAttrDialog_ = null;
public void init() {
/*
* init() is called when TAOMap is run as an JApplet.
*/
getContentPane().setLayout(new BorderLayout(0,0));
setSize(553,438);
JPane graph = makeGraph();
graph.setBatch(true);
getContentPane().add(graph, "Center");
getContentPane().add(makeButtonPanel(false), "South");
addValueIcon();
graph.setBatch(false);
}
JPanel makeButtonPanel(boolean mark) {
/*
* Create the buttonPanel. Leave the "mark" button off when
* creating the panel for a JApplet. The mark button is used for
* debugging events.
*/
JPanel button = new JPanel();
button.setLayout(new FlowLayout());
/*
* Create button to open a JClassTree dialog
*/
tree_ = new JButton("Tree View");
MyAction myAction = new MyAction();
tree_.addActionListener(myAction);
button.add(tree_);
if(mark) {
/*
* Create the <> button
*/
space_ = new JButton("Add Mark");
space_.addActionListener(myAction);
button.add(space_);
}
/*
* Create the zoom reset button.
*/
reset_ = new JButton("Reset Zoom");
reset_.addActionListener(myAction);
button.add(reset_);
return button;
}
public static void main(String[] args) {
/*
* main(String[] args) is called when TAOMap is run as an
* application
*/
TAOMap pd = new TAOMap();
/*
* Create a JFrame to place TAOMap into.
*/
JFrame frame = new JFrame("TAO Mooring Map");
JPane graph;
JPanel button;
frame.getContentPane().setLayout(new BorderLayout());
/*
* Add listener to properly dispose of window when closed.
*/
frame.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent event) {
JFrame fr = (JFrame)event.getSource();
fr.setVisible(false);
fr.dispose();
System.exit(0);
}
});
frame.setSize(553,438);
graph = pd.makeGraph();
/*
* make buttonPanel with "mark" button.
*/
button = pd.makeButtonPanel(true);
/*
* set batch to true. Changes to graph will not cause updates of
* the display.
*/
graph.setBatch(true);
frame.getContentPane().add(graph, BorderLayout.CENTER);
frame.getContentPane().add(button, BorderLayout.SOUTH);
frame.pack();
frame.setVisible(true);
pd.addValueIcon();
/*
* set batch to false. Any changes to graph will now cause graph
* to redraw.
*/
graph.setBatch(false);
}
JPane makeGraph() {
/*
* This example creates a very simple plot from
* scratch (not using one of the gov.noaa.pmel.sgt.swing classes)
* to display a Collection of points.
*/
coastLine_ = getCoastLine("finerezcoast.bin", 50200);
/*
* Create a Pane, place in the center of the Applet
* and set the layout to be StackedLayout.
*/
mainPane_ = new JPane("Point Plot Demo", new Dimension(553,438));
mainPane_.setLayout(new StackedLayout());
mainPane_.setBackground(Color.white);
/*
* Read two point collections, TAO.dat and TRITON.dat. These
* files contain coordinate and labelling information.
*
*/
Collection TAO;
Collection TRITON;
TAO = readPointCollection("TAO.dat");
TRITON = readPointCollection("TRITON.dat");
/*
* Although Collection has methods to query the x and y range of
* the coordinates we set the range to produce a "nice" graph.
*/
xrange_ = new Range2D(130.0, 270., 20.0);
yrange_ = new Range2D(-10.0, 14.0, 2.0);
/*
* xsize, ysize are the width and height in physical units
* of the Layer graphics region.
*
* xstart, xend are the start and end points for the X axis
* ystart, yend are the start and end points for the Y axis
*/
double xsize = 4.0;
double xstart = 0.45;
double xend = 3.75;
double ysize = 3.0;
double ystart = 0.45;
double yend = 2.95;
/*
* Create the transforms that will be used for all layers
*/
xt_ = new LinearTransform(xstart, xend, xrange_.start, xrange_.end);
yt_ = new LinearTransform(ystart, yend, yrange_.start, yrange_.end);
if(coastLine_ != null) {
/*
* Create the layer that will hold the coastline and add to the
* JPane.
*/
Layer layer = new Layer("CoastLine", new Dimension2D(xsize, ysize));
mainPane_.add(layer);
/*
* Create the CartesianGraph, attach to the layer, set the
* transforms and add the coastline data.
*/
CartesianGraph cstgraph = new CartesianGraph("CoastLine Graph");
layer.setGraph(cstgraph);
cstgraph.setXTransform(xt_);
cstgraph.setYTransform(yt_);
LineAttribute lattr = new LineAttribute();
lattr.setColor(new Color(165, 42, 42));
cstgraph.setData(coastLine_, lattr);
/*
* Since the coastline is for the entire world we clip the
* display to the current axes.
*/
cstgraph.setClip(xrange_.start, xrange_.end,
yrange_.start, yrange_.end);
cstgraph.setClipping(true);
}
/*
* Create the first "data" layer. This object reference is made
* class wide for convience later.
*/
layer_ = new Layer("Layer 1", new Dimension2D(xsize, ysize));
mainPane_.add(layer_);
/*
* Create a CartesianGraph and set the transforms.
*/
graph_ = new CartesianGraph("Point Graph");
layer_.setGraph(graph_);
graph_.setXTransform(xt_);
graph_.setYTransform(yt_);
/*
* This Graph contain the plots axes.
* Create the bottom axis, set its range in user units
* and its origin. Add the axis to the graph.
*/
String xLabel = "Longitude";
xbot_ = new PlainAxis("Botton Axis");
xbot_.setRangeU(xrange_);
xbot_.setLocationU(new Point2D.Double(xrange_.start, yrange_.start));
Font xbfont = new Font("Helvetica", Font.ITALIC, 14);
xbot_.setLabelFont(xbfont);
SGLabel xtitle = new SGLabel("xaxis title", xLabel,
new Point2D.Double(0.0, 0.0));
Font xtfont = new Font("Helvetica", Font.PLAIN, 14);
xtitle.setFont(xtfont);
xtitle.setHeightP(0.2);
xbot_.setTitle(xtitle);
graph_.addXAxis(xbot_);
/*
* Create the left axis, set its range in user units
* and its origin. Add the axis to the graph.
*/
String yLabel = "Latitude";
yleft_ = new PlainAxis("Left Axis");
yleft_.setRangeU(yrange_);
yleft_.setLocationU(new Point2D.Double(xrange_.start, yrange_.start));
yleft_.setLabelFont(xbfont);
SGLabel ytitle = new SGLabel("yaxis title", yLabel,
new Point2D.Double(0.0, 0.0));
Font ytfont = new Font("Helvetica", Font.PLAIN, 14);
ytitle.setFont(ytfont);
ytitle.setHeightP(0.2);
yleft_.setTitle(ytitle);
graph_.addYAxis(yleft_);
/*
* create the point collection key
*/
PointCollectionKey key = new PointCollectionKey();
key.setId("Mooring Key");
key.setVAlign(PointCollectionKey.TOP);
key.setHAlign(PointCollectionKey.LEFT);
key.setBorderStyle(PointCollectionKey.PLAIN_LINE);
key.setLocationP(new Point2D.Double(xstart+0.1, yend));
layer_.addChild(key);
/*
* Create a PointAttribute for the display of the
* Collection of points. The points will have a color or rgb
* (200,0,255) with the label at the NE corner and in a dark red.
*/
PointAttribute pattr;
Color markColor = new Color(200, 0, 255);
pattr = new PointAttribute(44, markColor);
pattr.setLabelPosition(PointAttribute.NE);
Font pfont = new Font("Helvetica", Font.PLAIN, 12);
pattr.setLabelFont(pfont);
pattr.setLabelColor(Color.red.darker());
pattr.setLabelHeightP(0.08);
pattr.setDrawLabel(true);
/*
* Associate the attribute and the point Collection
* with the graph.
*/
graph_.setData(TAO, pattr);
SGLabel pointTitle = new SGLabel("TAO title",
"TAO Moorings",
new Point2D.Double(0.0, 0.0));
pointTitle.setHeightP(0.16);
key.addPointGraph((PointCartesianRenderer)graph_.getRenderer(),
pointTitle);
/*
* create second layer for second file
*/
Layer layer2 = new Layer("Layer 2", new Dimension2D(xsize, ysize));
mainPane_.add(layer2);
/*
* Create the graph and set the shared transforms. NOTE: this
* graph will not contain any axes.
*/
CartesianGraph graph2 = new CartesianGraph("Point Graph2");
layer2.setGraph(graph2);
graph2.setXTransform(xt_);
graph2.setYTransform(yt_);
PointAttribute pattr2;
pattr2 = new PointAttribute(13, markColor);
pattr2.setLabelPosition(PointAttribute.NE);
pattr2.setLabelFont(pfont);
pattr2.setLabelColor(Color.blue.darker());
pattr2.setLabelHeightP(0.08);
pattr2.setDrawLabel(true);
graph2.setData(TRITON, pattr2);
SGLabel pointTitle2 = new SGLabel("TRITON title",
"TRITON Moorings",
new Point2D.Double(0.0, 0.0));
pointTitle2.setHeightP(0.16);
key.addPointGraph((PointCartesianRenderer)graph2.getRenderer(),
pointTitle2);
mainPane_.addPropertyChangeListener(this);
return mainPane_;
}
private void addValueIcon() {
/*
* add a ValueIcon. The value icon is used to show the current
* value for the user coordinates.
*/
ValueIcon vi = new ValueIcon(getClass().getResource("query.gif"), "value");
layer_.addChild(vi);
vi.setId("local query");
try {
vi.setLocationU(new Point2D.Double(220.0, -5.0));
} catch (java.beans.PropertyVetoException e) {
e.printStackTrace();
}
vi.setVisible(true);
vi.setSelectable(true);
vi.setValueFormat(new GeoFormat());
}
void tree_actionPerformed(ActionEvent e) {
/*
* Create a JClassTree to display a graph of the sgt objects
*/
JClassTree ct = new JClassTree();
ct.setModal(false);
ct.setJPane(mainPane_);
ct.show();
}
class MyAction implements java.awt.event.ActionListener {
public void actionPerformed(ActionEvent event) {
Object obj = event.getSource();
if(obj == space_) {
System.out.println(" <>");
}
if(obj == tree_)
tree_actionPerformed(event);
if(obj == reset_)
reset_actionPerformed(event);
}
}
Collection readPointCollection(String file) {
/*
* method used to parse the station information.
* The file format is
* lat;lon;label
*/
SimplePoint sp = null;
BufferedReader in = null;
String line = null;
String title;
int lat, lon;
Collection pc = new Collection(file);
/*
* Get data file. The getResourceAsStream enables java to find
* the data inside of a jar file.
*/
InputStream is = getClass().getResourceAsStream(file);
/*
* Open reader and get first line
*/
try {
in = new BufferedReader(new InputStreamReader(is));
line = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
while(line != null) {
StringTokenizer st = new StringTokenizer(line, ";");
String token1 = st.nextToken();
String token2 = st.nextToken();
title = st.nextToken();
lat = Integer.parseInt(token1);
lon = Integer.parseInt(token2);
if(lon < 0) lon = lon + 360;
lon = lon%360;
/*
* Create new SimplePoint with coordinates and title and
* add to Collection.
*/
sp = new SimplePoint((double)lon, (double)lat, title);
pc.addElement(sp);
/*
* Read the next line
*/
try {
line = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
}
return pc;
}
SGTLine getCoastLine(String coastFileName, int bufsize) {
/*
* Read coastline file
*/
SimpleLine line;
if(coastFileName.length() == 0) return null;
double[] lat = new double[bufsize];
double[] lon = new double[bufsize];
int cpt = 0;
int cpt_save = 0;
int cut_count = 0;
InputStream is = getClass().getResourceAsStream(coastFileName);
try {
BufferedInputStream bis;
bis = new BufferedInputStream(is, 1000000);
DataInputStream inData = new DataInputStream(bis);
while (true) {
// get the number of entries
int numEntries = inData.readShort();
if (numEntries == -1)
break;
// get the lats and lons for this segment
cpt_save = cpt;
for (int i=0; i 0 && Math.abs(lon[cpt] - lon[cpt-1]) > 50.0) {
cut_count++;
}
cpt++;
}
lat[cpt] = Double.NaN;
lon[cpt] = Double.NaN;
cpt++;
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(cpt + " points read");
System.out.println(cut_count + " cuts added");
/*
* The coastline file is setup for +180/-180 range for
* longitudes. Since TAO and TRITON span the pacific we
* need to remap longitude to 0-360. Additional points need
* to be added so that lines don't wrap strangely.
*/
int length = cpt;
double[] latVal = new double[cpt + cut_count];
double[] lonVal = new double[cpt + cut_count];
cpt = 0;
for(int i=0; i < length; i++) {
if(i > 0 && Math.abs(lon[i] - lon[i-1]) > 50.0) {
latVal[cpt] = Double.NaN;
lonVal[cpt] = Double.NaN;
cpt++;
}
latVal[cpt] = lat[i];
lonVal[cpt] = lon[i];
cpt++;
}
line = new SimpleLine(lonVal, latVal, "CoastLine");
SGTMetaData yMeta = new SGTMetaData("Latitude", "degrees_N",
false, false);
SGTMetaData xMeta = new SGTMetaData("Longitude", "degrees_E",
false, true);
line.setXMetaData(xMeta);
line.setYMetaData(yMeta);
return line;
}
public void propertyChange(PropertyChangeEvent event) {
/*
* Listen for propery change events from JPane.
*/
String name = event.getPropertyName();
if(name.equals("zoomRectangle")) {
/*
* compute zoom rectangle in user units
*/
Range2D xr = new Range2D();
Range2D yr = new Range2D();
Rectangle zm = (Rectangle)event.getNewValue();
if(zm.width <= 1 || zm.height <= 1) return;
xr.start = graph_.getXPtoU(layer_.getXDtoP(zm.x));
xr.end = graph_.getXPtoU(layer_.getXDtoP(zm.x + zm.width));
if(xr.start > xr.end) {
double temp = xr.start;
xr.start = xr.end;
xr.end = temp;
}
yr.start = graph_.getYPtoU(layer_.getYDtoP(zm.y));
yr.end = graph_.getYPtoU(layer_.getYDtoP(zm.y + zm.height));
if(yr.start > yr.end) {
double temp = yr.start;
yr.start = yr.end;
yr.end = temp;
}
mainPane_.setBatch(true);
/*
* set range for transforms
*/
xt_.setRangeU(xr);
yt_.setRangeU(yr);
/*
* set range and origin for axes
*/
Point2D.Double orig = new Point2D.Double(xr.start, yr.start);
xbot_.setRangeU(xr);
xbot_.setLocationU(orig);
yleft_.setRangeU(yr);
yleft_.setLocationU(orig);
/*
* set clipping on all graphs
*/
Component[] comps = mainPane_.getComponents();
Layer ly;
for(int i=0; i < comps.length; i++) {
if(comps[i] instanceof Layer) {
ly = (Layer)comps[i];
((CartesianGraph)ly.getGraph()).setClip(xr.start, xr.end,
yr.start, yr.end);
}
}
mainPane_.setBatch(false);
} else if(name.equals("objectSelected")) {
/*
* An sgt object has been selected.
* If it is a PointCartesianRenderer that means the key has been
* selected and so open a dialog to modified the PointAttribute.
*/
if(event.getNewValue() instanceof PointCartesianRenderer) {
PointAttribute pattr =
((PointCartesianRenderer)event.getNewValue()).getPointAttribute();
if(pAttrDialog_ == null) {
pAttrDialog_ = new PointAttributeDialog();
}
pAttrDialog_.setPointAttribute(pattr, mainPane_);
pAttrDialog_.setVisible(true);
} else {
/*
* Print the name of the object selected.
*/
System.out.println("objectSelected = " + event.getNewValue());
}
}
}
void reset_actionPerformed(ActionEvent e) {
mainPane_.setBatch(true);
Layer ly;
/*
* clear clipping on all graphs but coast line.
*/
Component[] comps = mainPane_.getComponents();
for(int i=0; i < comps.length; i++) {
if(comps[i] instanceof Layer) {
ly = (Layer)comps[i];
if(ly.getId().equals("CoastLine")) {
((CartesianGraph)ly.getGraph()).setClip(xrange_.start,
xrange_.end,
yrange_.start,
yrange_.end);
} else {
((CartesianGraph)ly.getGraph()).setClipping(false);
}
}
}
/*
* reset range for transform
*/
xt_.setRangeU(xrange_);
yt_.setRangeU(yrange_);
/*
* reset range for axes and origin
*/
Point2D.Double orig = new Point2D.Double(xrange_.start,
yrange_.start);
xbot_.setRangeU(xrange_);
xbot_.setLocationU(orig);
yleft_.setRangeU(yrange_);
yleft_.setLocationU(orig);
mainPane_.setBatch(false);
}
class GeoFormat extends ValueIconFormat {
/*
* Create a specialized format for ValueIcon so that it handles
* longitude wrapping properly and hemisphere labelling.
*/
public GeoFormat() {
super("#####.##;#####.##W", "#####.##N;#####.##S");
xfrm_.setPositiveSuffix("E");
}
public String format(double x, double y) {
double xt = (x + 360.0) % 360.0;
if(x > 180.0) x = x - 360.0;
return "(" + xfrm_.format(x) + ", " + yfrm_.format(y) + ")";
}
}
}