001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 = (Float.valueOf(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}