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