//
// TrackManipulation.java
//
/*
VisAD system for interactive analysis and visualization of numerical
data. Copyright (C) 1996 - 2017 Bill Hibbard, Curtis Rueden, Tom
Rink, Dave Glowacki, Steve Emmerson, Tom Whittaker, Don Murray, and
Tommy Jasmin.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this library; if not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA
*/
package visad.bom;
import visad.*;
import visad.java3d.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.*;
import java.rmi.*;
/**
TrackManipulation is the VisAD class for direct
manipulation rendering of storm tracks under Java3D
*/
public class TrackManipulation extends Object {
private DisplayImplJ3D display;
private DataReference track1_ref;
private DataReference track2_ref;
private DataReference track3_ref;
private RealTupleType latlonshape = null;
private ScalarMap latmap = null;
private ScalarMap lonmap = null;
private CoordinateSystem coord = null;
private int latindex = -1;
private int lonindex = -1;
private int otherindex = -1;
private float othervalue = 0.0f;
private ScalarMap shapemap = null;
private ShapeControl shapecontrol = null;
private float x_size;
private float y_size;
private float angle;
private static int NE = 32;
private float[] x_ellipse = new float[NE + 1];
private float[] y_ellipse = new float[NE + 1];
/** (lat1, lon1) start of track
(lat2, lon2) end of track
d is a DisplayImplJ3D that has ScalarMaps of Latitude and
Longitude but is not linked yet to any DataReferences;
this constructor will add another ScalarMap to Shape and
link the DisplayImplJ3D d to three data objects, two by
direct manipulation */
public TrackManipulation(float lat1, float lon1, float lat2, float lon2,
DisplayImplJ3D d, float xs, float ys, float ang)
throws VisADException, RemoteException {
// construct RealTuple objects to be manipulated at ends of track
RealTupleType latlon =
new RealTupleType(RealType.Latitude, RealType.Longitude);
RealTuple track1 = new RealTuple(latlon, new double[] {lat1, lon1});
RealTuple track2 = new RealTuple(latlon, new double[] {lat2, lon2});
// construct RealTuple with RealType mapped to Shape that
// creates storm track depiction
RealType shape = RealType.getRealType("shape");
latlonshape =
new RealTupleType(RealType.Latitude, RealType.Longitude, shape);
RealTuple track3 = new RealTuple(latlonshape, new double[] {lat1, lon1, 0.0});
// construct DataReferences for these three RealTuples
track1_ref = new DataReferenceImpl("track1_ref");
track2_ref = new DataReferenceImpl("track2_ref");
track3_ref = new DataReferenceImpl("track3_ref");
track1_ref.setData(track1);
track2_ref.setData(track2);
track3_ref.setData(track3);
// copy reference to DisplayImplJ3D
display = d;
// compute basic ellipse shape according to constructor
// arguments
x_size = (float) Math.abs(xs);
y_size = (float) Math.abs(ys);
angle = ang;
float sa = (float) Math.sin(ang * Data.DEGREES_TO_RADIANS);
float ca = (float) Math.cos(ang * Data.DEGREES_TO_RADIANS);
for (int i=0; i<NE+1; i++) {
double b = 2.0 * Math.PI * i / NE;
float xe = (float) (x_size * Math.cos(b));
float ye = (float) (y_size * Math.sin(b));
x_ellipse[i] = ca * xe + sa * ye;
y_ellipse[i] = ca * ye - sa * xe;
}
// find ScalarMaps of Latitude and Longitude
Vector scalar_map_vector = display.getMapVector();
Enumeration en = scalar_map_vector.elements();
while (en.hasMoreElements()) {
ScalarMap map = (ScalarMap) en.nextElement();
if (RealType.Latitude.equals(map.getScalar())) {
latmap = map;
}
else if (RealType.Longitude.equals(map.getScalar())) {
lonmap = map;
}
}
if (latmap == null || lonmap == null) {
throw new DisplayException("Latitude and Longitude must be mapped " +
"in display");
}
// get information from latmap and lonmap needed to
// compute display locations from manipulated RealTuple
// values
DisplayRealType latreal = latmap.getDisplayScalar();
DisplayRealType lonreal = lonmap.getDisplayScalar();
DisplayTupleType lattuple = latreal.getTuple();
DisplayTupleType lontuple = lonreal.getTuple();
if (lattuple == null || !lattuple.equals(lontuple)) {
throw new DisplayException("Latitude and Longitude must be mapped " +
"to spatial in display(1)");
}
latindex = latreal.getTupleIndex();
lonindex = lonreal.getTupleIndex();
otherindex = 3 - (latindex + lonindex);
DisplayRealType othertype =
(DisplayRealType) lattuple.getComponent(otherindex);
othervalue = (float) othertype.getDefaultValue();
coord = lattuple.getCoordinateSystem();
if (!lattuple.equals(Display.DisplaySpatialCartesianTuple) &&
!(coord != null &&
coord.getReference().equals(Display.DisplaySpatialCartesianTuple))) {
throw new DisplayException("Latitude and Longitude must be mapped " +
"to spatial in display(2)");
}
// construct ScalarMap to Shape, with a single 'shape'
shapemap = new ScalarMap(shape, Display.Shape);
display.addMap(shapemap);
shapecontrol = (ShapeControl) shapemap.getControl();
shapecontrol.setShapeSet(new Integer1DSet(1));
// link RealTuples to display
display.addReferences(new DirectManipulationRendererJ3D(), track1_ref);
display.addReferences(new DirectManipulationRendererJ3D(), track2_ref);
display.addReference(track3_ref);
// construct CellImpl that computes storm track shape in
// response to user manipulation of track end points, and
// link it to those manipulable RealTuples
TrackGetter tracker = new TrackGetter();
tracker.addReference(track1_ref);
tracker.addReference(track2_ref);
}
// compute approximate radius of ellipse in direction (x, y)
private float getStep(float x, float y) {
double dist = 2.0 * (x_size + y_size);
float step = -1.0f;
for (int i=0; i<NE+1; i++) {
double d = Math.abs(x * y_ellipse[i] - y * x_ellipse[i]);
if (d < dist) {
dist = d;
step = (float) Math.sqrt(x_ellipse[i] * x_ellipse[i] +
y_ellipse[i] * y_ellipse[i]);
}
}
return step;
}
// maximum size of shape geometry
private static final int NUM = 4096;
/** construct a VisADLineArray in the shape of a storm track
with end points given in the values array */
private VisADLineArray makeTrack(float[][] values) {
float d, xd, yd;
float x, y, z, x0, y0, x3, y3, x4, y4, x5, y5;
float sscale = 0.75f * 0.15f;
// start of arrow is located at first manipulable RealTuple
x = 0.0f;
y = 0.0f;
z = values[2][0];
// head of arrow is located at second manipulable RealTuple
x5 = values[0][1] - values[0][0];
y5 = values[1][1] - values[1][0];
// get arrow vector and length
float xdir = x5 - x;
float ydir = y5 - y;
float dist = (float) Math.sqrt(xdir * xdir + ydir * ydir);
// normalize direction
x0 = xdir / dist;
y0 = ydir / dist;
// running count of geometry size
int nv = 0;
// allocate arrays for geometry
float[] vx = new float[NUM];
float[] vy = new float[NUM];
float[] vz = new float[NUM];
int lenv = vx.length;
// draw arrow shaft
vx[nv] = x;
vy[nv] = y;
vz[nv] = z;
nv++;
vx[nv] = x5;
vy[nv] = y5;
vz[nv] = z;
nv++;
// computation for arrow head
xd = sscale * x0;
yd = sscale * y0;
x3 = x5 - 0.3f * (xd - yd);
y3 = y5 - 0.3f * (yd + xd);
x4 = x5 - 0.3f * (xd + yd);
y4 = y5 - 0.3f * (yd - xd);
// draw arrow head
vx[nv] = x5;
vy[nv] = y5;
vz[nv] = z;
nv++;
vx[nv] = x3;
vy[nv] = y3;
vz[nv] = z;
nv++;
vx[nv] = x5;
vy[nv] = y5;
vz[nv] = z;
nv++;
vx[nv] = x4;
vy[nv] = y4;
vz[nv] = z;
nv++;
// compute number of ellipses and spacing between them
float step = getStep(xdir, ydir);
int nsteps = (int) (dist / step);
if (nsteps < 1) nsteps = 1;
int lim = (vx.length - nv) / (2 * NE);
if (nsteps < 1) nsteps = 1;
if (nsteps > lim) nsteps = lim;
float xstep = xdir / nsteps;
float ystep = ydir / nsteps;
// compute which ellipse points are 'outside' previous
// ellipse (and hence visible)
boolean[] outside = new boolean[NE + 1];
for (int i=0; i<NE+1; i++) {
float xs = x_ellipse[i] + xstep;
float ys = y_ellipse[i] + ystep;
float radius = getStep(xs, ys);
float len = (float) Math.sqrt(xs * xs + ys * ys);
outside[i] = (len > radius);
}
// construct geometry for visible points of one ellipse
float[] xe = new float[2 * NE];
float[] ye = new float[2 * NE];
int ne = 0;
for (int i=0; i<NE; i++) {
if (outside[i] && outside[i + 1]) {
xe[ne] = x_ellipse[i];
ye[ne] = y_ellipse[i];
ne++;
xe[ne] = x_ellipse[i+1];
ye[ne] = y_ellipse[i+1];
ne++;
}
}
// draw first ellipse
float xcenter = x;
float ycenter = y;
for (int i=0; i<NE; i++) {
vx[nv] = x_ellipse[i];
vy[nv] = y_ellipse[i];
vz[nv] = z;
nv++;
vx[nv] = x_ellipse[i+1];
vy[nv] = y_ellipse[i+1];
vz[nv] = z;
nv++;
}
// add sequence of ellipses to storm track geometry
for (int i=0; i<nsteps; i++) {
xcenter += xstep;
ycenter += ystep;
for (int j=0; j<ne; j++) {
vx[nv] = xcenter + xe[j];
vy[nv] = ycenter + ye[j];
vz[nv] = z;
nv++;
}
}
// construct and return VisADLineArray from geometry
VisADLineArray array = new VisADLineArray();
array.vertexCount = nv;
float[] coordinates = new float[3 * nv];
int m = 0;
for (int i=0; i<nv; i++) {
coordinates[m++] = vx[i];
coordinates[m++] = vy[i];
coordinates[m++] = vz[i];
}
array.coordinates = coordinates;
return array;
}
/** this CellImpl computes storm track shapes from RealTuples
at start and end of track */
class TrackGetter extends CellImpl {
public TrackGetter() {
}
public void doAction() throws VisADException, RemoteException {
// get start and end locations, first in lat and lon
float[] latvalues = new float[2];
float[] lonvalues = new float[2];
RealTuple tuple1 = (RealTuple) track1_ref.getData();
latvalues[0] = (float)
((Real) tuple1.getComponent(0)).getValue(RealType.Latitude.getDefaultUnit());
lonvalues[0] = (float)
((Real) tuple1.getComponent(1)).getValue(RealType.Longitude.getDefaultUnit());
float lat0 = latvalues[0];
float lon0 = lonvalues[0];
RealTuple tuple2 = (RealTuple) track2_ref.getData();
latvalues[1] = (float)
((Real) tuple2.getComponent(0)).getValue(RealType.Latitude.getDefaultUnit());
lonvalues[1] = (float)
((Real) tuple2.getComponent(1)).getValue(RealType.Longitude.getDefaultUnit());
// scale end locations to graphics coordiantes
latvalues = latmap.scaleValues(latvalues);
lonvalues = lonmap.scaleValues(lonvalues);
float[][] values = new float[3][2];
for (int k=0; k<2; k++) {
values[latindex][k] = latvalues[k];
values[lonindex][k] = lonvalues[k];
values[otherindex][k] = othervalue;
}
// if necessary, convert to Cartesian graphics coordinates
if (coord != null) {
values = coord.toReference(values);
}
// disable display so it doesn't update twice
display.disableAction();
// first, base storm track shape at location of first
// manipulable RealTuple
RealTuple track3 = new RealTuple(latlonshape, new double[] {lat0, lon0, 0.0});
track3_ref.setData(track3);
// second, update shape based on end points
VisADGeometryArray shape = makeTrack(values);
shapecontrol.setShape(0, shape);
// now let display update, just once
display.enableAction();
}
}
/** test TrackManipulation
optional command line arguments:
java visad.bom.TrackManipulation xsize ysize angle(degrees) */
public static void main(String args[])
throws VisADException, RemoteException {
// get command line arguments for ellipse shape
float[] fargs = {0.2f, 0.1f, 0.0f};
for (int i=0; i<args.length; i++) {
try {
fargs[i] = Float.parseFloat(args[i]);
}
catch (NumberFormatException exc) {
}
}
// construct Java3D display with lat and lon mappings
DisplayImplJ3D display =
new DisplayImplJ3D("display1", new TwoDDisplayRendererJ3D());
ScalarMap lonmap = new ScalarMap(RealType.Longitude, Display.XAxis);
display.addMap(lonmap);
lonmap.setRange(-10.0, 10.0);
ScalarMap latmap = new ScalarMap(RealType.Latitude, Display.YAxis);
display.addMap(latmap);
latmap.setRange(-10.0, 10.0);
// construct a TrackManipulation object that sets up manipulation
// of storm track shape
TrackManipulation track = new TrackManipulation(0.0f, 0.0f, 3.0f, 3.0f,
display, fargs[0], fargs[1], fargs[2]);
// create JFrame (i.e., a window) for display and slider
JFrame frame = new JFrame("test TrackManipulation");
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {System.exit(0);}
});
// create JPanel in JFrame
JPanel panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
panel.setAlignmentY(JPanel.TOP_ALIGNMENT);
panel.setAlignmentX(JPanel.LEFT_ALIGNMENT);
frame.getContentPane().add(panel);
// add display to JPanel
panel.add(display.getComponent());
// set size of JFrame and make it visible
frame.setSize(500, 500);
frame.setVisible(true);
}
}