001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 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 package edu.wisc.ssec.mcidasv.probes; 029 030 import static edu.wisc.ssec.mcidasv.util.Contract.*; 031 032 import java.awt.Color; 033 import java.beans.PropertyChangeEvent; 034 import java.beans.PropertyChangeListener; 035 import java.rmi.RemoteException; 036 import java.text.DecimalFormat; 037 import java.util.concurrent.CopyOnWriteArrayList; 038 039 import ucar.unidata.collab.SharableImpl; 040 import ucar.unidata.util.LogUtil; 041 import ucar.unidata.view.geoloc.NavigatedDisplay; 042 import ucar.visad.ShapeUtility; 043 import ucar.visad.display.DisplayMaster; 044 import ucar.visad.display.LineProbe; 045 import ucar.visad.display.SelectorDisplayable; 046 import ucar.visad.display.TextDisplayable; 047 048 import visad.Data; 049 import visad.FlatField; 050 import visad.MathType; 051 import visad.Real; 052 import visad.RealTuple; 053 import visad.RealTupleType; 054 import visad.Text; 055 import visad.TextType; 056 import visad.Tuple; 057 import visad.TupleType; 058 import visad.VisADException; 059 import visad.georef.EarthLocationTuple; 060 061 public class ReadoutProbe extends SharableImpl implements PropertyChangeListener { 062 063 public static final String SHARE_PROFILE = "ReadoutProbeDeux.SHARE_PROFILE"; 064 065 public static final String SHARE_POSITION = "ReadoutProbeDeux.SHARE_POSITION"; 066 067 private static final Color DEFAULT_COLOR = Color.MAGENTA; 068 069 private static final TupleType TUPTYPE = makeTupleType(); 070 071 private final CopyOnWriteArrayList<ProbeListener> listeners = 072 new CopyOnWriteArrayList<ProbeListener>(); 073 074 /** Displays the value of the data at the current position. */ 075 private final TextDisplayable valueDisplay = createValueDisplay(DEFAULT_COLOR); 076 077 private final LineProbe probe = new LineProbe(getInitialLinePosition()); 078 079 private final DisplayMaster master; 080 081 private Color currentColor = DEFAULT_COLOR; 082 083 private String currentValue = "NaN"; 084 085 private double currentLatitude = Double.NaN; 086 private double currentLongitude = Double.NaN; 087 088 private float pointSize = 1.0f; 089 090 private FlatField field; 091 092 private static final DecimalFormat numFmt = new DecimalFormat(); 093 094 private RealTuple prevPos = null; 095 096 public ReadoutProbe(final DisplayMaster master, final FlatField field, final Color color, final boolean visible) throws VisADException, RemoteException { 097 super(); 098 notNull(master, "DisplayMaster can't be null"); 099 notNull(field, "Field can't be null"); 100 notNull(color, "Color can't be null"); 101 102 this.master = master; 103 this.field = field; 104 105 initSharable(); 106 107 probe.setColor(color); 108 valueDisplay.setVisible(visible); 109 valueDisplay.setColor(color); 110 currentColor = color; 111 probe.setVisible(visible); 112 probe.setPointSize(pointSize); 113 probe.setAutoSize(true); 114 probe.addPropertyChangeListener(this); 115 probe.setPointSize(getDisplayScale()); 116 117 numFmt.setMaximumFractionDigits(2); 118 119 master.addDisplayable(valueDisplay); 120 master.addDisplayable(probe); 121 setField(field); 122 } 123 124 /** 125 * Called whenever the probe fires off a {@link PropertyChangeEvent}. Only 126 * handles position changes right now, all other events are discarded. 127 * 128 * @param e Object that describes the property change. 129 * 130 * @throws NullPointerException if passed a {@code null} 131 * {@code PropertyChangeEvent}. 132 */ 133 public void propertyChange(final PropertyChangeEvent e) { 134 notNull(e, "Cannot handle a null property change event"); 135 if (e.getPropertyName().equals(SelectorDisplayable.PROPERTY_POSITION)) { 136 RealTuple prev = getEarthPosition(); 137 //handleProbeUpdate(); 138 RealTuple current = getEarthPosition(); 139 if (prevPos != null) { 140 fireProbePositionChanged(prev, current); 141 handleProbeUpdate(); 142 } 143 prevPos = current; 144 //fireProbePositionChanged(prev, current); 145 } 146 } 147 148 public void setField(final FlatField field) { 149 notNull(field); 150 this.field = field; 151 handleProbeUpdate(); 152 } 153 154 /** 155 * Adds a {@link ProbeListener} to the listener list so that it can be 156 * notified when the probe is changed. 157 * 158 * @param listener {@code ProbeListener} to register. {@code null} 159 * listeners are not allowed. 160 * 161 * @throws NullPointerException if {@code listener} is null. 162 */ 163 public void addProbeListener(final ProbeListener listener) { 164 notNull(listener, "Can't add a null listener"); 165 listeners.add(listener); 166 } 167 168 /** 169 * Removes a {@link ProbeListener} from the notification list. 170 * 171 * @param listener {@code ProbeListener} to remove. {@code null} values 172 * are permitted, but since they are not allowed to be added... 173 */ 174 public void removeProbeListener(final ProbeListener listener) { 175 listeners.remove(listener); 176 } 177 178 public boolean hasListener(final ProbeListener listener) { 179 return listeners.contains(listener); 180 } 181 182 /** 183 * Notifies the registered {@link ProbeListener}s that this probe's 184 * position has changed. 185 * 186 * @param previous Previous position. 187 * @param current Current position. 188 */ 189 protected void fireProbePositionChanged(final RealTuple previous, final RealTuple current) { 190 notNull(previous); 191 notNull(current); 192 193 ProbeEvent<RealTuple> event = new ProbeEvent<RealTuple>(this, previous, current); 194 for (ProbeListener listener : listeners) 195 listener.probePositionChanged(event); 196 } 197 198 /** 199 * Notifies the registered {@link ProbeListener}s that this probe's color 200 * has changed. 201 * 202 * @param previous Previous color. 203 * @param current Current color. 204 */ 205 protected void fireProbeColorChanged(final Color previous, final Color current) { 206 notNull(previous); 207 notNull(current); 208 209 ProbeEvent<Color> event = new ProbeEvent<Color>(this, previous, current); 210 for (ProbeListener listener : listeners) 211 listener.probeColorChanged(event); 212 } 213 214 /** 215 * Notifies registered {@link ProbeListener}s that this probe's visibility 216 * has changed. Only takes a {@literal "previous"} value, which is negated 217 * to form the {@literal "current"} value. 218 * 219 * @param previous Visibility <b>before</b> change. 220 */ 221 protected void fireProbeVisibilityChanged(final boolean previous) { 222 ProbeEvent<Boolean> event = new ProbeEvent<Boolean>(this, previous, !previous); 223 for (ProbeListener listener : listeners) 224 listener.probeVisibilityChanged(event); 225 } 226 227 public void setColor(final Color color) { 228 notNull(color, "Cannot set a probe to a null color"); 229 setColor(color, false); 230 } 231 232 private void setColor(final Color color, final boolean quietly) { 233 assert color != null; 234 235 if (currentColor.equals(color)) 236 return; 237 238 try { 239 probe.setColor(color); 240 valueDisplay.setColor(color); 241 Color prev = currentColor; 242 currentColor = color; 243 244 if (!quietly) 245 fireProbeColorChanged(prev, currentColor); 246 } catch (Exception e) { 247 LogUtil.logException("Couldn't set the color of the probe", e); 248 } 249 } 250 251 public Color getColor() { 252 return currentColor; 253 } 254 255 public String getValue() { 256 return currentValue; 257 } 258 259 public double getLatitude() { 260 return currentLatitude; 261 } 262 263 public double getLongitude() { 264 return currentLongitude; 265 } 266 267 public void setLatLon(final Double latitude, final Double longitude) { 268 notNull(latitude, "Null latitude values don't make sense!"); 269 notNull(longitude, "Null longitude values don't make sense!"); 270 271 try { 272 EarthLocationTuple elt = new EarthLocationTuple(latitude, longitude, 0.0); 273 double[] tmp = ((NavigatedDisplay)master).getSpatialCoordinates(elt, null); 274 probe.setPosition(tmp[0], tmp[1]); 275 } catch (Exception e) { 276 LogUtil.logException("Failed to set the probe's position", e); 277 } 278 } 279 280 public void quietlySetVisible(final boolean visibility) { 281 try { 282 probe.setVisible(visibility); 283 valueDisplay.setVisible(visibility); 284 } catch (Exception e) { 285 LogUtil.logException("Couldn't set the probe's internal visibility", e); 286 } 287 } 288 289 public void quietlySetColor(final Color newColor) { 290 setColor(newColor, true); 291 } 292 293 public void handleProbeUpdate() { 294 RealTuple pos = getEarthPosition(); 295 if (pos == null) 296 return; 297 298 Tuple positionValue = valueAtPosition(pos, field); 299 if (positionValue == null) 300 return; 301 302 try { 303 valueDisplay.setData(positionValue); 304 } catch (Exception e) { 305 LogUtil.logException("Failed to set readout value", e); 306 } 307 } 308 309 public void handleProbeRemoval() { 310 listeners.clear(); 311 try { 312 master.removeDisplayable(valueDisplay); 313 master.removeDisplayable(probe); 314 } catch (Exception e) { 315 LogUtil.logException("Problem removing visible portions of readout probe", e); 316 } 317 currentColor = null; 318 field = null; 319 } 320 321 /** 322 * Get the scaling factor for probes and such. The scaling is 323 * the parameter that gets passed to TextControl.setSize() and 324 * ShapeControl.setScale(). 325 * 326 * @return ratio of the current matrix scale factor to the 327 * saved matrix scale factor. 328 */ 329 public float getDisplayScale() { 330 float scale = 1.0f; 331 try { 332 scale = master.getDisplayScale(); 333 } catch (Exception e) { 334 System.err.println("Error getting display scale: "+e); 335 } 336 return scale; 337 } 338 339 public void setXYPosition(final RealTuple position) { 340 if (position == null) 341 throw new NullPointerException("cannot use a null position"); 342 343 try { 344 probe.setPosition(position); 345 } catch (Exception e) { 346 LogUtil.logException("Had problems setting probe's xy position", e); 347 } 348 } 349 350 public RealTuple getXYPosition() { 351 RealTuple position = null; 352 try { 353 position = probe.getPosition(); 354 } catch (Exception e) { 355 LogUtil.logException("Could not determine the probe's xy location", e); 356 } 357 return position; 358 } 359 360 public EarthLocationTuple getEarthPosition() { 361 EarthLocationTuple earthTuple = null; 362 try { 363 double[] values = probe.getPosition().getValues(); 364 earthTuple = (EarthLocationTuple)((NavigatedDisplay)master).getEarthLocation(values[0], values[1], 1.0, true); 365 currentLatitude = earthTuple.getLatitude().getValue(); 366 currentLongitude = earthTuple.getLongitude().getValue(); 367 } catch (Exception e) { 368 LogUtil.logException("Could not determine the probe's earth location", e); 369 } 370 return earthTuple; 371 } 372 373 private Tuple valueAtPosition(final RealTuple position, final FlatField imageData) { 374 assert position != null : "Cannot provide a null position"; 375 assert imageData != null : "Cannot provide a null image"; 376 377 double[] values = position.getValues(); 378 if (values[1] < -180) 379 values[1] += 360f; 380 381 if (values[0] > 180) 382 values[0] -= 360f; 383 384 Tuple positionTuple = null; 385 try { 386 // TODO(jon): do the positionFormat stuff in here. maybe this'll 387 // have to be an instance method? 388 RealTuple corrected = new RealTuple(RealTupleType.SpatialEarth2DTuple, new double[] { values[1], values[0] }); 389 390 Real realVal = (Real)imageData.evaluate(corrected, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS); 391 float val = (float)realVal.getValue(); 392 if (Float.isNaN(val)) 393 currentValue = "NaN"; 394 else 395 currentValue = numFmt.format(realVal.getValue()); 396 397 positionTuple = new Tuple(TUPTYPE, new Data[] { corrected, new Text(TextType.Generic, currentValue) }); 398 } catch (Exception e) { 399 LogUtil.logException("Encountered trouble when determining value at probe position", e); 400 } 401 return positionTuple; 402 } 403 404 private static RealTuple getInitialLinePosition() { 405 RealTuple position = null; 406 try { 407 double[] center = new double[] { 0.0, 0.0 }; 408 position = new RealTuple(RealTupleType.SpatialCartesian2DTuple, 409 new double[] { center[0], center[1] }); 410 } catch (Exception e) { 411 LogUtil.logException("Problem with finding an initial probe position", e); 412 } 413 return position; 414 } 415 416 private static TextDisplayable createValueDisplay(final Color color) { 417 assert color != null; 418 419 DecimalFormat fmt = new DecimalFormat(); 420 fmt.setMaximumIntegerDigits(3); 421 fmt.setMaximumFractionDigits(1); 422 423 TextDisplayable td = null; 424 try { 425 td = new TextDisplayable(TextType.Generic); 426 td.setLineWidth(2f); 427 td.setColor(color); 428 td.setNumberFormat(fmt); 429 } catch (Exception e) { 430 LogUtil.logException("Problem creating readout value container", e); 431 } 432 return td; 433 } 434 435 private static TupleType makeTupleType() { 436 TupleType t = null; 437 try { 438 t = new TupleType(new MathType[] { RealTupleType.SpatialEarth2DTuple, TextType.Generic }); 439 } catch (Exception e) { 440 LogUtil.logException("Problem creating readout tuple type", e); 441 } 442 return t; 443 } 444 445 /** 446 * Returns a brief summary of a ReadoutProbe. Please note that this format 447 * is subject to change. 448 * 449 * @return String that looks like {@code [ReadProbe@HASHCODE: color=..., 450 * latitude=..., longitude=..., value=...]} 451 */ 452 public String toString() { 453 return String.format("[ReadoutProbe@%x: color=%s, latitude=%s, longitude=%s, value=%f]", 454 hashCode(), getColor(), getLatitude(), getLongitude(), getValue()); 455 } 456 }