001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2023
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 http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.data.hydra;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.FlowLayout;
034import java.awt.event.ActionEvent;
035import java.awt.event.ActionListener;
036import java.net.URL;
037import java.rmi.RemoteException;
038import java.util.HashMap;
039import java.util.Hashtable;
040import java.util.Map;
041
042import javax.swing.DefaultBoundedRangeModel;
043import javax.swing.JComponent;
044import javax.swing.JLabel;
045import javax.swing.JPanel;
046import javax.swing.JSlider;
047import javax.swing.JTextField;
048import javax.swing.event.ChangeEvent;
049import javax.swing.event.ChangeListener;
050
051import org.slf4j.Logger;
052import org.slf4j.LoggerFactory;
053
054import ucar.unidata.data.DataChoice;
055import ucar.unidata.data.DataSelection;
056import ucar.unidata.data.DataSelectionComponent;
057import ucar.unidata.geoloc.ProjectionRect;
058import ucar.unidata.geoloc.projection.LatLonProjection;
059import ucar.unidata.view.geoloc.MapProjectionDisplayJ2D;
060import ucar.visad.ProjectionCoordinateSystem;
061import ucar.visad.display.DisplayMaster;
062import ucar.visad.display.LineDrawing;
063import ucar.visad.display.MapLines;
064
065import visad.FlatField;
066import visad.Gridded2DSet;
067import visad.GriddedSet;
068import visad.RealTupleType;
069import visad.VisADException;
070import visad.data.mcidas.BaseMapAdapter;
071import visad.georef.MapProjection;
072
073public class TrackSelection extends DataSelectionComponent {
074
075    private static final Logger logger =
076        LoggerFactory.getLogger(TrackSelection.class);
077
078    public static final int DEFAULT_TRACK_STRIDE = 5;
079    public static final int DEFAULT_VERTICAL_STRIDE = 2;
080    public static final int DEFAULT_TRACK_LENGTH_PERCENT = 5;
081
082    DataChoice dataChoice;
083    FlatField track;
084
085    double[] x_coords = new double[2];
086    double[] y_coords = new double[2];
087    MapProjectionDisplayJ2D mapProjDsp;
088    DisplayMaster dspMaster;
089
090    int trackStride;
091    int verticalStride;
092
093    JTextField trkStr;
094    JTextField vrtStr;
095    JTextField lengthField;
096
097    LineDrawing trackSelectDsp;
098    float[][] trackLocs;
099    int trackLen;
100    int trackPos;
101    int trackStart;
102    int trackStop;
103    int trackLengthPercent = DEFAULT_TRACK_LENGTH_PERCENT;
104    int selectWidth;
105    Map defaultSubset;
106
107    TrackSelection(DataChoice dataChoice, FlatField track, Map defaultSubset)
108            throws VisADException, RemoteException {
109        super("Track");
110        this.dataChoice = dataChoice;
111        this.track = track;
112        this.defaultSubset = defaultSubset;
113
114        GriddedSet gset = (GriddedSet) track.getDomainSet();
115        float[] lo = gset.getLow();
116        float[] hi = gset.getHi();
117        float[][] values = gset.getSamples();
118
119        trackLen = values[0].length;
120        selectWidth = (int) (trackLen * ((float) trackLengthPercent / 100.0f));
121        selectWidth /= 2;
122        trackPos = trackLen / 2;
123        trackStart = trackPos - selectWidth;
124        trackStop = trackPos + selectWidth;
125
126        trackLocs = values;
127        Gridded2DSet track2D = new Gridded2DSet(RealTupleType.SpatialEarth2DTuple, new float[][] {
128                values[0], values[1] }, values[0].length);
129
130        mapProjDsp = new MapProjectionDisplayJ2D();
131
132        dspMaster = mapProjDsp;
133        mapProjDsp.setMapProjection(getDataProjection(new ProjectionRect(lo[0], lo[1], hi[0], hi[1])));
134        LineDrawing trackDsp = new LineDrawing("track");
135        trackDsp.setLineWidth(0.5f);
136        trackDsp.setData(track2D);
137
138        trackSelectDsp = new LineDrawing("trackSelect");
139        trackSelectDsp.setLineWidth(3f);
140        trackSelectDsp.setColor(java.awt.Color.magenta);
141
142        updateTrackSelect();
143    
144        mapProjDsp.addDisplayable(trackSelectDsp);
145        mapProjDsp.addDisplayable(trackDsp);
146
147        MapLines mapLines = new MapLines("maplines");
148        URL mapSource = mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
149        try {
150            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
151            mapLines.setMapLines(mapAdapter.getData());
152            mapLines.setColor(Color.cyan);
153        } catch (Exception excp) {
154            logger.error("cannot open map file: " + mapSource, excp);
155        }
156
157        mapLines = new MapLines("maplines");
158        mapSource = mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
159        try {
160            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
161            mapLines.setMapLines(mapAdapter.getData());
162            mapLines.setColor(Color.cyan);
163            mapProjDsp.addDisplayable(mapLines);
164        } catch (Exception excp) {
165            logger.error("cannot open map file: " + mapSource, excp);
166        }
167
168        mapLines = new MapLines("maplines");
169        mapSource = mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
170        try {
171            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
172            mapLines.setMapLines(mapAdapter.getData());
173            mapLines.setColor(Color.cyan);
174        } catch (Exception excp) {
175            logger.error("cannot open map file: " + mapSource, excp);
176        }
177
178        dspMaster.draw();
179    }
180
181    public MapProjection getDataProjection(ProjectionRect rect) {
182        MapProjection mp = null;
183        try {
184            mp = new ProjectionCoordinateSystem(new LatLonProjection("blah", rect));
185        } catch (Exception e) {
186            logger.error("error getting data projection", e);
187        }
188        return mp;
189    }
190
191    protected JComponent doMakeContents() {
192        try {
193            JPanel panel = new JPanel(new BorderLayout());
194            panel.add("Center", dspMaster.getDisplayComponent());
195
196            JPanel stridePanel = new JPanel(new FlowLayout());
197            
198            // initialize the UI components
199            trkStr = new JTextField(Integer.toString(TrackSelection.DEFAULT_TRACK_STRIDE), 2);
200            vrtStr = new JTextField(Integer.toString(TrackSelection.DEFAULT_VERTICAL_STRIDE), 2);
201            lengthField = new JTextField(Integer.toString(TrackSelection.DEFAULT_TRACK_LENGTH_PERCENT), 2);
202            
203            // set tooltip hints
204            trkStr.setToolTipText("Sets the horizontal stride along the track (X/Y-axes)");
205            vrtStr.setToolTipText("Sets the vertical stride (Z-axis)");
206            lengthField.setToolTipText("Sets the percentage length of total track to display");
207
208            trkStr.addActionListener(new ActionListener() {
209                public void actionPerformed(ActionEvent ae) {
210                    setTrackStride(Integer.valueOf(trkStr.getText().trim()));
211                }
212            });
213            vrtStr.addActionListener(new ActionListener() {
214                public void actionPerformed(ActionEvent ae) {
215                    setVerticalStride(Integer.valueOf(vrtStr.getText().trim()));
216                }
217            });
218            lengthField.addActionListener(new ActionListener() {
219                public void actionPerformed(ActionEvent ae) {
220                    setLengthPercent(Integer.valueOf(lengthField.getText().trim()));
221                }
222            });
223
224            stridePanel.add(new JLabel("Track Stride:"));
225            stridePanel.add(trkStr);
226            stridePanel.add(new JLabel("Vertical Stride:"));
227            stridePanel.add(vrtStr);
228            stridePanel.add(new JLabel("Length %:"));
229            stridePanel.add(lengthField);
230
231            JPanel selectPanel = new JPanel(new BorderLayout());
232            DefaultBoundedRangeModel brm = new DefaultBoundedRangeModel(trackStart, 0, 0, trackLen);
233            JSlider trackSelect = new JSlider(brm);
234            trackSelect
235                    .setToolTipText("<html>"
236                            + "Sets the location of the track to display (with respect to Length % below). <br>"
237                            + "The slider represents the middle of the length to be plotted.  The left end of <br>"
238                            + "the slider is the beginning of the track, and the right is the end. The portion <br>"
239                            + "of the track to be displayed is outlined in magenta." + "</html>");
240            Hashtable<Integer, JLabel> labelTable = new Hashtable<Integer, JLabel>();
241            labelTable.put(0, new JLabel("Track Start"));
242            labelTable.put(trackLen, new JLabel("Track End"));
243            trackSelect.setLabelTable(labelTable);
244            trackSelect.setPaintLabels(true);
245            trackSelect.addChangeListener(new ChangeListener() {
246                public void stateChanged(ChangeEvent e) {
247                    trackPos = (int) ((JSlider) e.getSource()).getValue();
248                    updateTrackSelect();
249                }
250            });
251            selectPanel.add(trackSelect, BorderLayout.NORTH);
252            selectPanel.add(stridePanel, BorderLayout.SOUTH);
253            panel.add(selectPanel, BorderLayout.SOUTH);
254
255            return panel;
256        } catch (Exception e) {
257            logger.error("error creating contents", e);
258        }
259        return null;
260    }
261
262    public void setTrackStride(int stride) {
263        trackStride = stride;
264    }
265
266    public void setVerticalStride(int stride) {
267        verticalStride = stride;
268    }
269
270    public void setLengthPercent(int percent) {
271        trackLengthPercent = percent;
272        selectWidth = (int) (trackLen * ((float) trackLengthPercent / 100.0f));
273        selectWidth /= 2;
274        updateTrackSelect();
275    }
276
277    /**
278     * Update Track Stride if input text box holds a positive integer.
279     * 
280     * @return true if trackStride was updated
281     */
282
283    public boolean setTrackStride() {
284        boolean setOk = false;
285        try {
286            int newStride = Integer.valueOf(trkStr.getText().trim());
287            if (newStride >= 1) {
288                trackStride = newStride;
289                setOk = true;
290            } else {
291                setOk = false;
292            }
293        } catch (NumberFormatException nfe) {
294            // do nothing, will return correct result code
295        }
296        return setOk;
297    }
298
299    /**
300     * Update Vertical Stride if input text box holds a positive integer.
301     * 
302     * @return true if verticalStride was updated
303     */
304
305    public boolean setVerticalStride() {
306        boolean setOk = false;
307        try {
308            int newStride = Integer.valueOf(vrtStr.getText().trim());
309            if (newStride >= 1) {
310                verticalStride = newStride;
311                setOk = true;
312            } else {
313                setOk = false;
314            }
315        } catch (NumberFormatException nfe) {
316            // do nothing, will return correct result code
317        }
318        return setOk;
319    }
320
321    /**
322     * Update Track Length percentage if input text box holds a positive integer
323     * between 1 and 100.
324     * 
325     * @return true if trackLengthPercent was updated
326     */
327
328    public boolean setLengthPercent() {
329        boolean setOk = false;
330        try {
331            int newWidth = Integer.valueOf(lengthField.getText().trim());
332            if ((newWidth > 0) && (newWidth <= 100)) {
333                trackLengthPercent = newWidth;
334                setOk = true;
335            } 
336        } catch (NumberFormatException nfe) {
337            // do nothing, will return correct result code
338        }
339        return setOk;
340    }
341
342    void updateTrackSelect() {
343        trackStart = trackPos - selectWidth;
344        if (trackStart < 0)
345            trackStart = 0;
346
347        trackStop = trackPos + selectWidth;
348        if (trackStop >= trackLen)
349            trackStop = trackLen - 1;
350
351        try {
352            Gridded2DSet trck = new Gridded2DSet(RealTupleType.SpatialEarth2DTuple, new float[][] {
353                    java.util.Arrays.copyOfRange(trackLocs[0], trackStart, trackStop),
354                    java.util.Arrays.copyOfRange(trackLocs[1], trackStart, trackStop) },
355                    (trackStop - trackStart));
356            trackSelectDsp.setData(trck);
357        } catch (Exception exc) {
358            logger.trace("Problem creating Gridded2DSet", exc);
359        }
360    }
361
362    /**
363     * Apply new settings
364     */
365
366    public void applyToDataSelection(DataSelection dataSelection) {
367        setTrackStride();
368        setVerticalStride();
369        setLengthPercent();
370
371        HashMap subset = (HashMap) ((HashMap) defaultSubset).clone();
372        double[] coords = (double[]) subset.get(ProfileAlongTrack.vertDim_name);
373
374        subset.put(ProfileAlongTrack.trackDim_name, new double[] { trackStart, trackStop,
375                trackStride });
376        subset.put(ProfileAlongTrack.vertDim_name, new double[] { coords[0], coords[1],
377                verticalStride });
378
379        MultiDimensionSubset select = new MultiDimensionSubset(subset);
380
381        Hashtable table = dataChoice.getProperties();
382        table.put(MultiDimensionSubset.key, select);
383
384        table = dataSelection.getProperties();
385        table.put(MultiDimensionSubset.key, select);
386
387        dataChoice.setDataSelection(dataSelection);
388
389    }
390
391}