//
// DataRenderer.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;
import java.util.*;
import java.rmi.*;
/**
DataRenderer is the VisAD abstract super-class for graphics rendering
algorithms. These transform Data objects into 3-D (or 2-D)
depictions in a Display window.<P>
DataRenderer is not Serializable and should not be copied
between JVMs.<P>
*/
public abstract class DataRenderer extends Object implements Cloneable {
/** DisplayImpl this is associated with */
private DisplayImpl display = null;
/** used to insert output into scene graph */
private DisplayRenderer displayRenderer = null;
/** links to Data to be renderer by this */
private transient DataDisplayLink[] Links = null;
/** flag from DataDisplayLink.prepareData() */
private boolean[] feasible; // it's a miracle if this is correct
private boolean[] is_null; // WLH 7 May 2001
/** flag to indicate that DataDisplayLink.prepareData() was invoked */
private boolean[] changed;
private boolean any_changed;
private boolean all_feasible;
private boolean any_transform_control;
/** a Vector of BadMappingException and UnimplementedException
Strings generated during the last invocation of doAction() */
private Vector exceptionVector = new Vector();
/** flag indicating whether to suppress Exceptions
generated during doAction() */
private boolean suppress_exceptions = false;
/** flag for visibility of Data depictions */
protected boolean enabled = true;
/** place to hold ProjectionControl Listeners created by this renderer */
private ArrayList<ControlListener> projCntrlListeners = new ArrayList<ControlListener>();
/** Depth buffer offset and factor for this DataRenderer */
private boolean hasPolygonOffset = false;
private float polygonOffset = 0f;
private float polygonOffsetFactor = 0f;
/**
* construct a DataRenderer
*/
public DataRenderer() {
Links = null;
display = null;
}
/**
* clear Vector of Exceptions generated during doAction()
*/
public void clearExceptions() {
exceptionVector.removeAllElements();
}
/**
* set a flag indicating whether to suppress Exceptions
* generated during doAction()
*/
public void suppressExceptions(boolean suppress) {
suppress_exceptions = suppress;
}
/**
* add a BadMappingException or UnimplementedException to
* Vector of Exceptions generated during doAction()
* @param error Exception to add
*/
public void addException(Exception error) {
if (display == null) return;
exceptionVector.addElement(error);
// System.out.println(error.getMessage());
}
/**
* there is no need to over-ride this method, but it may be invoked
* by DisplayRenderer; gets a clone of exceptionVector to avoid
* concurrent access by Display thread
* @return a Vector of Strings from the BadMappingExceptions
* and UnimplementedExceptions generated during the last invocation
* of this DataRenderer's doAction method;
*/
public Vector getExceptionVector() {
return (suppress_exceptions ? new Vector() : (Vector) exceptionVector.clone());
}
/**
* @return flag indicating whether depiction generation is feasible
* for all linked Data
*/
public boolean get_all_feasible() {
return all_feasible;
}
/**
* @return flag indicating whether any linked Data have changed
* since last invocation of prepareAction()
*/
public boolean get_any_changed() {
return any_changed;
}
/**
* @return flag indicating whether any Controls associated with
* ScalarMaps applying to any linked Data have changed and require
* re-transform
*/
public boolean get_any_transform_control() {
return any_transform_control;
}
/**
* set flag indicating whether depiction generation is feasible
* for all linked Data
* @param b value to set in flag
*/
public void set_all_feasible(boolean b) {
all_feasible = b;
}
/**
* set DataDisplayLinks for linked Data, and set associated DisplayImpl
* @param links array of DataDisplayLinks to set
* @param d associated DisplayImpl to set
* @throws VisADException a VisAD error occurred
*/
public abstract void setLinks(DataDisplayLink[] links, DisplayImpl d)
throws VisADException;
/**
* Sets the visibility of the data being rendered by this instance.
*
* @param on Whether or not to render the data.
*/
public void toggle(boolean on) {
enabled = on;
}
/**
* Returns the visibility of the data being rendered by this instance.
*
* @return Whether or not the data is being rendered.
*/
public boolean getEnabled() {
return enabled;
}
/**
* Returns the list of ProjectionControl listeners created by this.
* Provides a way to remove listeners from the Control that may only
* be needed per doTransform.
*
* @return
*/
public ArrayList<ControlListener> getProjectionControlListeners() {
return projCntrlListeners;
}
/**
* set DataDisplayLinks for linked Data, including constructing
* arrays of booleans associated with DataDisplayLinks; called by
* setLinks(DataDisplayLink[], DisplayImpl)
* @param links array of DataDisplayLinks to set
*/
public synchronized void setLinks(DataDisplayLink[] links) {
if (display == null) return;
if (links == null || links.length == 0) return;
Links = links;
feasible = new boolean[Links.length];
is_null = new boolean[Links.length];
changed = new boolean[Links.length];
for (int i=0; i<Links.length; i++) {
feasible[i] = false;
is_null[i] = true;
}
}
/**
* @return an array of DataDisplayLinks to Data objects to be rendered
* (Data objects are accessed by DataDisplayLink.getData())
*/
public DataDisplayLink[] getLinks() {
return Links;
}
/**
* @return DisplayImpl associated with this DataRenderer
*/
public DisplayImpl getDisplay() {
return display;
}
/**
* set DisplayImpl associated with this DataRenderer
* @param d DisplayImpl to set
*/
public void setDisplay(DisplayImpl d) {
display = d;
}
/**
* @return associated with this DataRenderer
*/
public DisplayRenderer getDisplayRenderer() {
return displayRenderer;
}
/**
* set DisplayRenderer associated with this DataRenderer
* @param r DisplayRenderer to set
*/
public void setDisplayRenderer(DisplayRenderer r) {
displayRenderer = r;
}
/**
* @return flag indicating whether there is any pending need
* for re-transform for this DataRenderer
*/
public boolean checkAction() {
if (display == null) return false;
for (int i=0; i<Links.length; i++) {
if (Links[i].checkTicks() || !feasible[i]) {
return true;
}
// check if this Data includes any changed Controls
Enumeration maps = Links[i].getSelectedMapVector().elements();
while(maps.hasMoreElements()) {
ScalarMap map = (ScalarMap) maps.nextElement();
if (map.checkTicks(this, Links[i])) {
return true;
}
}
}
return false;
}
/**
* check if re-transform is needed; if initialize is true then
* compute ranges for RealTypes and Animation sampling
* @param go flag indicating that re-transform is required for
* at least one DataRenderer linked to DisplayImpl
* @param initialize flag indicating that initialization (i.e.,
* auto-scaling) is required
* @param shadow DataShadow shared by prepareAction() method of
* all DataRenderers linked to DisplayImpl
* @return DataShadow containing ranges and animation sampling
* Set (return null if no need for initialization)
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public DataShadow prepareAction(boolean go, boolean initialize,
DataShadow shadow)
throws VisADException, RemoteException {
if (display == null) return null;
any_changed = false;
all_feasible = true;
any_transform_control = false;
for (int i=0; i<Links.length; i++) {
changed[i] = false;
DataReference ref = Links[i].getDataReference();
// test for changed Controls that require doTransform
boolean do_prepare = Links[i].checkTicks() || !feasible[i] || go;
if (feasible[i] && !do_prepare) {
// check if this Data includes any changed Controls
Enumeration maps = Links[i].getSelectedMapVector().elements();
while(maps.hasMoreElements()) {
ScalarMap map = (ScalarMap) maps.nextElement();
if (map.checkTicks(this, Links[i])) {
do_prepare = true;
}
}
}
/*
System.out.println("prepareAction " + display.getName() + " " +
Links[i].getThingReference().getName() +
" Links[" + i + "].checkTicks() = " + Links[i].checkTicks() +
" feasible[" + i + "] = " + feasible[i] + " go = " + go +
" do_prepare = " + do_prepare);
*/
if (do_prepare) {
// data has changed - need to re-display
changed[i] = true;
any_changed = true;
// create ShadowType for data, classify data for display
try {
feasible[i] = Links[i].prepareData();
is_null[i] = (Links[i].getData() == null); // WLH 7 May 2001
} catch (RemoteException re) {
if (visad.collab.CollabUtil.isDisconnectException(re)) {
getDisplay().connectionFailed(this, Links[i]);
removeLink(Links[i]);
i--;
continue;
}
throw re;
}
if (!feasible[i]) {
all_feasible = false;
clearBranch();
}
if (initialize && feasible[i]) {
// compute ranges of RealTypes and Animation sampling
ShadowType type = Links[i].getShadow().getAdaptedShadowType();
Data data;
try {
data = Links[i].getData();
} catch (RemoteException re) {
if (visad.collab.CollabUtil.isDisconnectException(re)) {
getDisplay().connectionFailed(this, Links[i]);
removeLink(Links[i]);
i--;
continue;
}
throw re;
}
shadow = computeRanges(data, type, shadow);
}
} // end if (Links[i].checkTicks() || !feasible[i] || go)
if (feasible[i]) {
// check if this Data includes any changed Controls
Enumeration maps = Links[i].getSelectedMapVector().elements();
while(maps.hasMoreElements()) {
ScalarMap map = (ScalarMap) maps.nextElement();
if (map.checkTicks(this, Links[i])) {
any_transform_control = true;
}
}
} // end if (feasible[i])
} // end for (int i=0; i<Links.length; i++)
/*
System.out.println("any_changed = " + any_changed +
" all_feasible = " + all_feasible +
" any_transform_control = " + any_transform_control);
*/
return shadow;
}
/**
* Compute ranges of values for each RealType in
* DisplayImpl.RealTypeVector.
* @param data Data object in which to compute ranges of RealType values
* @param type ShadowType generated for MathType of data
* @param shadow DataShadow instance whose contained double[][]
* array and animation sampling Set are modified
* according to RealType values in data, and used
* as return value
* @return DataShadow instance containing double[][] array
* of RealType ranges, and an animation sampling Set
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public DataShadow computeRanges(Data data, ShadowType type, DataShadow shadow)
throws VisADException, RemoteException {
if (display == null) return null;
if (shadow == null) {
shadow =
data.computeRanges(type, display.getScalarCount());
}
else {
shadow = data.computeRanges(type, shadow);
}
return shadow;
}
/**
* clear part of Display scene graph generated by this DataRenderer
*/
public abstract void clearBranch();
/**
* transform linked Data objects into a scene graph depiction,
* if any Data object values have changed or relevant Controls
* have changed; DataRenderers that assume the default
* implementation of DisplayImpl.doAction can determine
* whether re-transform is needed by:
* (get_all_feasible() &&
* (get_any_changed() || get_any_transform_control()))
* these flags are computed by the default DataRenderer
* implementation of prepareAction()
* @return flag indicating if the transform was done successfully
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract boolean doAction() throws VisADException, RemoteException;
/**
* @return flag indicating whether initialization (i.e.,
* auto-scale) is needed on next re-transform
*/
public boolean getBadScale(boolean anyBadMap) {
if (display == null) return false;
boolean badScale = false;
for (int i=0; i<Links.length; i++) {
if (!feasible[i] && (anyBadMap || !is_null[i])) {
/*
try {
System.out.println("getBadScale not feasible " +
Links[i].getThingReference().getName());
}
catch (VisADException e) {}
catch (RemoteException e) {}
*/
return true;
}
Enumeration maps = Links[i].getSelectedMapVector().elements();
while(maps.hasMoreElements()) {
ScalarMap map = (ScalarMap) maps.nextElement();
badScale |= map.badRange();
/*
if (map.badRange()) {
System.out.println("getBadScale: " + map.getScalar().getName() + " -> " +
map.getDisplayScalar().getName());
}
*/
}
}
// System.out.println("getBadScale return " + badScale);
return badScale;
}
/**
* clear any scene graph created by this DataRenderer, and
* clear all instance variables
*/
public void clearScene() {
// test for display == null in methods
// remove any ProjectionControl listeners this renderer may have added
// to the display.
ProjectionControl pCntrl = display.getProjectionControl();
Iterator<ControlListener> iter = projCntrlListeners.iterator();
while (iter.hasNext()) {
pCntrl.removeControlListener(iter.next());
}
display = null;
displayRenderer = null;
Links = null;
exceptionVector.removeAllElements();
projCntrlListeners.clear();
// clear flow rendering and direct manipulation variables
shadow_data_out = null;
data_out = null;
data_units_out = null;
shadow_data_in = null;
data_in = null;
data_units_in = null;
data_coord_in = null;
sdo_maps = null;
sdi_maps = null;
rvts = new RealVectorType[] {null, null};
display_coordinate_system = null;
spatial_tuple = null;
lat_map = null;
lon_map = null;
link = null;
ref = null;
type = null;
shadow = null;
directMap = new ScalarMap[] {null, null, null};
tuple = null;
}
/**
* clear all information associated with AnimationControls
* and ValueControls created by this DataRenderer
*/
public void clearAVControls() {
if (display == null) return;
Enumeration controls = display.getControls(AVControl.class).elements();
while (controls.hasMoreElements()) {
((AVControl )controls.nextElement()).clearSwitches(this);
}
// a convenient place to throw this in
ProjectionControl control = display.getProjectionControl();
control.clearSwitches(this);
// a convenient place to throw this in
lat_index = -1;
lon_index = -1;
}
/**
* factory method for constructing a subclass of ShadowType appropriate
* for the graphics API, that also adapts ShadowFunctionType;
* ShadowType trees are constructed that 'shadow' the MathType trees of
* Data to be depicted, via recursive calls to buildShadowType() methods
* of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
* to Shadow*Type constructors, then back to buildShadowType() methods;
* the recursive call chain is initiated by DataDisplayLink.prepareData()
* calls to buildShadowType() methods of MathType sub-classes;
* @param type FunctionType that returned ShadowType will shadow
* @param link DataDisplayLink linking Data to be depicted
* @param parent parent in ShadowType tree structure
* @return constructed ShadowType
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract ShadowType makeShadowFunctionType(
FunctionType type, DataDisplayLink link, ShadowType parent)
throws VisADException, RemoteException;
/**
* factory for constructing a subclass of ShadowType appropriate
* for the graphics API, that also adapts ShadowRealTupleType;
* ShadowType trees are constructed that 'shadow' the MathType trees of
* Data to be depicted, via recursive calls to buildShadowType() methods
* of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
* to Shadow*Type constructors, then back to buildShadowType() methods;
* the recursive call chain is initiated by DataDisplayLink.prepareData()
* calls to buildShadowType() methods of MathType sub-classes;
* @param type FunctionType that returned ShadowType will shadow
* @param link DataDisplayLink linking Data to be depicted
* @param parent parent in ShadowType tree structure
* @return constructed ShadowType
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract ShadowType makeShadowRealTupleType(
RealTupleType type, DataDisplayLink link, ShadowType parent)
throws VisADException, RemoteException;
/**
* factory for constructing a subclass of ShadowType appropriate
* for the graphics API, that also adapts ShadowRealType;
* ShadowType trees are constructed that 'shadow' the MathType trees of
* Data to be depicted, via recursive calls to buildShadowType() methods
* of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
* to Shadow*Type constructors, then back to buildShadowType() methods;
* the recursive call chain is initiated by DataDisplayLink.prepareData()
* calls to buildShadowType() methods of MathType sub-classes;
* @param type FunctionType that returned ShadowType will shadow
* @param link DataDisplayLink linking Data to be depicted
* @param parent parent in ShadowType tree structure
* @return constructed ShadowType
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract ShadowType makeShadowRealType(
RealType type, DataDisplayLink link, ShadowType parent)
throws VisADException, RemoteException;
/**
* factory for constructing a subclass of ShadowType appropriate
* for the graphics API, that also adapts ShadowSetType;
* ShadowType trees are constructed that 'shadow' the MathType trees of
* Data to be depicted, via recursive calls to buildShadowType() methods
* of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
* to Shadow*Type constructors, then back to buildShadowType() methods;
* the recursive call chain is initiated by DataDisplayLink.prepareData()
* calls to buildShadowType() methods of MathType sub-classes;
* @param type FunctionType that returned ShadowType will shadow
* @param link DataDisplayLink linking Data to be depicted
* @param parent parent in ShadowType tree structure
* @return constructed ShadowType
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract ShadowType makeShadowSetType(
SetType type, DataDisplayLink link, ShadowType parent)
throws VisADException, RemoteException;
/**
* factory for constructing a subclass of ShadowType appropriate
* for the graphics API, that also adapts ShadowTextType;
* ShadowType trees are constructed that 'shadow' the MathType trees of
* Data to be depicted, via recursive calls to buildShadowType() methods
* of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
* to Shadow*Type constructors, then back to buildShadowType() methods;
* the recursive call chain is initiated by DataDisplayLink.prepareData()
* calls to buildShadowType() methods of MathType sub-classes;
* @param type FunctionType that returned ShadowType will shadow
* @param link DataDisplayLink linking Data to be depicted
* @param parent parent in ShadowType tree structure
* @return constructed ShadowType
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract ShadowType makeShadowTextType(
TextType type, DataDisplayLink link, ShadowType parent)
throws VisADException, RemoteException;
/**
* factory for constructing a subclass of ShadowType appropriate
* for the graphics API, that also adapts ShadowTupleType;
* ShadowType trees are constructed that 'shadow' the MathType trees of
* Data to be depicted, via recursive calls to buildShadowType() methods
* of MathType sub-classes, to DataRenderer.makeShadow*Type() methods,
* to Shadow*Type constructors, then back to buildShadowType() methods;
* the recursive call chain is initiated by DataDisplayLink.prepareData()
* calls to buildShadowType() methods of MathType sub-classes;
* @param type FunctionType that returned ShadowType will shadow
* @param link DataDisplayLink linking Data to be depicted
* @param parent parent in ShadowType tree structure
* @return constructed ShadowType
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public abstract ShadowType makeShadowTupleType(
TupleType type, DataDisplayLink link, ShadowType parent)
throws VisADException, RemoteException;
/**
* DataRenderer-specific decision about which Controls require
* re-transform; may be over-ridden by DataRenderer sub-classes;
* this decision may use some values computed by link.prepareData()
* @param control Control being judged whether it needs re-transform
* @param link DataDisplayLink possibly involved in decision
* @return flag indicating whether re-transform is needed
*/
public boolean isTransformControl(Control control, DataDisplayLink link) {
if (display == null) return false;
if (control instanceof ProjectionControl ||
control instanceof ToggleControl) {
return false;
}
/* WLH 1 Nov 97 - temporary hack -
RangeControl changes always require Transform
ValueControl and AnimationControl never do
if (control instanceof AnimationControl ||
control instanceof ValueControl ||
control instanceof RangeControl) {
return link.isTransform[control.getIndex()];
*/
if (control instanceof AnimationControl ||
control instanceof ValueControl) {
return false;
}
return true;
}
/**
* used by ShadowFunctionOrSetType for transform time-out hack
* @return single DataDisplayLink (over-ridden by sub-classes)
*/
public DataDisplayLink getLink() {
return null;
}
/**
* @return flag indicating whether texture mapping is legal
* for this DataRenderer
*/
public boolean isLegalTextureMap() {
return true;
}
/* ********************** */
/* flow rendering stuff */
/* ********************** */
// value array (display_values) indices
// ((ScalarMap) MapVector.elementAt(valueToMap[index]))
// can get these indices through shadow_data_out or shadow_data_in
/** true if lat and lon in data_in & shadow_data_in is allSpatial
or if lat and lon in data_in & lat_lon_in_by_coord */
boolean lat_lon_in = false;
/** true if lat_lon_in and shadow_data_out is allSpatial, i.e.,
map from lat, lon to display is through data CoordinateSystem */
boolean lat_lon_in_by_coord = false;
/** true if lat and lon in data_out & shadow_data_out is allSpatial */
boolean lat_lon_out = false;
/** true if lat_lon_out and shadow_data_in is allSpatial, i.e.,
map from lat, lon to display is inverse via data CoordinateSystem */
boolean lat_lon_out_by_coord = false;
/** earth dimension, either 2, 3 or -1 (for none) */
int lat_lon_dimension = -1;
/** Shadow of reference RealTupleType in Data */
ShadowRealTupleType shadow_data_out = null;
/** reference RealTupleType in Data */
RealTupleType data_out = null;
/** Units of reference RealTupleType in Data */
Unit[] data_units_out = null;
// CoordinateSystem data_coord_out is always null
/** Shadow of RealTupleType with reference in Data */
ShadowRealTupleType shadow_data_in = null;
/** RealTupleType with reference in Data */
RealTupleType data_in = null;
/** Units of RealTupleType with reference in Data */
Unit[] data_units_in = null;
/** CoordinateSystems relating data_in to data_out,
usually just one, but may be one per point */
CoordinateSystem[] data_coord_in = null;
/** spatial ScalarMaps for allSpatial shadow_data_out */
ScalarMap[] sdo_maps = null;
/** spatial ScalarMaps for allSpatial shadow_data_in */
ScalarMap[] sdi_maps = null;
/** indices in DisplayTupleType of DisplayRealTypes in sdo_maps */
int[] sdo_spatial_index = null;
/** indices in DisplayTupleType of DisplayRealTypes in sdi_maps */
int[] sdi_spatial_index = null;
/** index of RealType.Latitude
if lat_lon_in then index in data_in
if lat_lon_out then index in data_out
if lat_lon_spatial then values index */
int lat_index = -1;
/** index of RealType.Longitude
if lat_lon_in then index in data_in
if lat_lon_out then index in data_out
if lat_lon_spatial then values indices */
int lon_index = -1;
/** other index if lat & lon in a RealTupleType of length 3,
otherwise -1 */
int other_index = -1;
/** true if other_index Units convertable to meter */
boolean other_meters = false;
/** RealVectorTypes (extends RealTupleType) mapped to flow1 and
flow2, to be tested for being instances of EarthVectorType;
values will be either data_in, data_out or null */
RealVectorType[] rvts = {null, null};
/**
* @param index 0 or 1 for flow1 and flow2
* @return RealVectorType (extends RealTupleType) mapped to flow1 or
* flow2 (values will be either data_in, data_out or null)
*/
public RealVectorType getRealVectorTypes(int index) {
if (index == 0 || index == 1) return rvts[index];
else return null;
}
/**
* @return indices of RealType.Latitude and RealType.Longitude
* in data_in, data_out, or just spatial value array
*/
public int[] getLatLonIndices() {
return new int[] {lat_index, lon_index};
}
/**
* @param indices indices of RealType.Latitude and RealType.Longitude
* in data_in, data_out, or just spatial value array
*/
public void setLatLonIndices(int[] indices) {
lat_index = indices[0];
lon_index = indices[1];
}
/**
* @return earth dimension, either 2, 3 or -1 (for none)
*/
public int getEarthDimension() {
return lat_lon_dimension;
}
/**
* @return Units of earth coordinates used in Data
*/
public Unit[] getEarthUnits() {
if (display == null) return null;
Unit[] units = null;
if (lat_lon_in) {
units = data_units_in;
}
else if (lat_lon_out) {
units = data_units_out;
}
else if (lat_lon_spatial) {
units = new Unit[] {RealType.Latitude.getDefaultUnit(),
RealType.Longitude.getDefaultUnit()};
}
else {
units = null;
}
int lat = lat_index;
int lon = lon_index;
int other = other_index;
if (units == null) {
return null;
}
else if (units.length == 2) {
return new Unit[] {lat >= 0 ? units[lat] : null,
lon >= 0 ? units[lon] : null};
}
else if (units.length == 3) {
return new Unit[] {lat >= 0 ? units[lat] : null,
lon >= 0 ? units[lon] : null,
other >= 0 ? units[other] : null};
}
else {
return null;
}
}
/**
* @return maximum of display ranges of latitude and longitude
*/
public float getLatLonRange() {
if (display == null) return 1.0f;
double[] rlat = null;
double[] rlon = null;
int lat = lat_index;
int lon = lon_index;
if ((lat_lon_out && !lat_lon_out_by_coord) ||
(lat_lon_in && lat_lon_in_by_coord)) {
rlat = lat >= 0 ? sdo_maps[lat].getRange() :
new double[] {Double.NaN, Double.NaN};
rlon = lon >= 0 ? sdo_maps[lon].getRange() :
new double[] {Double.NaN, Double.NaN};
}
else if ((lat_lon_in && !lat_lon_in_by_coord) ||
(lat_lon_out && lat_lon_out_by_coord)) {
rlat = lat >= 0 ? sdi_maps[lat].getRange() :
new double[] {Double.NaN, Double.NaN};
rlon = lon >= 0 ? sdi_maps[lon].getRange() :
new double[] {Double.NaN, Double.NaN};
}
else if (lat_lon_spatial) {
rlat = lat_map.getRange();
rlon = lon_map.getRange();
}
else {
return 1.0f;
}
double dlat = Math.abs(rlat[1] - rlat[0]);
double dlon = Math.abs(rlon[1] - rlon[0]);
if (dlat != dlat) dlat = 1.0f;
if (dlon != dlon) dlon = 1.0f;
return (dlat > dlon) ? (float) dlat : (float) dlon;
}
/**
* convert (lat, lon) or (lat, lon, other) values to display (x, y, z)
* @param locs (lat, lon) or (lat, lon, other) coordinates
* @param vert vertical flow component (if non-null, used to
* adjust non-lat/lon spatial_locs
* @return display (x, y, z) coordinates
* @throws VisADException a VisAD error occurred
*/
public float[][] earthToSpatial(float[][] locs, float[] vert)
throws VisADException {
if (display == null) return null;
return earthToSpatial(locs, vert, null);
}
/**
* convert (lat, lon) or (lat, lon, other) values to display (x, y, z)
* @param locs (lat, lon) or (lat, lon, other) coordinates
* @param vert vertical flow component (if non-null, used to
* adjust non-lat/lon spatial_locs
* @param base_spatial_locs saved spatial_locs argument from
* spatialToEarth() call used to fill in any null members
* of return array
* @return display (x, y, z) coordinates
* @throws VisADException a VisAD error occurred
*/
public float[][] earthToSpatial(float[][] locs, float[] vert,
float[][] base_spatial_locs)
throws VisADException {
if (display == null) return null;
int lat = lat_index;
int lon = lon_index;
int other = other_index;
if (lat_index < 0 || lon_index < 0) return null;
int size = locs[0].length;
if (locs.length < lat_lon_dimension) {
// extend locs to lat_lon_dimension with zero fill
float[][] temp = locs;
locs = new float[lat_lon_dimension][];
for (int i=0; i<locs.length; i++) {
locs[i] = temp[i];
}
float[] zero = new float[size];
for (int j=0; j<size; j++) zero[j] = 0.0f;
for (int i=locs.length; i<lat_lon_dimension; i++) {
locs[i] = zero;
}
}
else if (locs.length > lat_lon_dimension) {
// truncate locs to lat_lon_dimension
float[][] temp = locs;
locs = new float[lat_lon_dimension][];
for (int i=0; i<lat_lon_dimension; i++) {
locs[i] = temp[i];
}
}
// permute (lat, lon, other) to data RealTupleType
float[][] tuple_locs = new float[lat_lon_dimension][];
float[][] spatial_locs = new float[3][];
tuple_locs[lat] = locs[0];
tuple_locs[lon] = locs[1];
if (lat_lon_dimension == 3) tuple_locs[other] = locs[2];
int vert_index = -1; // non-lat/lon index for lat_lon_dimension = 2
if (lat_lon_in) {
if (lat_lon_in_by_coord) {
// transform 'RealTupleType data_in' to 'RealTupleType data_out'
if (data_coord_in.length == 1) {
// one data_coord_in applies to all data points
tuple_locs = CoordinateSystem.transformCoordinates(data_out, null,
data_units_out, null, data_in, data_coord_in[0],
data_units_in, null, tuple_locs);
}
else {
// one data_coord_in per data point
float[][] temp = new float[lat_lon_dimension][1];
for (int j=0; j<size; j++) {
for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
temp = CoordinateSystem.transformCoordinates(data_out, null,
data_units_out, null, data_in, data_coord_in[j],
data_units_in, null, temp);
for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
}
}
// map data_out to spatial DisplayRealTypes
for (int i=0; i<lat_lon_dimension; i++) {
spatial_locs[sdo_spatial_index[i]] =
sdo_maps[i].scaleValues(tuple_locs[i]);
}
if (lat_lon_dimension == 2) {
vert_index = 3 - (sdo_spatial_index[0] + sdo_spatial_index[1]);
}
}
else {
// map data_in to spatial DisplayRealTypes
for (int i=0; i<lat_lon_dimension; i++) {
spatial_locs[sdi_spatial_index[i]] =
sdi_maps[i].scaleValues(tuple_locs[i]);
}
if (lat_lon_dimension == 2) {
vert_index = 3 - (sdi_spatial_index[0] + sdi_spatial_index[1]);
}
}
}
else if (lat_lon_out) {
if (lat_lon_out_by_coord) {
// transform 'RealTupleType data_out' to 'RealTupleType data_in'
if (data_coord_in.length == 1) {
// one data_coord_in applies to all data points
tuple_locs = CoordinateSystem.transformCoordinates(data_in,
data_coord_in[0], data_units_in, null, data_out,
null, data_units_out, null, tuple_locs);
}
else {
// one data_coord_in per data point
float[][] temp = new float[lat_lon_dimension][1];
for (int j=0; j<size; j++) {
for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
temp = CoordinateSystem.transformCoordinates(data_in,
data_coord_in[j], data_units_in, null, data_out,
null, data_units_out, null, temp);
for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
}
}
// map data_in to spatial DisplayRealTypes
for (int i=0; i<lat_lon_dimension; i++) {
spatial_locs[sdi_spatial_index[i]] =
sdi_maps[i].scaleValues(tuple_locs[i]);
}
if (lat_lon_dimension == 2) {
vert_index = 3 - (sdi_spatial_index[0] + sdi_spatial_index[1]);
}
}
else {
// map data_out to spatial DisplayRealTypes
for (int i=0; i<lat_lon_dimension; i++) {
spatial_locs[sdo_spatial_index[i]] =
sdo_maps[i].scaleValues(tuple_locs[i]);
}
if (lat_lon_dimension == 2) {
vert_index = 3 - (sdo_spatial_index[0] + sdo_spatial_index[1]);
}
}
}
else if (lat_lon_spatial) {
// map lat & lon, not in allSpatial RealTupleType, to
// spatial DisplayRealTypes
spatial_locs[lat_spatial_index] =
lat_map.scaleValues(tuple_locs[lat]);
spatial_locs[lon_spatial_index] =
lon_map.scaleValues(tuple_locs[lon]);
vert_index = 3 - (lat_spatial_index + lon_spatial_index);
}
else {
// should never happen
return null;
}
// WLH 9 Dec 99
// fill any empty spatial DisplayRealTypes with default values
for (int i=0; i<3; i++) {
if (spatial_locs[i] == null) {
if (base_spatial_locs != null && base_spatial_locs[i] != null) {
spatial_locs[i] = base_spatial_locs[i]; // copy not necessary
}
else {
spatial_locs[i] = new float[size];
float def = default_spatial_in[i]; // may be non-Cartesian
for (int j=0; j<size; j++) spatial_locs[i][j] = def;
}
}
}
// adjust non-lat/lon spatial_locs by vertical flow component
/* WLH 28 July 99
if (vert != null && vert_index > -1) {
for (int j=0; j<size; j++) spatial_locs[vert_index][j] += vert[j];
}
*/
if (vert != null && vert_index > -1 && spatial_locs[vert_index] != null) {
for (int j=0; j<size; j++) spatial_locs[vert_index][j] += vert[j];
}
if (display_coordinate_system != null) {
// transform non-Cartesian spatial DisplayRealTypes to Cartesian
if (spatial_locs != null && spatial_locs.length > 0 &&
spatial_locs[0] != null && spatial_locs[0].length > 0) {
// DRM 14 Apr 2003 - could do transform in place
//spatial_locs = display_coordinate_system.toReference(spatial_locs);
spatial_locs =
display_coordinate_system.toReference(Set.copyFloats(spatial_locs));
}
}
return spatial_locs;
}
/**
* convert display (x, y, z) to (lat, lon) or (lat, lon, other) values
* @param spatial_locs display (x, y, z) coordinates
* @return (lat, lon) or (lat, lon, other) coordinates
* @throws VisADException a VisAD error occurred
*/
public float[][] spatialToEarth(float[][] spatial_locs)
throws VisADException {
if (display == null) return null;
float[][] base_spatial_locs = new float[3][];
return spatialToEarth(spatial_locs, base_spatial_locs);
}
/**
* convert display (x, y, z) to (lat, lon) or (lat, lon, other) values
* @param spatial_locs display (x, y, z) coordinates
* @param base_spatial_locs float[3][] array used to return member
* arrays of spatial_locs argument
* @return (lat, lon) or (lat, lon, other) coordinates
* @throws VisADException a VisAD error occurred
*/
public float[][] spatialToEarth(float[][] spatial_locs,
float[][] base_spatial_locs)
throws VisADException {
if (display == null) return null;
int lat = lat_index;
int lon = lon_index;
int other = other_index;
if (lat_index < 0 || lon_index < 0) return null;
if (spatial_locs.length != 3) return null;
int size = 0;
for (int i=0; i<3; i++) {
if (spatial_locs[i] != null && spatial_locs[i].length > size) {
size = spatial_locs[i].length;
}
}
if (size == 0) return null;
// fill any empty spatial DisplayRealTypes with default values
for (int i=0; i<3; i++) {
if (spatial_locs[i] == null) {
spatial_locs[i] = new float[size];
// defaults for Cartesian spatial DisplayRealTypes = 0.0f
for (int j=0; j<size; j++) spatial_locs[i][j] = 0.0f;
}
}
if (display_coordinate_system != null) {
// transform Cartesian spatial DisplayRealTypes to non-Cartesian
// DRM 14 Apr 2003 - could do transform in place
//spatial_locs = display_coordinate_system.fromReference(spatial_locs);
spatial_locs =
display_coordinate_system.fromReference(Set.copyFloats(spatial_locs));
}
base_spatial_locs[0] = spatial_locs[0];
base_spatial_locs[1] = spatial_locs[1];
base_spatial_locs[2] = spatial_locs[2];
float[][] tuple_locs = new float[lat_lon_dimension][];
if (lat_lon_in) {
if (lat_lon_in_by_coord) {
// map spatial DisplayRealTypes to data_out
for (int i=0; i<lat_lon_dimension; i++) {
tuple_locs[i] =
sdo_maps[i].inverseScaleValues(spatial_locs[sdo_spatial_index[i]]);
}
// transform 'RealTupleType data_out' to 'RealTupleType data_in'
if (data_coord_in.length == 1) {
// one data_coord_in applies to all data points
tuple_locs = CoordinateSystem.transformCoordinates(data_in,
data_coord_in[0], data_units_in, null, data_out,
null, data_units_out, null, tuple_locs);
}
else {
// one data_coord_in per data point
float[][] temp = new float[lat_lon_dimension][1];
for (int j=0; j<size; j++) {
for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
temp = CoordinateSystem.transformCoordinates(data_in,
data_coord_in[j], data_units_in, null, data_out,
null, data_units_out, null, temp);
for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
}
}
}
else {
// map spatial DisplayRealTypes to data_in
for (int i=0; i<lat_lon_dimension; i++) {
tuple_locs[i] =
sdi_maps[i].inverseScaleValues(spatial_locs[sdi_spatial_index[i]]);
}
}
}
else if (lat_lon_out) {
if (lat_lon_out_by_coord) {
// map spatial DisplayRealTypes to data_in
for (int i=0; i<lat_lon_dimension; i++) {
tuple_locs[i] =
sdi_maps[i].inverseScaleValues(spatial_locs[sdi_spatial_index[i]]);
}
// transform 'RealTupleType data_in' to 'RealTupleType data_out'
if (data_coord_in.length == 1) {
// one data_coord_in applies to all data points
tuple_locs = CoordinateSystem.transformCoordinates(data_out, null,
data_units_out, null, data_in, data_coord_in[0],
data_units_in, null, tuple_locs);
}
else {
// one data_coord_in per data point
float[][] temp = new float[lat_lon_dimension][1];
for (int j=0; j<size; j++) {
for (int k=0; k<lat_lon_dimension; k++) temp[k][0] = tuple_locs[k][j];
temp = CoordinateSystem.transformCoordinates(data_out, null,
data_units_out, null, data_in, data_coord_in[j],
data_units_in, null, temp);
for (int k=0; k<lat_lon_dimension; k++) tuple_locs[k][j] = temp[k][0];
}
}
}
else {
// map spatial DisplayRealTypes to data_out
for (int i=0; i<lat_lon_dimension; i++) {
tuple_locs[i] =
sdo_maps[i].inverseScaleValues(spatial_locs[sdo_spatial_index[i]]);
}
}
}
else if (lat_lon_spatial) {
// map spatial DisplayRealTypes to lat & lon, not in
// allSpatial RealTupleType
tuple_locs[lat] =
lat_map.inverseScaleValues(spatial_locs[lat_spatial_index]);
tuple_locs[lon] =
lon_map.inverseScaleValues(spatial_locs[lon_spatial_index]);
}
else {
// should never happen
return null;
}
// permute data RealTupleType to (lat, lon, other)
float[][] locs = new float[lat_lon_dimension][];
locs[0] = tuple_locs[lat];
locs[1] = tuple_locs[lon];
if (lat_lon_dimension == 3) locs[2] = tuple_locs[other];
return locs;
}
/**
* save information about relation between earth and display
* spatial coordinates, IF the arguments do define the relation
* @param s_d_i candidate shadow_data_in
* (Shadow of RealTupleType with reference in Data)
* @param s_d_o candidate shadow_data_out
* (Shadow of reference RealTupleType in Data)
* @param d_o candidate data_out
* (reference RealTupleType in Data)
* @param d_u_o candidate data_units_out
* (Units of reference RealTupleType in Data)
* @param d_i candidate data_in
* (RealTupleType with reference in Data)
* @param d_c_i candidate data_coord_in
* (CoordinateSystems relating data_in to data_out)
* @param d_u_i candidate data_units_in
* (Units of RealTupleType with reference in Data)
* @throws VisADException a VisAD error occurred
*/
public void setEarthSpatialData(ShadowRealTupleType s_d_i,
ShadowRealTupleType s_d_o, RealTupleType d_o,
Unit[] d_u_o, RealTupleType d_i,
CoordinateSystem[] d_c_i, Unit[] d_u_i)
throws VisADException {
if (display == null) return;
// first check for VectorRealType components mapped to flow
// TO_DO: check here for flow mapped via CoordinateSystem
if (d_o != null && d_o instanceof RealVectorType) {
ScalarMap[] maps = new ScalarMap[3];
int k = getFlowMaps(s_d_o, maps);
if (k > -1) rvts[k] = (RealVectorType) d_o;
}
if (d_i != null && d_i instanceof RealVectorType) {
ScalarMap[] maps = new ScalarMap[3];
int k = getFlowMaps(s_d_i, maps);
if (k > -1) rvts[k] = (RealVectorType) d_i;
}
int lat_index_local = -1;
int lon_index_local = -1;
int other_index_local = -1;
int n = 0;
int m = 0;
if (d_i != null) {
n = d_i.getDimension();
for (int i=0; i<n; i++) {
RealType real = (RealType) d_i.getComponent(i);
if (RealType.Latitude.equals(real)) lat_index_local = i;
if (RealType.Longitude.equals(real)) lon_index_local = i;
}
}
if (lat_index_local > -1 && lon_index_local > -1 && (n == 2 || n == 3)) {
if (s_d_i != null && s_d_i.getAllSpatial() &&
!s_d_i.getSpatialReference()) {
lat_lon_in_by_coord = false;
sdi_spatial_index = new int[s_d_i.getDimension()];
sdi_maps = getSpatialMaps(s_d_i, sdi_spatial_index);
if (sdi_maps == null) {
throw new DisplayException("sdi_maps null A");
}
}
else if (s_d_o != null && s_d_o.getAllSpatial() &&
!s_d_o.getSpatialReference()) {
lat_lon_in_by_coord = true;
sdo_spatial_index = new int[s_d_o.getDimension()];
sdo_maps = getSpatialMaps(s_d_o, sdo_spatial_index);
if (sdo_maps == null) {
throw new DisplayException("sdo_maps null A");
}
}
else {
// do not update lat_index & lon_index
return;
}
lat_lon_in = true;
lat_lon_out = false;
lat_lon_out_by_coord = false;
lat_lon_spatial = false;
lat_lon_dimension = n;
if (n == 3) {
other_index_local = 3 - (lat_index_local + lon_index_local);
if (Unit.canConvert(d_u_i[other_index_local], CommonUnit.meter)) {
other_meters = true;
}
}
}
else { // if( !(lat & lon in di, di dimension = 2 or 3) )
lat_index_local = -1;
lon_index_local = -1;
other_index_local = -1;
if (d_o != null) {
m = d_o.getDimension();
for (int i=0; i<m; i++) {
RealType real = (RealType) d_o.getComponent(i);
if (RealType.Latitude.equals(real)) lat_index_local = i;
if (RealType.Longitude.equals(real)) lon_index_local = i;
}
}
if (lat_index_local < 0 || lon_index_local < 0 || !(m == 2 || m == 3)) {
// do not update lat_index & lon_index
return;
}
if (s_d_o != null && s_d_o.getAllSpatial() &&
!s_d_o.getSpatialReference()) {
lat_lon_out_by_coord = false;
sdo_spatial_index = new int[s_d_o.getDimension()];
sdo_maps = getSpatialMaps(s_d_o, sdo_spatial_index);
if (sdo_maps == null) {
throw new DisplayException("sdo_maps null B");
}
}
else if (s_d_i != null && s_d_i.getAllSpatial() &&
!s_d_i.getSpatialReference()) {
lat_lon_out_by_coord = true;
sdi_spatial_index = new int[s_d_i.getDimension()];
sdi_maps = getSpatialMaps(s_d_i, sdi_spatial_index);
if (sdi_maps == null) {
throw new DisplayException("sdi_maps null B");
}
}
else {
// do not update lat_index & lon_index
return;
}
lat_lon_out = true;
lat_lon_in = false;
lat_lon_in_by_coord = false;
lat_lon_spatial = false;
lat_lon_dimension = m;
if (m == 3) {
other_index_local = 3 - (lat_index_local + lon_index_local);
if (Unit.canConvert(d_u_i[other_index_local], CommonUnit.meter)) {
other_meters = true;
}
}
}
shadow_data_out = s_d_o;
data_out = d_o;
data_units_out = d_u_o;
shadow_data_in = s_d_i;
data_in = d_i;
data_units_in = d_u_i;
data_coord_in = d_c_i; // may be one per point
lat_index = lat_index_local;
lon_index = lon_index_local;
other_index = other_index_local;
return;
}
/**
* get information about spatial ScalarMaps of components in srt
* @param srt tuple of ShadowRealTypes
* @param spatial_index array with length equal to number of
* components in srt, used to return indices in
* DisplayTupleTypes of DisplayRealType mapped from
* RealTypes of corresponding components in srt
* @return array of spatial ScalarMaps for components in srt (or null)
*/
private ScalarMap[] getSpatialMaps(ShadowRealTupleType srt,
int[] spatial_index) {
if (display == null) return null;
int n = srt.getDimension();
ScalarMap[] maps = new ScalarMap[n];
for (int i=0; i<n; i++) {
ShadowRealType real = (ShadowRealType) srt.getComponent(i);
Enumeration ms = real.getSelectedMapVector().elements();
while (ms.hasMoreElements()) {
ScalarMap map = (ScalarMap) ms.nextElement();
DisplayRealType dreal = map.getDisplayScalar();
DisplayTupleType tuple = dreal.getTuple();
if (tuple != null &&
(tuple.equals(Display.DisplaySpatialCartesianTuple) ||
(tuple.getCoordinateSystem() != null &&
tuple.getCoordinateSystem().getReference().equals(
Display.DisplaySpatialCartesianTuple)))) {
maps[i] = map;
spatial_index[i] = dreal.getTupleIndex();
break;
}
}
if (maps[i] == null) {
return null;
}
}
return maps;
}
/**
* determine whether Flow1 or Flow2 is used by given ShadowRealTupleType
* @param srt tuple of ShadowRealTypes
* @param maps array with length equal to number of components in srt,
* used to return ScalarMaps to Flow from RealTypes of
* corresponding components in srt
* @return 0 for Flow1, 1 for Flow2, or -1 for neither (or both - error)
*/
private int getFlowMaps(ShadowRealTupleType srt, ScalarMap[] maps) {
if (display == null) return -1;
int n = srt.getDimension();
maps[0] = null;
maps[1] = null;
maps[2] = null;
DisplayTupleType ftuple = null;
for (int i=0; i<n; i++) {
ShadowRealType real = (ShadowRealType) srt.getComponent(i);
Enumeration ms = real.getSelectedMapVector().elements();
while (ms.hasMoreElements()) {
ScalarMap map = (ScalarMap) ms.nextElement();
DisplayRealType dreal = map.getDisplayScalar();
DisplayTupleType tuple = dreal.getTuple();
if (Display.DisplayFlow1Tuple.equals(tuple) ||
Display.DisplayFlow2Tuple.equals(tuple)) {
if (ftuple != null && !ftuple.equals(tuple)) return -1;
ftuple = tuple;
maps[i] = map;
break;
}
}
if (maps[i] == null) return -1;
}
return Display.DisplayFlow1Tuple.equals(ftuple) ? 0 : 1;
}
/** if non-null, float[][] new_spatial_values =
display_coordinate_system.toReference(spatial_values); */
CoordinateSystem display_coordinate_system = null;
/** spatial DisplayTupleType; set whether
display_coordinate_system is null or not */
DisplayTupleType spatial_tuple = null;
/** map from spatial_tuple tuple_index to value array indices;
set whether display_coordinate_system is null or not */
int[] spatial_value_indices = {-1, -1, -1};
/** default values for spatial DisplayRealTypes */
float[] default_spatial_in = {0.0f, 0.0f, 0.0f};
/** true if lat and lon mapped directly to spatial */
boolean lat_lon_spatial = false;
/** ScalarMap from RealType.Latitude */
ScalarMap lat_map = null;
/** ScalarMap from RealType.Longitude */
ScalarMap lon_map = null;
/** index of lat_map DisplayRealType in DisplayTupleType */
int lat_spatial_index = -1;
/** index of lon_map DisplayRealType in DisplayTupleType */
int lon_spatial_index = -1;
/** array of normalized (i.e., max = 1.0) ranges for spatial
ScalarMaps, for flow adjustment */
double[] ranges = null;
/**
* @return array of normalized (i.e., max = 1.0) ranges for spatial
* ScalarMaps, for flow adjustment
*/
public double[] getRanges() {
return ranges;
}
/**
* @return CoordinateSystem for spatial DisplayTupleType
* (null if DisplaySpatialCartesianTuple)
*/
public CoordinateSystem getDisplayCoordinateSystem() {
return display_coordinate_system ;
}
/**
* save information from ShadowType.assembleSpatial() about
* relation between earth and display spatial coordinates
* @param coord CoordinateSystem for spatial DisplayTupleType
* (null if DisplaySpatialCartesianTuple)
* @param t spatial DisplayTupleType
* @param display the DisplayImpl
* @param indices indices in display_values array for 3 spatial
* coordinates
* @param default_values default values for 3 spatial coordinates
* @param r double[3] array of normalized (i.e., max = 1.0)
* ranges for spatial ScalarMaps, for flow adjustment
* @throws VisADException a VisAD error occurred
*/
public void setEarthSpatialDisplay(CoordinateSystem coord,
DisplayTupleType t, DisplayImpl display, int[] indices,
float[] default_values, double[] r)
throws VisADException {
if (display == null) return;
display_coordinate_system = coord;
spatial_tuple = t;
System.arraycopy(indices, 0, spatial_value_indices, 0, 3);
ranges = r;
for (int i=0; i<3; i++) {
int default_index = display.getDisplayScalarIndex(
((DisplayRealType) t.getComponent(i)) );
default_spatial_in[i] = default_values[default_index];
}
if (lat_index > -1 && lon_index > -1) return;
lat_index = -1;
lon_index = -1;
other_index = -1;
int valueArrayLength = display.getValueArrayLength();
int[] valueToScalar = display.getValueToScalar();
int[] valueToMap = display.getValueToMap();
Vector MapVector = display.getMapVector();
for (int i=0; i<valueArrayLength; i++) {
ScalarMap map = (ScalarMap) MapVector.elementAt(valueToMap[i]);
ScalarType real = map.getScalar();
DisplayRealType dreal = map.getDisplayScalar();
DisplayTupleType tuple = dreal.getTuple();
if (tuple != null &&
(tuple.equals(Display.DisplaySpatialCartesianTuple) ||
(tuple.getCoordinateSystem() != null &&
tuple.getCoordinateSystem().getReference().equals(
Display.DisplaySpatialCartesianTuple)))) {
if (RealType.Latitude.equals(real)) {
lat_index = 0;
lat_map = map;
lat_spatial_index = dreal.getTupleIndex();
}
if (RealType.Longitude.equals(real)) {
lon_index = 1;
lon_map = map;
lon_spatial_index = dreal.getTupleIndex();
}
}
}
if (lat_index > -1 && lon_index > -1) {
lat_lon_spatial = true;
lat_lon_dimension = 2;
lat_lon_out = false;
lat_lon_in_by_coord = false;
lat_lon_in = false;
}
else {
lat_lon_spatial = false;
lat_index = -1;
lon_index = -1;
}
}
/* *************************** */
/* direct manipulation stuff */
/* *************************** */
private float[][] spatialValues = null;
/** if Function, last domain index and range values */
private int lastIndex = -1;
private double[] lastD = null;
private float[] lastX = new float[6];
/** index into spatialValues found by checkClose */
private int closeIndex = -1;
/** pick error offset, communicated from checkClose() to drag_direct() */
private float offsetx = 0.0f, offsety = 0.0f, offsetz = 0.0f;
/** count down to decay offset to 0.0 */
private int offset_count = 0;
/** initial offset_count */
private static final int OFFSET_COUNT_INIT = 30;
/** for use in drag_direct */
private transient DataDisplayLink link = null;
// private transient ShadowTypeJ3D type = null;
private transient DataReference ref = null;
private transient MathType type = null;
private transient ShadowType shadow = null;
/** point on direct manifold line or plane */
private float point_x, point_y, point_z;
/** normalized direction of line or perpendicular to plane */
private float line_x, line_y, line_z;
/** arrays of length one for inverseScaleValues */
private float[] f = new float[1];
private float[] d = new float[1];
private float[][] value = new float[1][1];
/** information calculated by checkDirect */
/** explanation for invalid use of DirectManipulationRenderer */
private String whyNotDirect = null;
/** mapping from spatial axes to tuple component */
private int[] axisToComponent = {-1, -1, -1};
/** mapping from spatial axes to ScalarMaps */
private ScalarMap[] directMap = {null, null, null};
/** spatial axis for Function domain */
private int domainAxis = -1;
/** dimension of direct manipulation
(including any Function domain) */
private int directManifoldDimension = 0;
/** spatial DisplayTupleType other than
DisplaySpatialCartesianTuple */
private DisplayTupleType tuple = null;
/** possible values for whyNotDirect */
private final static String notRealFunction =
"FunctionType must be Real";
private final static String notSimpleField =
"not simple field";
private final static String notSimpleTuple =
"not simple tuple";
private final static String multipleMapping =
"RealType with multiple mappings";
private final static String multipleSpatialMapping =
"RealType with multiple spatial mappings";
private final static String nonSpatial =
"no spatial mapping";
private final static String viaReference =
"spatial mapping through Reference";
private final static String domainDimension =
"domain dimension must be 1";
private final static String domainNotSpatial =
"domain must be mapped to spatial";
private final static String rangeType =
"range must be RealType or RealTupleType";
private final static String rangeNotSpatial =
"range must be mapped to spatial";
private final static String domainSet =
"domain Set must be Gridded1DSet";
private final static String tooFewSpatial =
"Function without spatial domain";
private final static String functionTooFew =
"Function directManifoldDimension < 2";
private final static String badCoordSysManifoldDim =
"bad directManifoldDimension with spatial CoordinateSystem";
private final static String lostConnection =
"lost connection to Data server";
private boolean stop = false;
private int LastMouseModifiers = 0;
/**
* determine if direct manipulation is feasible for the Data
* objects rendered by this, and for the ScalarMaps linked to
* the associated DisplayImpl;
* "returns" its result by calls to setIsDirectManipulation()
* called by checkDirect() method of DirectManipulationRendererJ2D
* and DirectManipulationRendererJ3D, basically just to share
* code between those two classes
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public synchronized void realCheckDirect()
throws VisADException, RemoteException {
if (display == null) return;
setIsDirectManipulation(false);
DataDisplayLink[] Links = getLinks();
if (Links == null || Links.length == 0) {
link = null;
return;
}
link = Links[0];
ref = link.getDataReference();
shadow = link.getShadow().getAdaptedShadowType();
type = link.getType();
tuple = null;
if (type instanceof FunctionType) {
ShadowRealTupleType domain =
((ShadowFunctionType) shadow).getDomain();
ShadowType range =
((ShadowFunctionType) shadow).getRange();
tuple = domain.getDisplaySpatialTuple();
// there is some redundancy among these conditions
if (!((FunctionType) type).getReal()) {
whyNotDirect = notRealFunction;
return;
}
else if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_FIELD) {
whyNotDirect = notSimpleField;
return;
}
else if (shadow.getMultipleSpatialDisplayScalar()) {
whyNotDirect = multipleSpatialMapping;
return;
}
else if (domain.getDimension() != 1) {
whyNotDirect = domainDimension;
return;
}
else if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
(tuple != null &&
tuple.getCoordinateSystem().getReference().equals(
Display.DisplaySpatialCartesianTuple)) )) {
whyNotDirect = domainNotSpatial;
return;
}
else if (domain.getSpatialReference()) {
whyNotDirect = viaReference;
return;
}
DisplayTupleType rtuple = null;
if (range instanceof ShadowRealTupleType) {
rtuple = ((ShadowRealTupleType) range).getDisplaySpatialTuple();
}
else if (range instanceof ShadowRealType) {
rtuple = ((ShadowRealType) range).getDisplaySpatialTuple();
}
else {
whyNotDirect = rangeType;
return;
}
if (!tuple.equals(rtuple)) {
whyNotDirect = rangeNotSpatial;
return;
}
else if (range instanceof ShadowRealTupleType &&
((ShadowRealTupleType) range).getSpatialReference()) {
whyNotDirect = viaReference;
return;
}
else {
Data data;
try {
data = link.getData();
} catch (RemoteException re) {
if (visad.collab.CollabUtil.isDisconnectException(re)) {
getDisplay().connectionFailed(this, link);
removeLink(link);
link = null;
whyNotDirect = lostConnection;
return;
}
throw re;
}
if (!(data instanceof Field) ||
!(((Field) data).getDomainSet() instanceof Gridded1DSet))
{
whyNotDirect = domainSet;
return;
}
}
if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
tuple = null;
}
domainAxis = -1;
for (int i=0; i<3; i++) {
axisToComponent[i] = -1;
directMap[i] = null;
}
directManifoldDimension =
setDirectMap((ShadowRealType) domain.getComponent(0), -1, true);
if (range instanceof ShadowRealType) {
directManifoldDimension +=
setDirectMap((ShadowRealType) range, 0, false);
}
else if (range instanceof ShadowRealTupleType) {
ShadowRealTupleType r = (ShadowRealTupleType) range;
for (int i=0; i<r.getDimension(); i++) {
directManifoldDimension +=
setDirectMap((ShadowRealType) r.getComponent(i), i, false);
}
}
if (domainAxis == -1) {
whyNotDirect = tooFewSpatial;
return;
}
if (directManifoldDimension < 2) {
whyNotDirect = functionTooFew;
return;
}
boolean twod = displayRenderer.getMode2D();
if (tuple != null &&
(!twod && directManifoldDimension != 3 ||
twod && directManifoldDimension != 2) ) {
whyNotDirect = badCoordSysManifoldDim;
return;
}
setIsDirectManipulation(true);
}
else if (type instanceof RealTupleType) {
//
// TO_DO
// allow for any Flat ShadowTupleType
//
tuple = ((ShadowRealTupleType) shadow).getDisplaySpatialTuple();
if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_TUPLE) {
whyNotDirect = notSimpleTuple;
return;
}
else if (shadow.getMultipleSpatialDisplayScalar()) {
whyNotDirect = multipleSpatialMapping;
return;
}
else if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
(tuple != null &&
tuple.getCoordinateSystem().getReference().equals(
Display.DisplaySpatialCartesianTuple)) )) {
whyNotDirect = nonSpatial;
return;
}
else if (((ShadowRealTupleType) shadow).getSpatialReference()) {
whyNotDirect = viaReference;
return;
}
if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
tuple = null;
}
domainAxis = -1;
for (int i=0; i<3; i++) {
axisToComponent[i] = -1;
directMap[i] = null;
}
directManifoldDimension = 0;
for (int i=0; i<((ShadowRealTupleType) shadow).getDimension(); i++) {
directManifoldDimension += setDirectMap((ShadowRealType)
((ShadowRealTupleType) shadow).getComponent(i), i, false);
}
boolean twod = displayRenderer.getMode2D();
if (tuple != null &&
(!twod && directManifoldDimension != 3 ||
twod && directManifoldDimension != 2) ) {
whyNotDirect = badCoordSysManifoldDim;
return;
}
setIsDirectManipulation(true);
}
else if (type instanceof RealType) {
tuple = ((ShadowRealType) shadow).getDisplaySpatialTuple();
if (shadow.getLevelOfDifficulty() != ShadowType.SIMPLE_TUPLE) {
whyNotDirect = notSimpleTuple;
return;
}
else if (shadow.getMultipleSpatialDisplayScalar()) {
whyNotDirect = multipleSpatialMapping;
return;
}
else if(!(Display.DisplaySpatialCartesianTuple.equals(tuple) ||
(tuple != null &&
tuple.getCoordinateSystem().getReference().equals(
Display.DisplaySpatialCartesianTuple)) )) {
whyNotDirect = nonSpatial;
return;
}
if (Display.DisplaySpatialCartesianTuple.equals(tuple)) {
tuple = null;
}
domainAxis = -1;
for (int i=0; i<3; i++) {
axisToComponent[i] = -1;
directMap[i] = null;
}
directManifoldDimension =
setDirectMap((ShadowRealType) shadow, 0, false);
boolean twod = displayRenderer.getMode2D();
if (tuple != null &&
(!twod && directManifoldDimension != 3 ||
twod && directManifoldDimension != 2) ) {
whyNotDirect = badCoordSysManifoldDim;
return;
}
setIsDirectManipulation(true);
} // end else if (type instanceof RealType)
}
/**
* set directMap and axisToComponent (domain = false) or domainAxis
* (domain = true) from real; called by realCheckDirect()
* @param real shadow of RealType in a ScalarMap
* @param component index of real in a ShadowRealTupleType
* -1 if real is a domain, 0 if range not in tuple
* @param domain true if real occurs in a Function domain
* @return direct manifold dimension (i.e., degrees of freedom
* of manipulation)
*/
synchronized int setDirectMap(ShadowRealType real, int component,
boolean domain) {
if (display == null) return 0;
Enumeration maps = real.getSelectedMapVector().elements();
while (maps.hasMoreElements()) {
ScalarMap map = (ScalarMap) maps.nextElement();
DisplayRealType dreal = map.getDisplayScalar();
DisplayTupleType tuple = dreal.getTuple();
if (Display.DisplaySpatialCartesianTuple.equals(tuple) ||
(tuple != null && tuple.getCoordinateSystem() != null &&
Display.DisplaySpatialCartesianTuple.equals(
tuple.getCoordinateSystem().getReference() )) ) {
int index = dreal.getTupleIndex();
if (domain) {
domainAxis = index;
}
else {
axisToComponent[index] = component;
}
directMap[index] = map;
return 1;
}
}
return 0;
}
/**
* @return direct manifold dimension (i.e., degrees of freedom
* of manipulation)
*/
private int getDirectManifoldDimension() {
return directManifoldDimension;
}
/**
* @return String with reason MathType and ScalarMaps do not
* qualify for direct manipulation
*/
public String getWhyNotDirect() {
return whyNotDirect;
}
/**
* @param index of a spatial axis
* @return index of tuple component for spatial axis i
*/
private int getAxisToComponent(int i) {
return axisToComponent[i];
}
/**
* @param index of a spatial axis
* @return ScalarMap for spatial axis i
*/
private ScalarMap getDirectMap(int i) {
return directMap[i];
}
/**
* @return spatial axis for Function domain
*/
private int getDomainAxis() {
return domainAxis;
}
/**
* set spatial values for Data depiction; used to detect when
* direct manipulation mouse selects a point of a Data depiction
* @param spatial_values float[3][number_of_points] of 3-D locations
* of depiction points
*/
public synchronized void setSpatialValues(float[][] spatial_values) {
// these are X, Y, Z values
spatialValues = spatial_values;
}
/**
* find minimum distance from ray to spatialValues; save index of
* point with minimum distance in closeIndex; reset lastIndex to -1
* (Field domain index of Field range value last modified by
* drag_direct())
* @param origin 3-D origin of ray
* @param direction 3-D direction of ray
* @return minimum distance of ray to any point in spatialValues
* (spatial values for Data depiction)
*/
public synchronized float checkClose(double[] origin, double[] direction) {
float distance = Float.MAX_VALUE;
if (display == null) return distance;
lastIndex = -1;
if (spatialValues == null) return distance;
float o_x = (float) origin[0];
float o_y = (float) origin[1];
float o_z = (float) origin[2];
float d_x = (float) direction[0];
float d_y = (float) direction[1];
float d_z = (float) direction[2];
/*
System.out.println("origin = " + o_x + " " + o_y + " " + o_z);
System.out.println("direction = " + d_x + " " + d_y + " " + d_z);
*/
for (int i=0; i<spatialValues[0].length; i++) {
float x = spatialValues[0][i] - o_x;
float y = spatialValues[1][i] - o_y;
float z = spatialValues[2][i] - o_z;
float dot = x * d_x + y * d_y + z * d_z;
x = x - dot * d_x;
y = y - dot * d_y;
z = z - dot * d_z;
float d = (float) Math.sqrt(x * x + y * y + z * z);
if (d < distance) {
distance = d;
closeIndex = i;
offsetx = x;
offsety = y;
offsetz = z;
}
/*
System.out.println("spatialValues["+i+"] = " + spatialValues[0][i] + " " +
spatialValues[1][i] + " " + spatialValues[2][i] + " d = " + d);
*/
}
/*
System.out.println("checkClose: distance = " + distance);
*/
return distance;
}
/**
* called when mouse button is released ending direct manipulation;
* intended to be over-ridden by DataRenderer extensions that need
* to act on this event
*/
public synchronized void release_direct() {
}
/**
* discontinue manipulating Data values for current mouse drag;
* (this only applies to the current mouse drag and is not a
* general disable)
*/
public void stop_direct() {
stop = true;
}
/**
* @return value of InputEvent.getModifiers() from most recent
* direct manipulation mouse click
*/
public int getLastMouseModifiers() {
return LastMouseModifiers;
}
/**
* called by MouseHelper.processEvent() to set LastMouseModifiers
* @param mouseModifiers value of InputEvent.getModifiers() from
* last direct manipulation mouse click
*/
public void setLastMouseModifiers(int mouseModifiers) {
LastMouseModifiers = mouseModifiers;
}
/**
* modify Data values based on direct manipulation mouse actions
* @param ray 3-D graphics coordinates of ray corresponding to
* mouse screen location
* @param first flag if this is first call (for MouseEvent.MOUSE_PRESSED,
* not for MouseEvent.MOUSE_DRAGGED)
* @param mouseModifiers value of InputEvent.getModifiers() from
* most recent direct manipulation mouse click
*/
public synchronized void drag_direct(VisADRay ray, boolean first,
int mouseModifiers) {
if (display == null) return;
// System.out.println("drag_direct " + first + " " + type);
if (spatialValues == null || ref == null || shadow == null ||
link == null) return;
if (first) {
stop = false;
}
else {
if (stop) return;
}
float o_x = (float) ray.position[0];
float o_y = (float) ray.position[1];
float o_z = (float) ray.position[2];
float d_x = (float) ray.vector[0];
float d_y = (float) ray.vector[1];
float d_z = (float) ray.vector[2];
if (pickCrawlToCursor) {
if (first) {
offset_count = OFFSET_COUNT_INIT;
}
else {
if (offset_count > 0) offset_count--;
}
if (offset_count > 0) {
float mult = ((float) offset_count) / ((float) OFFSET_COUNT_INIT);
o_x += mult * offsetx;
o_y += mult * offsety;
o_z += mult * offsetz;
}
}
if (first) {
point_x = spatialValues[0][closeIndex];
point_y = spatialValues[1][closeIndex];
point_z = spatialValues[2][closeIndex];
int lineAxis = -1;
if (getDirectManifoldDimension() == 3) {
// coord sys ok
line_x = d_x;
line_y = d_y;
line_z = d_z;
}
else {
if (getDirectManifoldDimension() == 2) {
if (displayRenderer.getMode2D()) {
// coord sys ok
lineAxis = 2;
}
else {
for (int i=0; i<3; i++) {
if (getAxisToComponent(i) < 0 && getDomainAxis() != i) {
lineAxis = i;
}
}
}
}
else if (getDirectManifoldDimension() == 1) {
for (int i=0; i<3; i++) {
if (getAxisToComponent(i) >= 0) {
lineAxis = i;
}
}
}
line_x = (lineAxis == 0) ? 1.0f : 0.0f;
line_y = (lineAxis == 1) ? 1.0f : 0.0f;
line_z = (lineAxis == 2) ? 1.0f : 0.0f;
}
} // end if (first)
float[] x = new float[3]; // x marks the spot
if (getDirectManifoldDimension() == 1) {
// find closest point on line to ray
// logic from vis5d/cursor.c
// line o_, d_ to line point_, line_
float ld = d_x * line_x + d_y * line_y + d_z * line_z;
float od = o_x * d_x + o_y * d_y + o_z * d_z;
float pd = point_x * d_x + point_y * d_y + point_z * d_z;
float ol = o_x * line_x + o_y * line_y + o_z * line_z;
float pl = point_x * line_x + point_y * line_y + point_z * line_z;
if (ld * ld == 1.0f) return;
float t = ((pl - ol) - (ld * (pd - od))) / (ld * ld - 1.0f);
// x is closest point
x[0] = point_x + t * line_x;
x[1] = point_y + t * line_y;
x[2] = point_z + t * line_z;
}
else { // getDirectManifoldDimension() = 2 or 3
// intersect ray with plane
float dot = (point_x - o_x) * line_x +
(point_y - o_y) * line_y +
(point_z - o_z) * line_z;
float dot2 = d_x * line_x + d_y * line_y + d_z * line_z;
if (dot2 == 0.0) return;
dot = dot / dot2;
// x is intersection
x[0] = o_x + dot * d_x;
x[1] = o_y + dot * d_y;
x[2] = o_z + dot * d_z;
}
//
//jeffmc: new call so a derived class can constrain the position of the drag point
//
constrainDragPoint(x);
//
// TO_DO
// might estimate errors from pixel resolution on screen
//
try {
float[] xx = {x[0], x[1], x[2]};
if (tuple != null) {
float[][] cursor = {{x[0]}, {x[1]}, {x[2]}};
float[][] new_cursor =
tuple.getCoordinateSystem().fromReference(cursor);
x[0] = new_cursor[0][0];
x[1] = new_cursor[1][0];
x[2] = new_cursor[2][0];
}
Data newData = null;
Data data;
try {
data = link.getData();
} catch (RemoteException re) {
if (visad.collab.CollabUtil.isDisconnectException(re)) {
getDisplay().connectionFailed(this, link);
removeLink(link);
link = null;
return;
}
throw re;
}
if (type instanceof RealType) {
addPoint(xx);
for (int i=0; i<3; i++) {
if (getAxisToComponent(i) >= 0) {
f[0] = x[i];
d = getDirectMap(i).inverseScaleValues(f);
// RealType rtype = (RealType) data.getType();
RealType rtype = (RealType) type;
newData = new Real(rtype, (double) d[0], rtype.getDefaultUnit(), null);
// create location string
Vector vect = new Vector();
Real r = new Real(rtype, d[0]);
Unit overrideUnit = getDirectMap(i).getOverrideUnit();
Unit rtunit = rtype.getDefaultUnit();
// units not part of Time string
if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
(!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
rtunit.getAbsoluteUnit().equals(rtunit))) {
double dval = overrideUnit.toThis((double) d[0], rtunit);
r = new Real(rtype, dval, overrideUnit);
}
String valueString = r.toValueString();
vect.addElement(rtype.getName() + " = " + valueString);
getDisplayRenderer().setCursorStringVector(vect);
break;
}
}
ref.setData(newData);
link.clearData();
}
else if (type instanceof RealTupleType) {
addPoint(xx);
int n = ((RealTuple) data).getDimension();
Real[] reals = new Real[n];
Vector vect = new Vector();
for (int i=0; i<3; i++) {
int j = getAxisToComponent(i);
if (j >= 0) {
f[0] = x[i];
d = getDirectMap(i).inverseScaleValues(f);
Real c = (Real) ((RealTuple) data).getComponent(j);
RealType rtype = (RealType) c.getType();
reals[j] = new Real(rtype, (double) d[0], rtype.getDefaultUnit(), null);
// create location string
Real r = new Real(rtype, d[0]);
Unit overrideUnit = getDirectMap(i).getOverrideUnit();
Unit rtunit = rtype.getDefaultUnit();
// units not part of Time string
if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
(!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
rtunit.getAbsoluteUnit().equals(rtunit))) {
double dval = overrideUnit.toThis((double) d[0], rtunit);
r = new Real(rtype, dval, overrideUnit);
}
String valueString = r.toValueString();
vect.addElement(rtype.getName() + " = " + valueString);
}
}
getDisplayRenderer().setCursorStringVector(vect);
for (int j=0; j<n; j++) {
if (reals[j] == null) {
reals[j] = (Real) ((RealTuple) data).getComponent(j);
}
}
newData = new RealTuple((RealTupleType) type, reals,
((RealTuple) data).getCoordinateSystem());
ref.setData(newData);
link.clearData();
}
else if (type instanceof FunctionType) {
Vector vect = new Vector();
if (first) lastIndex = -1;
int k = getDomainAxis();
f[0] = x[k];
d = getDirectMap(k).inverseScaleValues(f);
RealType rtype = (RealType) getDirectMap(k).getScalar();
// first, save value in default Unit
double dsave = d[0];
// WLH 4 Jan 99
// convert d from default Unit to actual domain Unit of data
Unit[] us = ((Field) data).getDomainUnits();
if (us != null && us[0] != null) {
d[0] = (float) us[0].toThis((double) d[0], rtype.getDefaultUnit());
}
// create location string
Real r = new Real(rtype, dsave);
Unit overrideUnit = getDirectMap(k).getOverrideUnit();
Unit rtunit = rtype.getDefaultUnit();
// units not part of Time string
if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
(!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
rtunit.getAbsoluteUnit().equals(rtunit))) {
dsave = overrideUnit.toThis(dsave, rtunit);
r = new Real(rtype, dsave, overrideUnit);
}
String valueString = r.toValueString();
vect.addElement(rtype.getName() + " = " + valueString);
// convert domain value to domain index
Gridded1DSet set = (Gridded1DSet) ((Field) data).getDomainSet();
value[0][0] = (float) d[0];
int[] indices = set.valueToIndex(value);
int thisIndex = indices[0];
if (thisIndex < 0) {
lastIndex = -1;
return;
}
if (lastIndex < 0) {
addPoint(xx);
}
else {
lastX[3] = xx[0];
lastX[4] = xx[1];
lastX[5] = xx[2];
addPoint(lastX);
}
lastX[0] = xx[0];
lastX[1] = xx[1];
lastX[2] = xx[2];
int n;
MathType range = ((FunctionType) type).getRange();
if (range instanceof RealType) {
n = 1;
}
else {
n = ((RealTupleType) range).getDimension();
}
double[] thisD = new double[n];
boolean[] directComponent = new boolean[n];
for (int j=0; j<n; j++) {
thisD[j] = Double.NaN;
directComponent[j] = false;
}
for (int i=0; i<3; i++) {
int j = getAxisToComponent(i);
if (j >= 0) {
f[0] = x[i];
d = getDirectMap(i).inverseScaleValues(f);
// create location string
rtype = (RealType) getDirectMap(i).getScalar();
r = new Real(rtype, d[0]);
overrideUnit = getDirectMap(i).getOverrideUnit();
rtunit = rtype.getDefaultUnit();
// units not part of Time string
if (overrideUnit != null && !overrideUnit.equals(rtunit) &&
(!Unit.canConvert(rtunit, CommonUnit.secondsSinceTheEpoch) ||
rtunit.getAbsoluteUnit().equals(rtunit))) {
double dval = overrideUnit.toThis((double) d[0], rtunit);
r = new Real(rtype, dval, overrideUnit);
}
valueString = r.toValueString();
vect.addElement(rtype.getName() + " = " + valueString);
thisD[j] = d[0];
directComponent[j] = true;
}
}
getDisplayRenderer().setCursorStringVector(vect);
if (lastIndex < 0) {
lastIndex = thisIndex;
lastD = new double[n];
for (int j=0; j<n; j++) {
lastD[j] = thisD[j];
}
}
Real[] reals = new Real[n];
int m = Math.abs(lastIndex - thisIndex) + 1;
indices = new int[m];
int index = thisIndex;
int inc = (lastIndex >= thisIndex) ? 1 : -1;
for (int i=0; i<m; i++) {
indices[i] = index;
index += inc;
}
float[][] values = set.indexToValue(indices);
double coefDiv = values[0][m-1] - values[0][0];
for (int i=0; i<m; i++) {
index = indices[i];
double coef = (i == 0 || coefDiv == 0.0) ? 0.0 :
(values[0][i] - values[0][0]) / coefDiv;
Data tuple = ((Field) data).getSample(index);
if (tuple instanceof Real) {
if (directComponent[0]) {
rtype = (RealType) tuple.getType();
tuple = new Real(rtype, thisD[0] + coef * (lastD[0] - thisD[0]),
rtype.getDefaultUnit(), null);
}
}
else {
for (int j=0; j<n; j++) {
Real c = (Real) ((RealTuple) tuple).getComponent(j);
if (directComponent[j]) {
rtype = (RealType) c.getType();
reals[j] = new Real(rtype, thisD[j] + coef * (lastD[j] - thisD[j]),
rtype.getDefaultUnit(), null);
}
else {
reals[j] = c;
}
}
tuple = new RealTuple(reals);
}
((Field) data).setSample(index, tuple);
} // end for (int i=0; i<m; i++)
if (ref instanceof RemoteDataReference &&
!(data instanceof RemoteData)) {
// ref is Remote and data is local, so we have only modified
// a local copy and must send it back to ref
ref.setData(data);
link.clearData(); // WLH 27 July 99
}
// set last index to this, and component values
lastIndex = thisIndex;
for (int j=0; j<n; j++) {
lastD[j] = thisD[j];
}
} // end else if (type instanceof FunctionType)
} // end try
catch (VisADException e) {
// do nothing
System.out.println("drag_direct " + e);
e.printStackTrace();
}
catch (RemoteException e) {
// do nothing
System.out.println("drag_direct " + e);
e.printStackTrace();
}
}
/**
* jeffmc: new method that provides a hook so derived classes can easily constrain the position of a drag point
*
* @param dragPoint The position of the drag point
*/
public void constrainDragPoint(float[]dragPoint) {}
/**
* add point for temporary rendering; intended to be
* over-ridden by graphics-API-specific extensions of
* DataRenderer
* @param x 3-D graphics coordinates of point to render
* @throws VisADException a VisAD error occurred
*/
public void addPoint(float[] x) throws VisADException {
}
/** flag indicating whether DirectManipulationRenderer is valid
for this ShadowType */
private boolean isDirectManipulation;
/**
* set isDirectManipulation = true if this DataRenderer supports
* direct manipulation for the MathType of its linked Data, and
* for its ScalarMaps; intended to be over-ridden by extensions of
* DataRenderer
* @throws VisADException a VisAD error occurred
* @throws RemoteException an RMI error occurred
*/
public void checkDirect() throws VisADException, RemoteException {
isDirectManipulation = false;
}
/**
* set value of isDirectManipulation flag (indicating whether this
* DataRenderer supports direct manipulation for its MathType and
* ScalarMaps)
* @param b value to set in isDirectManipulation
*/
public void setIsDirectManipulation(boolean b) {
isDirectManipulation = b;
}
/**
* @return value of isDirectManipulation flag (indicating whether this
* DataRenderer supports direct manipulation for its MathType
* and ScalarMaps)
*/
public boolean getIsDirectManipulation() {
return isDirectManipulation;
}
/** flag indicating whether points affected by direct manipulation should
"crawl" toward the cursor instead of jumping to it immediately. */
protected boolean pickCrawlToCursor = true;
/**
* set pickCrawlToCursor flag indicating whether Data points being
* manipulated should "crawl" toward the cursor instead of jumping
* to it immediately
* @param b value to set in pickCrawlToCursor
*/
public void setPickCrawlToCursor(boolean b) {
pickCrawlToCursor = b;
}
/**
* @return pickCrawlToCursor flag indicating whether Data points being
* manipulated should "crawl" toward the cursor instead of
* jumping to it immediately
*/
public boolean getPickCrawlToCursor() {
return pickCrawlToCursor;
}
private float ray_pos; // save last ray_pos as first guess for next
private static final int HALF_GUESSES = 200;
private static final int GUESSES = 2 * HALF_GUESSES + 1;
private static final float RAY_POS_INC = 0.1f;
private static final int TRYS = 10;
private static final double EPS = 0.001f;
/**
* find intersection of a ray and a 2-D manifold, using Newton's method
* @param first flag requesting to generate a first guess ray position
* by brute force search
* @param origin 3-D graphics coordinates of ray origin
* @param direction 3-D graphics coordinates of ray direction
* @param tuple spatial DisplayTupleType used to define 2-D manifold
* (manifold is defined by fixing value of one component
* of 3-D tuple)
* @param otherindex index of tuple component to be fixed
* @param othervalue value at which to fix otherindex tuple component
* @return parameter of point along ray of ray-manifold intersection
* (point = origin + parameter * direction)
* @throws VisADException a VisAD error occurred
*/
public float findRayManifoldIntersection(boolean first, double[] origin,
double[] direction, DisplayTupleType tuple,
int otherindex, float othervalue)
throws VisADException {
ray_pos = Float.NaN;
if (display == null) return ray_pos;
if (otherindex < 0) return ray_pos;
CoordinateSystem tuplecs = null;
if (tuple != null) tuplecs = tuple.getCoordinateSystem();
if (tuple == null || tuplecs == null) {
ray_pos = (float)
((othervalue - origin[otherindex]) / direction[otherindex]);
}
else { // tuple != null
double adjust = Double.NaN;
if (Display.DisplaySpatialSphericalTuple.equals(tuple)) {
if (otherindex == 1) adjust = 360.0;
}
if (first) {
// generate a first guess ray_pos by brute force
ray_pos = Float.NaN;
float[][] guesses = new float[3][GUESSES];
for (int i=0; i<GUESSES; i++) {
float rp = (i - HALF_GUESSES) * RAY_POS_INC;
guesses[0][i] = (float) (origin[0] + rp * direction[0]);
guesses[1][i] = (float) (origin[1] + rp * direction[1]);
guesses[2][i] = (float) (origin[2] + rp * direction[2]);
if (adjust == adjust) {
guesses[otherindex][i] = (float)
(((othervalue + 0.5 * adjust + guesses[otherindex][i]) % adjust) -
(othervalue + 0.5 * adjust));
}
}
guesses = tuplecs.fromReference(guesses);
double distance = Double.MAX_VALUE;
float lastg = 0.0f;
for (int i=0; i<GUESSES; i++) {
float g = othervalue - guesses[otherindex][i];
// first, look for nearest zero crossing and interpolate
if (i > 0 && ((g < 0.0f && lastg >= 0.0f) || (g >= 0.0f && lastg < 0.0f))) {
float r = (float)
(i - (Math.abs(g) / (Math.abs(lastg) + Math.abs(g))));
ray_pos = (r - HALF_GUESSES) * RAY_POS_INC;
break;
}
lastg = g;
// otherwise look for closest to zero
double d = Math.abs(othervalue - guesses[otherindex][i]);
if (d < distance) {
distance = d;
ray_pos = (i - HALF_GUESSES) * RAY_POS_INC;
}
} // end for (int i=0; i<GUESSES; i++)
}
if (ray_pos == ray_pos) {
// use Newton's method to refine first guess
// double error_limit = 10.0 * EPS;
double error_limit = EPS;
double r = ray_pos;
double error = 1.0f;
double[][] guesses = new double[3][3];
int itry;
// System.out.println("\nothervalue = " + (float) othervalue + " r = " + (float) r);
for (itry=0; (itry<TRYS && r == r); itry++) {
double rp = r + EPS;
double rm = r - EPS;
guesses[0][0] = origin[0] + rp * direction[0];
guesses[1][0] = origin[1] + rp * direction[1];
guesses[2][0] = origin[2] + rp * direction[2];
guesses[0][1] = origin[0] + r * direction[0];
guesses[1][1] = origin[1] + r * direction[1];
guesses[2][1] = origin[2] + r * direction[2];
guesses[0][2] = origin[0] + rm * direction[0];
guesses[1][2] = origin[1] + rm * direction[1];
guesses[2][2] = origin[2] + rm * direction[2];
// System.out.println(" guesses = " + (float) guesses[0][1] + " " +
// (float) guesses[1][1] + " " + (float) guesses[2][1]);
guesses = tuplecs.fromReference(guesses);
// System.out.println(" transformed = " + (float) guesses[0][1] + " " +
// (float) guesses[1][1] + " " + (float) guesses[2][1]);
if (adjust == adjust) {
guesses[otherindex][0] =
((othervalue + 0.5 * adjust + guesses[otherindex][0]) % adjust) -
(othervalue + 0.5 * adjust);
guesses[otherindex][1] =
((othervalue + 0.5 * adjust + guesses[otherindex][1]) % adjust) -
(othervalue + 0.5 * adjust);
guesses[otherindex][2] =
((othervalue + 0.5 * adjust + guesses[otherindex][2]) % adjust) -
(othervalue + 0.5 * adjust);
}
// System.out.println(" adjusted = " + (float) guesses[0][1] + " " +
// (float) guesses[1][1] + " " + (float) guesses[2][1]);
double g = othervalue - guesses[otherindex][1];
error = Math.abs(g);
if (error <= EPS) break;
double gp = othervalue - guesses[otherindex][0];
double gm = othervalue - guesses[otherindex][2];
double dg = (gp - gm) / (EPS + EPS);
// System.out.println("r = " + (float) r + " g = " + (float) g + " gm = " +
// (float) gm + " gp = " + (float) gp + " dg = " + (float) dg);
r = r - g / dg;
}
if (error < error_limit) {
ray_pos = (float) r;
}
else {
// System.out.println("error = " + error + " itry = " + itry);
ray_pos = Float.NaN;
}
}
} // end (tuple != null)
return ray_pos;
}
/**
* <b>WARNING!</b>
* Do <b>NOT</b> use this routine unless you know what you are doing!
* remove link from Links[] array when remote connection fails
* @param link DataDisplayLink to remove
*/
public void removeLink(DataDisplayLink link)
{
if (display == null) return;
final int newLen = Links.length - 1;
if (newLen < 0) {
// give up if the Links array is already empty
return;
}
DataDisplayLink[] newLinks = new DataDisplayLink[newLen];
int n = 0;
for (int i = 0; i <= newLen; i++) {
if (Links[i] == link) {
// skip the specified link
} else {
if (n == newLen) {
// yikes! Obviously didn't find this link in the list!
return;
}
newLinks[n++] = Links[i];
}
}
if (n < newLen) {
// Hmmm ... seem to have removed multiple instances of 'link'!
DataDisplayLink[] newest = new DataDisplayLink[n];
System.arraycopy(newLinks, 0, newest, 0, n);
newLinks = newest;
}
Links = newLinks;
}
/**
* @return a copy of this DataRenderer
*/
public abstract Object clone() throws CloneNotSupportedException;
public boolean hasPolygonOffset() {
return hasPolygonOffset;
}
public void setHasPolygonOffset(boolean hasPolygonOffset) {
this.hasPolygonOffset = hasPolygonOffset;
}
public float getPolygonOffset() {
return polygonOffset;
}
public void setPolygonOffset(float polygonOffset) {
this.polygonOffset = polygonOffset;
}
public float getPolygonOffsetFactor() {
return polygonOffsetFactor;
}
public void setPolygonOffsetFactor(float polygonOffsetFactor) {
this.polygonOffsetFactor = polygonOffsetFactor;
}
}