001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.control;
030
031import java.awt.Color;
032import java.awt.Component;
033import java.awt.Container;
034import java.awt.GridBagConstraints;
035import java.awt.Insets;
036import java.rmi.RemoteException;
037import java.text.DecimalFormat;
038import java.util.ArrayList;
039import java.util.Enumeration;
040import java.util.Hashtable;
041import java.util.List;
042
043import javax.swing.JComponent;
044import javax.swing.JLabel;
045import javax.swing.JTabbedPane;
046
047import org.slf4j.Logger;
048import org.slf4j.LoggerFactory;
049import ucar.unidata.data.DataChoice;
050import ucar.unidata.data.DerivedDataChoice;
051import ucar.unidata.data.DirectDataChoice;
052import ucar.unidata.idv.ViewManager;
053import ucar.unidata.idv.control.ControlWidget;
054import ucar.unidata.idv.control.DisplayControlImpl;
055import ucar.unidata.util.ColorTable;
056import ucar.unidata.util.GuiUtils;
057import ucar.unidata.util.LogUtil;
058import ucar.unidata.util.Range;
059import ucar.visad.ShapeUtility;
060import ucar.visad.display.DisplayMaster;
061import ucar.visad.display.DisplayableData;
062import ucar.visad.display.LineDrawing;
063import ucar.visad.display.SelectorPoint;
064import ucar.visad.display.TextDisplayable;
065import ucar.visad.display.XYDisplay;
066
067import visad.ConstantMap;
068import visad.Data;
069import visad.DataReference;
070import visad.DataReferenceImpl;
071import visad.Display;
072import visad.FlatField;
073import visad.FunctionType;
074import visad.Gridded1DSet;
075import visad.Gridded3DSet;
076import visad.GriddedSet;
077import visad.Integer1DSet;
078import visad.LocalDisplay;
079import visad.MathType;
080import visad.Real;
081import visad.RealTuple;
082import visad.RealTupleType;
083import visad.RealType;
084import visad.SampledSet;
085import visad.ScalarMap;
086import visad.Set;
087import visad.SimpleSet;
088import visad.Text;
089import visad.TextType;
090import visad.Tuple;
091import visad.TupleType;
092import visad.UnionSet;
093import visad.VisADException;
094import visad.VisADGeometryArray;
095import visad.georef.EarthLocationTuple;
096
097import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
098import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionDataSource;
099import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
100import edu.wisc.ssec.mcidasv.display.hydra.DragLine;
101import edu.wisc.ssec.mcidasv.display.hydra.MultiSpectralDisplay;
102
103public class ProfileAlongTrackControl extends DisplayControlImpl {
104  
105  private static final Logger logger =
106      LoggerFactory.getLogger(ProfileAlongTrackControl.class);
107  
108  private DisplayableData imageDisplay;
109  private DisplayableData trackDisplay;
110  private DisplayableData meshDisplay;
111  private DisplayableData textDisplay;
112
113  private DisplayMaster mainViewMaster;
114
115  private RealType imageRangeType;
116
117  public MultiDimensionSubset subset;
118
119  private MultiDimensionDataSource dataSource;
120
121  private FlatField track;
122
123  private XYDisplay display2D = null;
124
125  private SelectorPoint locOnTrack;
126
127  private DecimalFormat numFmt = new DecimalFormat();
128  
129
130  public ProfileAlongTrackControl() {
131    super();
132    setAttributeFlags(FLAG_COLORTABLE | FLAG_SELECTRANGE);
133  }
134
135  public boolean init(DataChoice dataChoice) throws VisADException, RemoteException {
136
137    FlatField data;
138
139    if (dataChoice instanceof DerivedDataChoice) {
140      data = (FlatField) dataChoice.getData(getDataSelection());
141    }
142    else {
143      dataSource = (MultiDimensionDataSource) ((DirectDataChoice)dataChoice).getDataSource();
144      ViewManager vm = getViewManager();
145      mainViewMaster = vm.getMaster();
146
147      Hashtable table = dataChoice.getProperties();
148      Enumeration keys = table.keys();
149      while (keys.hasMoreElements()) {
150        Object key = keys.nextElement();
151        if (key instanceof MultiDimensionSubset) {
152           subset = (MultiDimensionSubset) table.get(key);
153        }
154      }
155      subset.setGeoSelection(getDataSelection().getGeoSelection());
156
157      data = (FlatField) dataSource.getData(dataChoice, null, getDataSelection(), dataSource.getProperties());
158    }
159
160    if (data == null) {
161      return false;
162    }
163
164    imageRangeType = (RealType) ((FunctionType)data.getType()).getRange();
165    track = createTrackDisplay(dataChoice);
166    imageDisplay = create3DDisplay(data);
167    addDisplayable(imageDisplay, FLAG_COLORTABLE | FLAG_SELECTRANGE);
168    if (track != null) create3DMesh(track);
169
170    // 2D Display in Control Window, only line graph type display for now
171    if (((SimpleSet)data.getDomainSet()).getManifoldDimension() == 1) {
172      display2D = makeDisplay2D(data);
173    }
174
175    return true;
176  }
177
178  public synchronized void dataChanged() {
179    super.dataChanged();
180  }
181
182  private FlatField createTrackDisplay(DataChoice dataChoice) throws VisADException, RemoteException {
183
184    FlatField track = null;
185
186    dataChoice = dataSource.findDataChoice("Track3D");
187    if (dataChoice == null) {
188       return null;
189    }
190   
191    Hashtable table = dataChoice.getProperties();
192    table.put(MultiDimensionSubset.key, subset);
193    
194
195    track = (FlatField) dataSource.getData(dataChoice, null, getDataSelection(), dataSource.getProperties());
196
197    LineDrawing trackDsp = new LineDrawing("track");
198    trackDsp.setLineWidth(2f);
199    trackDsp.setData(track.getDomainSet());
200    mainViewMaster.addDisplayable(trackDsp);
201
202    // ??? setConstantPosition(val, display real type) ??
203    locOnTrack = new SelectorPoint("marker", new EarthLocationTuple(10, 10, 0));
204//    locOnTrack.setMarker(ShapeUtility.makeShape(ShapeUtility.CROSS));
205    VisADGeometryArray[] markerShape = ShapeUtility.createShape(ShapeUtility.CROSS);
206    locOnTrack.setMarker(markerShape[0]);
207    mainViewMaster.addDisplayable(locOnTrack);
208    locOnTrack.setScale(0.1f);
209
210    trackDisplay = trackDsp;
211    return track;
212  }
213
214  private DisplayableData create3DDisplay(FlatField data) throws VisADException, RemoteException {
215    RealType imageRangeType = (RealType) ((FunctionType)data.getType()).getRange();
216    HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, (RealType) null, true, null);
217    imageDsp.setDefaultRenderer();
218    imageDsp.setData(data);
219    return imageDsp;
220  }
221
222  private void create3DMesh(FlatField track) throws VisADException, RemoteException {
223    float del_lat = 2f;
224    int n_sets = 3;
225    GriddedSet set = (GriddedSet) track.getDomainSet();
226
227    float[][] samples = set.getSamples();
228    float[][] samples3D = new float[][] {samples[0], samples[1], new float[samples[0].length]};
229
230    SampledSet[] sets = new SampledSet[n_sets];
231    Tuple[] labels = new Tuple[n_sets];
232    float alt_start = 2000;
233    float alt_inc = 5000;
234    for (int k=0; k<n_sets; k++) {
235      for (int i=0; i<samples3D[2].length; i++) {
236        samples3D[2][i] = alt_start + k*alt_inc;
237      }
238      sets[k] = new Gridded3DSet(RealTupleType.SpatialEarth3DTuple, samples3D, samples3D[2].length);
239      Tuple tup = new Tuple(new TupleType(new MathType[] {RealTupleType.SpatialEarth3DTuple, TextType.Generic}),
240            new Data[] {new RealTuple(RealTupleType.SpatialEarth3DTuple, 
241                  new double[] {samples3D[0][0], samples3D[1][0] - del_lat, samples3D[2][0]}), 
242                          new Text(TextType.Generic, Float.toString(samples3D[2][0]))});
243      labels[k] = tup;
244    }
245
246    UnionSet u_set = new UnionSet(sets);
247    LineDrawing meshDsp = new LineDrawing("mesh");
248    meshDsp.setLineWidth(2f);
249    meshDsp.setData(u_set);
250    mainViewMaster.addDisplayable(meshDsp);
251
252    TextDisplayable txtDsp = new TextDisplayable(TextType.Generic);
253    txtDsp.setData(new Tuple(labels));
254    txtDsp.setLineWidth(2f);
255    mainViewMaster.addDisplayable(txtDsp);
256
257    meshDisplay = meshDsp;
258    textDisplay = txtDsp;
259    
260    return;
261  }
262
263  private XYDisplay makeDisplay2D(final FlatField data) throws VisADException, RemoteException {
264    
265    FunctionType fncType = (FunctionType) data.getType();
266
267    RealType domainType = RealType.Generic;
268    RealType rangeType = (RealType) fncType.getRange();
269
270    final Set domainSet = data.getDomainSet();
271    int len = domainSet.getLength();
272    Integer1DSet newDomain = new Integer1DSet(len);
273    FlatField newFF = new FlatField(new FunctionType(RealType.Generic, rangeType), newDomain);
274    newFF.setSamples(data.getFloats());
275
276    XYDisplay master = new XYDisplay("2D disp", domainType, rangeType);
277
278    master.showAxisScales(true);
279    master.setAspect(2.5, 0.75);
280    double[] proj = master.getProjectionMatrix();
281    proj[0] = 0.35;
282    proj[5] = 0.35;
283    proj[10] = 0.35;
284    master.setProjectionMatrix(proj);
285
286    ScalarMap xmap = new ScalarMap(domainType, Display.XAxis);
287    ScalarMap ymap = new ScalarMap(rangeType, Display.YAxis);
288    ScalarMap txtMap = new ScalarMap(TextType.Generic, Display.Text);
289
290    LocalDisplay display = master.getDisplay();
291    display.addMap(xmap);
292    display.addMap(ymap);
293    display.addMap(txtMap);
294
295    DataReference dataRef = new DataReferenceImpl("data");
296    dataRef.setData(newFF);
297    display.addReference(dataRef);
298
299    final DataReference txtRef = new DataReferenceImpl("text");
300    display.addReference(txtRef, new ConstantMap[] {new ConstantMap(0.9, Display.YAxis)});
301
302
303    class MyDragLine extends DragLine {
304      public MyDragLine(Gridded1DSet domain, RealType domainType, RealType rangeType,
305            final float lastSelectedValue, LocalDisplay display, final String controlId,
306            final ConstantMap[] color, float[] YRANGE) throws Exception {
307        super(domain, domainType, rangeType, lastSelectedValue, display, controlId, color, YRANGE);
308      }
309
310      public void update() {
311         int idx = (new Float(this.lastSelectedValue)).intValue();
312         try {
313           float[][] val = domainSet.indexToValue(new int[] {idx});
314           locOnTrack.setPoint(new EarthLocationTuple(val[1][0], val[0][0], 0));
315           float rangeVal = (float) ((Real)data.getSample(idx)).getValue();
316           Tuple tup = new Tuple(new Data[] {new Real(RealType.Generic, (double) idx), new Text(TextType.Generic, numFmt.format(rangeVal))});
317           txtRef.setData(tup);
318           
319         } catch (Exception e) {
320           System.out.println(e);
321         }
322      }
323    }
324
325    try {
326        MyDragLine draggable = new MyDragLine(newDomain, domainType, rangeType, 100f, display, 
327            "dragLine", MultiSpectralDisplay.makeColorMap(Color.GREEN), new float[] {0, 16});
328    } catch (Exception e) {
329      logger.error("Problem creating drag line", e);
330    }
331
332    return master;
333  }
334
335  protected ColorTable getInitialColorTable() {
336    return getDisplayConventions().getParamColorTable(imageRangeType.getName());
337  }
338
339  protected Range getInitialRange() throws RemoteException, VisADException {
340      Range range = getDisplayConventions().getParamRange(imageRangeType.getName(), null);
341      if (range != null) {
342        setSelectRange(range);
343        return range;
344      }
345      else {
346        return super.getInitialRange();
347      }
348  }
349
350  public void doRemove() throws RemoteException, VisADException{
351    
352    if (meshDisplay != null) mainViewMaster.removeDisplayable(meshDisplay);
353    if (textDisplay != null) mainViewMaster.removeDisplayable(textDisplay);
354    if (trackDisplay != null) mainViewMaster.removeDisplayable(trackDisplay);
355    super.doRemove();
356  }
357
358  public void setDisplayVisibility(boolean on) {
359    super.setDisplayVisibility(on);
360    try {
361      if (meshDisplay != null) meshDisplay.setVisible(on);
362      if (textDisplay != null) textDisplay.setVisible(on);
363      if (trackDisplay != null) trackDisplay.setVisible(on);
364    } catch( Exception e) {
365      logger.error("Problem changing display visibility", e);
366    }
367  }
368
369  public Container doMakeContents() {
370        try {
371            JTabbedPane pane = new JTabbedPane();
372            if (display2D != null) {
373              pane.add("Display", GuiUtils.inset(display2D.getDisplayComponent(), 5));
374            }
375            pane.add("Settings",
376                     GuiUtils.inset(GuiUtils.top(doMakeWidgetComponent()), 5));
377            GuiUtils.handleHeavyWeightComponentsInTabs(pane);
378            return pane;
379        } catch (Exception e) {
380            logException("MultiSpectralControl.doMakeContents", e);
381        }
382        return null;
383  }
384
385  protected JComponent doMakeWidgetComponent() {
386        List<Component> widgetComponents;
387        try {
388            List<ControlWidget> controlWidgets = new ArrayList<ControlWidget>();
389            getControlWidgets(controlWidgets);
390            widgetComponents = ControlWidget.fillList(controlWidgets);
391        } catch (Exception e) {
392            LogUtil.logException("Problem building the ProfileAlongTrackControl settings", e);
393            widgetComponents = new ArrayList<Component>();
394            widgetComponents.add(new JLabel("Error building component..."));
395        }
396
397        GuiUtils.tmpInsets = new Insets(4, 8, 4, 8);
398        GuiUtils.tmpFill = GridBagConstraints.HORIZONTAL;
399        return GuiUtils.doLayout(widgetComponents, 2, GuiUtils.WT_NY, GuiUtils.WT_N);
400    }
401
402}