// // EmpiricalCoordinateSystem.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.rmi.RemoteException; import visad.util.Util; /** * Provides support for empirically-defined CoordinateSystem-s. This is useful * for data-dependent coordinate transformations that must be determined * empirically rather than analytically (e.g. pressure <-> height).</p> * * <p>Coordinates in this system are termed "world" coordinates and coordinates * in the reference coordinate system are termed "reference" coordinates.</p> * * <p>Instances of this class are immutable. * * @author Steven R. Emmerson */ public final class EmpiricalCoordinateSystem extends CoordinateSystem implements java.io.Serializable { /** * The world/grid coordinate system. * @serial */ private final GridCoordinateSystem worldCS; /** * The reference/grid coordinate system. * @serial */ private final GridCoordinateSystem referenceCS; /** * Constructs from two GriddedSet-s. The dimensionality (i.e. rank and * lengths) of the two sets must be identical. The RealTupleType of the * reference will be that of the reference set. The units of the world * coordinates will be the actual units of the world set. * @param world A set of world coordinates. * <code>world.getLengths()</code> shall equal * <code>reference.getLengths()</code>. Determines * the default units of world coordinates: * the default units will be those of <code> * world.getSetUnits()</code>. * @param reference A set of reference coordinates. Determines * the reference RealTupleType. * @throws VisADException Couldn't create necessary VisAD object. */ public EmpiricalCoordinateSystem(GriddedSet world, GriddedSet reference) throws VisADException { this(world, reference, true, true); } /** * Constructs from two GriddedSet-s. The dimensionality (i.e. rank and * lengths) of the two sets must be identical. The RealTupleType of the * reference will be that of the reference set. The units of the world * coordinates will be the actual units of the world set. * @param world A set of world coordinates. * <code>world.getLengths()</code> shall equal * <code>reference.getLengths()</code>. Determines * the default units of world coordinates: * the default units will be those of <code> * world.getSetUnits()</code>. * @param reference A set of reference coordinates. Determines * the reference RealTupleType. * @param copy copy samples if clone is made * @param check check whether samples form a valid grid * @throws VisADException Couldn't create necessary VisAD object. */ public EmpiricalCoordinateSystem(GriddedSet world, GriddedSet reference, boolean copy, boolean check) throws VisADException { super(((SetType)reference.getType()).getDomain(), world.getSetUnits()); worldCS = new GridCoordinateSystem(ensureNoCoordinateSystem(world, copy, check)); referenceCS = new GridCoordinateSystem(ensureNoCoordinateSystem(reference, copy, check)); } /** * Ensures that a GriddedSet doesn't have a default CoordinateSystem. * * @param griddedSet The GriddedSet to not have a CoordinateSystem. * @return The GriddedSet without a CoordinateSystem. * May be the original GriddedSet. * @throws VisADException Couldn't create necessary VisAD object. */ protected static GriddedSet ensureNoCoordinateSystem(GriddedSet griddedSet) throws VisADException { return ensureNoCoordinateSystem(griddedSet, true, true); } /** * Ensures that a GriddedSet doesn't have a default CoordinateSystem. * * @param griddedSet The GriddedSet to not have a CoordinateSystem. * @return The GriddedSet without a CoordinateSystem. * May be the original GriddedSet. * @throws VisADException Couldn't create necessary VisAD object. */ protected static GriddedSet ensureNoCoordinateSystem(GriddedSet griddedSet, boolean copy, boolean check) throws VisADException { if (griddedSet.getCoordinateSystem() != null) { /* * The GriddedSet has a CoordinateSystem. Clone the GriddedSet without * the CoordinateSystem. */ SetType setType = (SetType)griddedSet.getType(); RealTupleType realTupleType = setType.getDomain(); if (realTupleType.getCoordinateSystem() != null) setType = new SetType(new RealTupleType(realTupleType.getRealComponents())); griddedSet = GriddedSet.create( setType, griddedSet.getSamples(), griddedSet.getLengths(), (CoordinateSystem)null, griddedSet.getSetUnits(), griddedSet.getSetErrors(), copy, check); } return griddedSet; } /** * Constructs an EmpiricalCoordinateSystem from a Field. The * <code>toReference()</code> method of the resulting CoordinateSystem will * transform numeric values in actual units of the domain of the field to * numeric values in default units of the range of the field. Similarly, * the <code>fromReference()</code> method will transform numeric values in * default units of the range of the field to numeric values in the actual * units of the domain of the field. * * @param field The FlatField comprising a coordinate system * transformation from the domain to the range. * The rank of the domain and range shall be the * same. The domain set of the field shall be a * GriddedSet. * @throws SetException The field's domain set isn't a GriddedSet. * @throws VisADException Couldn't create necessary VisAD object. * @throws RemoteException Java RMI failure. */ public static EmpiricalCoordinateSystem create(Field field) throws SetException, VisADException, RemoteException { Set domainSet = field.getDomainSet(); float[][] samples = field.getFloats(false); // in default units if (!(domainSet instanceof GriddedSet)) throw new SetException("Domain set must be GriddedSet"); return new EmpiricalCoordinateSystem( (GriddedSet)domainSet, GriddedSet.create( ((FunctionType)field.getType()).getFlatRange(), samples, new int[] {samples[0].length}, (CoordinateSystem)null, (Unit[])null, (ErrorEstimate[])null)); } /** * Constructs an EmpiricalCoordinateSystem from a Field. The * <code>toReference()</code> method of the resulting CoordinateSystem will * transform numeric values in actual units of the range of the field to * numeric values in default units of the domain of the field. Similarly, * the <code>fromReference()</code> method will transform numeric values in * default units of the domain of the field to numeric values in the actual * units of the range of the field. The coordinate system transformation * created by this method is the inverse of that created by {@link * #create(Field)}. * * @param field The FlatField comprising a coordinate system * transformation from the range to the domain. * The rank of the domain and range shall be the * same. The domain set of the field shall be a * GriddedSet. * @throws SetException The field's domain set isn't a GriddedSet. * @throws VisADException Couldn't create necessary VisAD object. * @throws RemoteException Java RMI failure. */ public static EmpiricalCoordinateSystem inverseCreate(Field field) throws SetException, VisADException, RemoteException { Set domainSet = field.getDomainSet(); float[][] samples = field.getFloats(false); // in default units if (!(domainSet instanceof GriddedSet)) throw new SetException("Domain set must be GriddedSet"); return new EmpiricalCoordinateSystem( GriddedSet.create( ((FunctionType)field.getType()).getFlatRange(), samples, new int[] {samples[0].length}, (CoordinateSystem)null, (Unit[])null, (ErrorEstimate[])null), (GriddedSet)domainSet); } /** * Returns the Set of world coordinates. * @return The Set of world coordinates. */ public GriddedSet getWorldSet() { return worldCS.getGriddedSet(); } /** * Returns the Set of reference coordinates. * @return The Set of reference coordinates. */ public GriddedSet getReferenceSet() { return referenceCS.getGriddedSet(); } /** * Converts reference coordinates to world coordinates. * @param values Numeric reference coordinates in default units * of the reference RealTupleType to be * converted to numeric world coordinates. * <code>values.length</code> shall * equal <code>getDimension()</code> and * <code>values[i].length</code> shall be the same * for all <code>i</code>. * @return <code>values</code> which now contains * world coordinates. Values which could * not be converted will have the value * <code>Double.NaN</code>. * @throws SetException Mismatch between input values and field domain. * @throws VisADException Couldn't create necessary VisAD object. */ public double[][] fromReference(double[][] values) throws SetException, VisADException { return worldCS.toReference(referenceCS.fromReference(values)); } /** * Convert world coordinates to reference coordinates. * @param values Numeric world coordinates to be converted * to numeric reference coordinates in default * units of the reference RealTupleType. * <code>values.length</code> shall * equal <code>getDimension()</code> and * <code>values[i].length</code> shall be the same * for all <code>i</code>. * @return <code>values</code> which now contains * world coordinates. Values which could * not be converted will have the value * <code>Double.NaN</code>. * @throws SetException Mismatch between input values and field domain. * @throws VisADException Couldn't create necessary VisAD object. */ public double[][] toReference(double[][] values) throws SetException, VisADException { GriddedSet set = worldCS.getGriddedSet(); double[][] samples = set.getDoubles(false); if (set.getLength() == values[0].length) { int[] lens = set.getLengths(); int dim = lens.length; double[][] tvalues = new double[values.length][values[0].length]; int[] save_index = new int[values[0].length]; int start = 0; int cnt = 0; for (int i = 0; i < values[0].length; i++) { boolean all = true; for (int k = 0; k < dim; k++) { if (!(Util.isApproximatelyEqual(values[k][i], samples[k][i]))) { all = false; } } if (all) { int k = i; int[] indexI = new int[dim]; for (int j=0; j<dim-1; j++) { tvalues[j][i] = k % lens[j]; k = k / lens[j]; } tvalues[dim-1][i] = k; } else { return referenceCS.toReference(worldCS.fromReference(values)); } } return referenceCS.toReference(tvalues); } else { return referenceCS.toReference(worldCS.fromReference(values)); } } /** * Converts reference coordinates to world coordinates. * @param values Numeric reference coordinates in default units * of the reference RealTupleType to be * converted to numeric world coordinates. * <code>values.length</code> shall * equal <code>getDimension()</code> and * <code>values[i].length</code> shall be the same * for all <code>i</code>. * @return <code>values</code> which now contains * world coordinates. Values which could * not be converted will have the value * <code>Double.NaN</code>. * @throws SetException Mismatch between input values and field domain. * @throws VisADException Couldn't create necessary VisAD object. */ public float[][] fromReference(float[][] values) throws SetException, VisADException { return worldCS.toReference(referenceCS.fromReference(values)); } /** * Convert world coordinates to reference coordinates. * @param values Numeric world coordinates to be converted * to numeric reference coordinates in default * units of the reference RealTupleType. * <code>values.length</code> shall * equal <code>getDimension()</code> and * <code>values[i].length</code> shall be the same * for all <code>i</code>. * @return <code>values</code> which now contains * world coordinates. Values which could * not be converted will have the value * <code>Double.NaN</code>. * @throws SetException Mismatch between input values and field domain. * @throws VisADException Couldn't create necessary VisAD object. */ public float[][] toReference(float[][] values) throws SetException, VisADException { GriddedSet set = worldCS.getGriddedSet(); float[][] samples = set.getSamples(false); if (set.getLength() == values[0].length) { int[] lens = set.getLengths(); int dim = lens.length; float[][] tvalues = new float[values.length][values[0].length]; int start = 0; int cnt = 0; for (int i = 0; i < values[0].length; i++) { boolean all = true; for (int k = 0; k < dim; k++) { if (!(Util.isApproximatelyEqual(values[k][i], samples[k][i]))) { all = false; } } if (all) { int k = i; int[] indexI = new int[dim]; for (int j=0; j<dim-1; j++) { tvalues[j][i] = k % lens[j]; k = k / lens[j]; } tvalues[dim-1][i] = k; } else { return referenceCS.toReference(worldCS.fromReference(values)); } } return referenceCS.toReference(tvalues); } else { return referenceCS.toReference(worldCS.fromReference(values)); } } /** * Indicates if this coordinate system is semantically identical to an * object. * @param object The object in question. * @return <code>true</code> if and only if this * coordinate system is semantically identical to * <code>object</code>. */ public boolean equals(Object object) { if (!(object instanceof EmpiricalCoordinateSystem)) return false; EmpiricalCoordinateSystem that = (EmpiricalCoordinateSystem)object; return worldCS.equals(that.worldCS) && referenceCS.equals(that.referenceCS); } }