Former-commit-id:580e2938d7
[formerly66be3bec40
] [formerlyc83e5ff474
[formerly 2a9569942c48542cf708b6c0b9189146fd954c11]] Former-commit-id:c83e5ff474
Former-commit-id:1faae42484
1126 lines
No EOL
34 KiB
Java
1126 lines
No EOL
34 KiB
Java
/**
|
|
* This software was developed and / or modified by Raytheon Company,
|
|
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
|
|
*
|
|
* U.S. EXPORT CONTROLLED TECHNICAL DATA
|
|
* This software product contains export-restricted data whose
|
|
* export/transfer/disclosure is restricted by U.S. law. Dissemination
|
|
* to non-U.S. persons whether in the United States or abroad requires
|
|
* an export license or other authorization.
|
|
*
|
|
* Contractor Name: Raytheon Company
|
|
* Contractor Address: 6825 Pine Street, Suite 340
|
|
* Mail Stop B8
|
|
* Omaha, NE 68106
|
|
* 402.291.0100
|
|
*
|
|
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
|
|
* further licensing information.
|
|
**/
|
|
package com.raytheon.uf.common.sounding;
|
|
|
|
import static com.raytheon.uf.common.sounding.SoundingLayer.MISSING;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.Iterator;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
|
|
import com.raytheon.uf.common.pointdata.spatial.SurfaceObsLocation;
|
|
import com.raytheon.uf.common.sounding.SoundingLayer.DATA_TYPE;
|
|
import com.raytheon.uf.common.time.DataTime;
|
|
import com.vividsolutions.jts.geom.Geometry;
|
|
|
|
/**
|
|
* VerticalSounding is an ordered collection of SoundingLayers sorted by
|
|
* pressure, where the highest pressure is at the beginning of the collection.
|
|
*
|
|
* <pre>
|
|
* SOFTWARE HISTORY
|
|
*
|
|
* Date Ticket# Engineer Description
|
|
* ------------ ---------- ----------- --------------------------
|
|
* 06 Nov 2006 jkorman Initial Coding
|
|
* 20071127 382 jkorman Moved from Cave graphing.
|
|
* 15Jan2008 682 ebabin Updated to remove non calculated parameters.
|
|
* 16Jan2008 682 ebabin Updates for grib model traps on multiple loads.
|
|
* 04Oct2008 dhladky Many changes.
|
|
* </pre>
|
|
*
|
|
* @author jkorman
|
|
* @version 1.0
|
|
*/
|
|
public class VerticalSounding implements Iterable<SoundingLayer>, Cloneable {
|
|
|
|
private static final float FREEZING = 273.15f;
|
|
|
|
/**
|
|
* Is the value not null and above the missing/invalid values.
|
|
*
|
|
* @param value
|
|
* A value to check.
|
|
* @return Is the value valid.
|
|
*/
|
|
protected static boolean isValid(double value) {
|
|
return value < MISSING;
|
|
}
|
|
|
|
// *************************************************
|
|
private SurfaceObsLocation spatialInfo;
|
|
|
|
private Calendar obsTime = null;
|
|
|
|
private DataTime dataTime = null;
|
|
|
|
private List<SoundingLayer> layerData = new ArrayList<SoundingLayer>();
|
|
|
|
private Map<Float, SoundingLayer> layerMap = new HashMap<Float, SoundingLayer>();
|
|
|
|
private List<SoundingLayer> maxWinds = new ArrayList<SoundingLayer>();
|
|
|
|
private SoundingLayer sfcLayer = null;
|
|
|
|
private String displayFormat;
|
|
|
|
// *************************************************
|
|
// * Computed data.
|
|
// *************************************************
|
|
private SoundingLayer maxTemperatureLayer = null;
|
|
|
|
private SoundingLayer minTemperatureLayer = null;
|
|
|
|
private boolean initialLoad = true;
|
|
|
|
private String name = "";
|
|
|
|
/**
|
|
* Create an empty sounding.
|
|
*/
|
|
public VerticalSounding() {
|
|
}
|
|
|
|
/**
|
|
* @return the displayFormat
|
|
*/
|
|
public String getDisplayFormat() {
|
|
return displayFormat;
|
|
}
|
|
|
|
/**
|
|
* @param displayFormat
|
|
* the displayFormat to set
|
|
*/
|
|
public void setDisplayFormat(String displayFormat) {
|
|
this.displayFormat = displayFormat;
|
|
}
|
|
|
|
/*
|
|
* (non-Javadoc)
|
|
*
|
|
* @see java.lang.Object#clone()
|
|
*/
|
|
@Override
|
|
public Object clone() throws CloneNotSupportedException {
|
|
VerticalSounding clone = (VerticalSounding) super.clone();
|
|
clone.layerData = new ArrayList<SoundingLayer>();
|
|
clone.layerMap = new HashMap<Float, SoundingLayer>();
|
|
clone.maxWinds = new ArrayList<SoundingLayer>();
|
|
clone.spatialInfo = (SurfaceObsLocation) spatialInfo.clone();
|
|
clone.setInitialLoad(true);
|
|
|
|
for (SoundingLayer layer : layerData) {
|
|
clone.addLayer((SoundingLayer) layer.clone());
|
|
}
|
|
clone.invalidate();
|
|
|
|
return clone;
|
|
}
|
|
|
|
/**
|
|
* Check to see if a surface layer exists. If it does not then find the
|
|
* lowest layer and set this as the surface.
|
|
*/
|
|
public void checkSfcLayer() {
|
|
|
|
// Did the data declare a surface layer?
|
|
if (sfcLayer == null) {
|
|
// Did we set Elevation? If so use that to calculate the surface
|
|
// layer.
|
|
if (this.getElevation() != null) {
|
|
int press = (int) WxMath.heightToPressure(this.getElevation());
|
|
sfcLayer = this.getLayerNearest(press);
|
|
if (press != sfcLayer.getPressure()) {
|
|
SoundingLayer sLayer = new SoundingLayer();
|
|
sLayer.setGeoHeight(this.getElevation());
|
|
sLayer.setPressure(press);
|
|
sLayer.setTemperature((float) (Math.pow(
|
|
press / sfcLayer.getPressure(), 0.286) * sfcLayer
|
|
.getTemperature()));
|
|
sLayer.setWindU(sfcLayer.getWindU());
|
|
sLayer.setWindV(sfcLayer.getWindV());
|
|
sLayer.setWindDirection(sfcLayer.getWindDirection());
|
|
sLayer.setWindSpeed(sfcLayer.getWindSpeed());
|
|
sfcLayer = sLayer;
|
|
addLayer(sfcLayer);
|
|
}
|
|
} else {
|
|
SoundingLayer lowestLayer = new SoundingLayer();
|
|
// Start at 1 hPa. We won't consider anything higher that that.
|
|
lowestLayer.setPressure(1.0f);
|
|
for (SoundingLayer layer : layerData) {
|
|
if (layer.isLowerThan(lowestLayer)) {
|
|
lowestLayer = layer;
|
|
}
|
|
}
|
|
sfcLayer = lowestLayer;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get the surface layer if defined.
|
|
*/
|
|
public SoundingLayer getSfcLayer() {
|
|
return sfcLayer;
|
|
}
|
|
|
|
/**
|
|
* @return the layerData
|
|
*/
|
|
public List<SoundingLayer> getLayerData() {
|
|
return layerData;
|
|
}
|
|
|
|
/**
|
|
*/
|
|
public void removeBelowSfcLayers() {
|
|
if (layerData != null) {
|
|
if (sfcLayer != null) {
|
|
Iterator<SoundingLayer> layers = layerData.iterator();
|
|
while (layers.hasNext()) {
|
|
SoundingLayer layer = layers.next();
|
|
if (layer.isLowerThan(sfcLayer)) {
|
|
layers.remove();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private void rebuildMap() {
|
|
layerMap.clear();
|
|
for (SoundingLayer layer : layerData) {
|
|
layerMap.put(new Float(layer.getPressure()), layer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a layer to this sounding.
|
|
*
|
|
* @param layer
|
|
* A sounding layer to add.
|
|
*/
|
|
/**
|
|
* Add a layer to this sounding.
|
|
*
|
|
* @param layer
|
|
* A sounding layer to add.
|
|
*/
|
|
public void addLayer(SoundingLayer layer) {
|
|
if (okToAdd(layer)) {
|
|
sortLayerIntoSounding(layer);
|
|
if (layer.getPressure() != MISSING) {
|
|
// update values
|
|
SoundingLayer currLayer = layerMap.get(layer.getPressure());
|
|
if (currLayer != null) {
|
|
if (layer.getTemperature() != MISSING) {
|
|
currLayer.setTemperature(layer.getTemperature());
|
|
}
|
|
if (layer.getDewpoint() != MISSING) {
|
|
currLayer.setDewpoint(layer.getDewpoint());
|
|
}
|
|
if (layer.getWindDirection() != MISSING) {
|
|
currLayer.setWindDirection(layer.getWindDirection());
|
|
}
|
|
if (layer.getWindSpeed() != MISSING) {
|
|
currLayer.setWindSpeed(layer.getWindSpeed());
|
|
}
|
|
} else {
|
|
layerMap.put(new Float(layer.getPressure()), layer);
|
|
}
|
|
}
|
|
|
|
invalidate();
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param layers
|
|
*/
|
|
public void addLayers(List<SoundingLayer> layers) {
|
|
initialLoad = true;
|
|
layerData.clear();
|
|
layerMap.clear();
|
|
for (SoundingLayer layer : layers) {
|
|
addLayer(layer);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
public void invalidate() {
|
|
if (!isInitialLoad()) {
|
|
sortByPressure();
|
|
rebuildMap();
|
|
updateMaxMinTemp();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks a layer to be inserted to determine if a layer with the same
|
|
* pressure already exists.
|
|
*
|
|
* @param layer
|
|
* The layer to be added.
|
|
*/
|
|
private boolean okToAdd(SoundingLayer layer) {
|
|
boolean shouldAdd = false;
|
|
if (layer != null) {
|
|
float p = layer.getPressure();
|
|
if ((p != MISSING) && (layer.getLayerType() != null)) {
|
|
SoundingLayer currLayer = layerMap.get(p);
|
|
if (currLayer != null) {
|
|
switch (layer.getLayerType()) {
|
|
case SURFACE: {
|
|
if (LayerType.MAN_PRESSURE.equals(currLayer
|
|
.getLayerType())) {
|
|
int i = layerData.indexOf(currLayer);
|
|
layerData.remove(i);
|
|
layerMap.remove(currLayer.getPressure());
|
|
shouldAdd = true;
|
|
} else if (LayerType.SIG_PRESSURE.equals(currLayer
|
|
.getLayerType())) {
|
|
int i = layerData.indexOf(currLayer);
|
|
layerData.remove(i);
|
|
layerMap.remove(currLayer.getPressure());
|
|
shouldAdd = true;
|
|
}
|
|
break;
|
|
}
|
|
case MAN_PRESSURE: {
|
|
if (LayerType.SIG_PRESSURE.equals(currLayer
|
|
.getLayerType())) {
|
|
int i = layerData.indexOf(currLayer);
|
|
layerData.remove(i);
|
|
layerMap.remove(currLayer.getPressure());
|
|
shouldAdd = true;
|
|
}
|
|
break;
|
|
}
|
|
case SIG_PRESSURE: {
|
|
// TODO : Make sure to check other layer types.
|
|
break;
|
|
}
|
|
default: {
|
|
shouldAdd = true;
|
|
}
|
|
} // end switch
|
|
|
|
} else {
|
|
shouldAdd = true;
|
|
}
|
|
} else {
|
|
layerMap.put(new Float(layer.getPressure()), layer);
|
|
}
|
|
}
|
|
return shouldAdd;
|
|
}
|
|
|
|
/**
|
|
* Remove Data layer
|
|
*
|
|
* @param layer
|
|
*/
|
|
public void removeLayer(SoundingLayer layer) {
|
|
|
|
if (layerMap.containsKey(layer.getPressure())) {
|
|
layerMap.remove(layer.getPressure());
|
|
}
|
|
layerData.remove(layer);
|
|
invalidate();
|
|
}
|
|
|
|
/**
|
|
* Gets the layer at the specified pressure level
|
|
*
|
|
* @param level
|
|
* @return the layer
|
|
*/
|
|
public SoundingLayer getLayer(float level) {
|
|
return layerMap.get(level);
|
|
}
|
|
|
|
/**
|
|
* Get the layer at the specified index
|
|
*
|
|
* @param index
|
|
* @return the layer
|
|
*/
|
|
public SoundingLayer get(int index) {
|
|
return layerData.get(index);
|
|
}
|
|
|
|
/**
|
|
* Sort a layer into this sounding. The primary sort key is the layer
|
|
* pressure. If pressure is not available, the geopotential height is used.
|
|
*
|
|
* @param layer
|
|
* The layer that is being added.
|
|
*/
|
|
private void sortLayerIntoSounding(SoundingLayer layer) {
|
|
// If empty, just add the layer.
|
|
if (LayerType.MAX_WIND.equals(layer.getLayerType())) {
|
|
maxWinds.add(layer);
|
|
return;
|
|
}
|
|
if (LayerType.SURFACE.equals(layer.getLayerType())) {
|
|
sfcLayer = layer;
|
|
}
|
|
if (layerData.size() == 0) {
|
|
layerData.add(layer);
|
|
} else {
|
|
boolean entered = false;
|
|
for (int i = 0; i < layerData.size(); i++) {
|
|
SoundingLayer cLayer = layerData.get(i);
|
|
if (layer.isLowerThan(cLayer)) {
|
|
layerData.add(i, layer);
|
|
entered = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!entered) {
|
|
layerData.add(layer);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the initialLoad boolean
|
|
*
|
|
* @return
|
|
*/
|
|
public boolean isInitialLoad() {
|
|
return initialLoad;
|
|
}
|
|
|
|
/**
|
|
* Sets the initialLoad param
|
|
*
|
|
* @param initalLoad
|
|
*/
|
|
public void setInitialLoad(boolean initialLoad) {
|
|
this.initialLoad = initialLoad;
|
|
}
|
|
|
|
/**
|
|
* Return the layer closest to a given pressure.
|
|
*
|
|
* @param pressure
|
|
* The target pressure.
|
|
* @return The SoundingLayer instance nearest to the target.
|
|
*/
|
|
public SoundingLayer getLayerNearest(float pressure) {
|
|
SoundingLayer retLayer = new SoundingLayer(pressure, MISSING, MISSING,
|
|
MISSING, MISSING, MISSING, MISSING);
|
|
|
|
if (layerData.size() > 0) {
|
|
retLayer = layerData.get(0);
|
|
if (layerData.size() == 1) {
|
|
return retLayer;
|
|
}
|
|
|
|
SoundingLayer loLayer = null;
|
|
SoundingLayer hiLayer = null;
|
|
SoundingLayer sLayer = new SoundingLayer(pressure, MISSING,
|
|
MISSING, MISSING, MISSING, MISSING, MISSING);
|
|
int hiIndex = 0;
|
|
for (int i = 0; i < layerData.size(); i++) {
|
|
SoundingLayer layer = layerData.get(i);
|
|
if (layer.getPressure() == pressure) {
|
|
return layer;
|
|
} else if (layer.isHigherThan(sLayer)) {
|
|
// Make sure we have a lower pressure level.
|
|
if (layer.getPressure() < MISSING) {
|
|
hiLayer = layer;
|
|
hiIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if (hiIndex > 0) {
|
|
SoundingLayer layer = layerData.get(0);
|
|
loLayer = layer;
|
|
|
|
for (int i = hiIndex - 1; i > 0; i--) {
|
|
layer = layerData.get(i);
|
|
if (layer.isLowerThan(sLayer)) {
|
|
// Make sure we have a lower pressure level.
|
|
if (layer.getPressure() < MISSING) {
|
|
loLayer = layer;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
if (pressure > layerData.get(0).getPressure()) {
|
|
retLayer = layerData.get(0);
|
|
} else {
|
|
retLayer = layerData.get(layerData.size() - 1);
|
|
}
|
|
}
|
|
|
|
if ((loLayer != null) && (hiLayer != null)) {
|
|
double hPoint = (loLayer.getPressure() + hiLayer.getPressure()) / 2;
|
|
|
|
if (pressure < hPoint) {
|
|
retLayer = hiLayer;
|
|
} else {
|
|
retLayer = loLayer;
|
|
}
|
|
}
|
|
}
|
|
return retLayer;
|
|
}
|
|
|
|
/**
|
|
* Return the layer closest to a given windspeed and winddir.
|
|
*
|
|
* @param windspeed
|
|
* , winddir The target pressure.
|
|
* @return The SoundingLayer instance nearest to the target.
|
|
*/
|
|
public SoundingLayer getLayerNearest(float windspeed, float winddir) {
|
|
SoundingLayer retLayer = null;
|
|
SoundingLayer loLayer = null;
|
|
SoundingLayer hiLayer = null;
|
|
SoundingLayer sLayer = new SoundingLayer(MISSING, MISSING, MISSING,
|
|
MISSING, windspeed, winddir, MISSING);
|
|
int hiIndex = 0;
|
|
|
|
// Check and find closest wind speed
|
|
for (int i = 0; i < layerData.size(); i++) {
|
|
SoundingLayer layer = layerData.get(i);
|
|
if (layer.getWindSpeed() == sLayer.getWindSpeed()
|
|
|
|
&& layer.getWindDirection() == sLayer.getWindDirection()) {
|
|
return layer;
|
|
} else if ((layer.getWindSpeed() > sLayer.getWindSpeed())
|
|
&& layer.getWindSpeed() != 99999.0) {
|
|
hiLayer = layer;
|
|
hiIndex = i;
|
|
break;
|
|
}
|
|
}
|
|
if (hiIndex > 0) {
|
|
for (int i = hiIndex + 1; i < layerData.size(); i++) {
|
|
SoundingLayer layer = layerData.get(i);
|
|
if (layer.getWindSpeed() < sLayer.getWindSpeed()) {
|
|
loLayer = layer;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
if (windspeed > layerData.get(0).getPressure()) {
|
|
retLayer = layerData.get(0);
|
|
} else {
|
|
retLayer = layerData.get(layerData.size() - 1);
|
|
}
|
|
}
|
|
|
|
if ((loLayer != null) && (hiLayer != null)) {
|
|
double hPoint = (loLayer.getWindSpeed() + hiLayer.getWindSpeed()) / 2;
|
|
|
|
if (windspeed < hPoint) {
|
|
retLayer = hiLayer;
|
|
} else {
|
|
retLayer = loLayer;
|
|
}
|
|
}
|
|
return retLayer;
|
|
}
|
|
|
|
/**
|
|
* Get an iterator to the internal layer data.
|
|
*
|
|
* @return The layer data iterator.
|
|
*/
|
|
public Iterator<SoundingLayer> iterator() {
|
|
return layerData.iterator();
|
|
}
|
|
|
|
/**
|
|
* Get this observation's geometry.
|
|
*
|
|
* @return The geometry for this observation.
|
|
*/
|
|
public Geometry getObsGeometry() {
|
|
Geometry obsGeometry = null;
|
|
if (spatialInfo != null) {
|
|
obsGeometry = spatialInfo.getGeometry();
|
|
}
|
|
return obsGeometry;
|
|
}
|
|
|
|
/**
|
|
* Get the geometry latitude.
|
|
*
|
|
* @return The geometry latitude.
|
|
*/
|
|
public double getLatitude() {
|
|
return spatialInfo.getLatitude();
|
|
}
|
|
|
|
/**
|
|
* Get the geometry longitude.
|
|
*
|
|
* @return The geometry longitude.
|
|
*/
|
|
public double getLongitude() {
|
|
return spatialInfo.getLongitude();
|
|
}
|
|
|
|
/**
|
|
* Set the geometry latitude.
|
|
*
|
|
*/
|
|
public void setLatitude(double latitude) {
|
|
spatialInfo.setLatitude(latitude);
|
|
}
|
|
|
|
/**
|
|
* Set the geometry longitude.
|
|
*
|
|
*/
|
|
public void setLongitude(double longitude) {
|
|
spatialInfo.setLongitude(longitude);
|
|
}
|
|
|
|
/**
|
|
* Get the station identifier for this observation.
|
|
*
|
|
* @return the stationId
|
|
*/
|
|
public String getStationId() {
|
|
String stationId = null;
|
|
if (spatialInfo != null) {
|
|
stationId = (spatialInfo).getStationId();
|
|
|
|
}
|
|
return stationId;
|
|
}
|
|
|
|
/**
|
|
* Set the station identifier for this observation.
|
|
*
|
|
* @param stationId
|
|
* the stationId to set
|
|
*/
|
|
public void setStationId(String stationId) {
|
|
if (spatialInfo == null) {
|
|
spatialInfo = new SurfaceObsLocation();
|
|
}
|
|
(spatialInfo).setStationId(stationId);
|
|
}
|
|
|
|
/**
|
|
* Get the elevation, in meters, of the observing platform or location.
|
|
*
|
|
* @return The observation elevation, in meters.
|
|
*/
|
|
public Integer getElevation() {
|
|
Integer elevation = null;
|
|
if (spatialInfo != null) {
|
|
elevation = (spatialInfo).getElevation();
|
|
}
|
|
return elevation;
|
|
}
|
|
|
|
/**
|
|
* Set the elevation, in meters, of the observing platform or location.
|
|
*
|
|
* @param elevation
|
|
* The elevation to set
|
|
*/
|
|
public void setElevation(Integer elevation) {
|
|
if (spatialInfo == null) {
|
|
spatialInfo = new SurfaceObsLocation();
|
|
}
|
|
(spatialInfo).setElevation(elevation);
|
|
}
|
|
|
|
/**
|
|
* @return the timeObs
|
|
*/
|
|
public Calendar getObsTime() {
|
|
return obsTime;
|
|
}
|
|
|
|
/**
|
|
* @param timeObs
|
|
* the timeObs to set
|
|
*/
|
|
public void setObsTime(Calendar obsTime) {
|
|
this.obsTime = obsTime;
|
|
}
|
|
|
|
/**
|
|
* @return the dataTime
|
|
*/
|
|
public DataTime getDataTime() {
|
|
return dataTime;
|
|
}
|
|
|
|
/**
|
|
* @param dataTime
|
|
* the dataTime to set
|
|
*/
|
|
public void setDataTime(DataTime dataTime) {
|
|
this.dataTime = dataTime;
|
|
}
|
|
|
|
/**
|
|
* Get the three char ICAO climo station name associated with this sounding.
|
|
*
|
|
* @return The ICAO climo station name.
|
|
*/
|
|
public String getName() {
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Set the three char ICAO climo station name associated with this sounding.
|
|
*
|
|
* @param name
|
|
* The ICAO climo station name.
|
|
*/
|
|
public void setName(String name) {
|
|
this.name = name;
|
|
}
|
|
|
|
// *************************************************
|
|
|
|
/**
|
|
* Gets the maximum pressure layer, always first in array unless added to.
|
|
*
|
|
* @return
|
|
*/
|
|
public SoundingLayer getMaxPressurelayer() {
|
|
int i = 0;
|
|
while (layerData.get(i).getPressure() >= MISSING) {
|
|
i++;
|
|
}
|
|
|
|
return layerData.get(i);
|
|
}
|
|
|
|
/**
|
|
* Returns the top of the sounding layer
|
|
*
|
|
* @return SoundingLayer
|
|
*/
|
|
public SoundingLayer getMinPressureLayer() {
|
|
int i = layerData.size();
|
|
while (layerData.get(--i).getPressure() >= MISSING) {
|
|
}
|
|
|
|
return layerData.get(i);
|
|
}
|
|
|
|
/**
|
|
* Get the layer containing the maximum temperature in the sounding.
|
|
*
|
|
* @return The layer containing the maximum temperature in the sounding.
|
|
*/
|
|
public SoundingLayer getMaxTempLayer() {
|
|
return maxTemperatureLayer;
|
|
}
|
|
|
|
/**
|
|
* Get the layer containing the minimum temperature in the sounding.
|
|
*
|
|
* @return The layer containing the minimum temperature in the sounding.
|
|
*/
|
|
public SoundingLayer getMinTempLayer() {
|
|
return minTemperatureLayer;
|
|
}
|
|
|
|
/**
|
|
* Determine the pressure level of the first occurrence of a specified
|
|
* temperature.
|
|
*
|
|
* @param temperature
|
|
* The temperature to find.
|
|
* @return The pressure of the first occurrence. Returns a null value if the
|
|
* temperature does not exist.
|
|
*/
|
|
// public Double tempLevel(double temperature) {
|
|
// Double temperatureLevel = null;
|
|
// updateMaxMinTemp();
|
|
// // If either of the following layers are null don't bother checking
|
|
// // further.
|
|
// if ((maxTemperatureLayer != null) && (minTemperatureLayer != null)) {
|
|
// // Check if the temperature is in this sounding's range.
|
|
// if (isValid(maxTemperatureLayer.getTemperature())
|
|
// && isValid(minTemperatureLayer.getTemperature())) {
|
|
// if (temperature < maxTemperatureLayer.getTemperature()
|
|
// && temperature > minTemperatureLayer.getTemperature()) {
|
|
// SoundingLayer layerBelow = null;
|
|
// SoundingLayer layerAbove = null;
|
|
//
|
|
// // find the first level that contains a valid temperature.
|
|
// for (SoundingLayer layer : layerData) {
|
|
// if (isValid(layer.getTemperature())) {
|
|
// // we're looking for a base layer
|
|
// if (layerBelow == null) {
|
|
// layerBelow = layer;
|
|
// } else {
|
|
// // we have a base layer, now find the next layer
|
|
// // with a temperature.
|
|
// layerAbove = layer;
|
|
//
|
|
// // now we have two layers with temperature.
|
|
// // Find out if the temperature is here.
|
|
// Double p = isContained(layerBelow, layerAbove,
|
|
// temperature);
|
|
// // we found it.
|
|
// if (isValid(p)) {
|
|
// temperatureLevel = p;
|
|
// break;
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// }
|
|
// return temperatureLevel;
|
|
// }
|
|
/**
|
|
*
|
|
* @param type
|
|
* @return
|
|
*/
|
|
public float[] getValues(DATA_TYPE type) {
|
|
|
|
float f[] = new float[layerData.size()];
|
|
for (int i = 0; i < layerData.size(); i++) {
|
|
f[i] = layerData.get(i).getValue(type);
|
|
}
|
|
return f;
|
|
}
|
|
|
|
public String printableSoundingData() {
|
|
StringBuffer buffer = new StringBuffer();
|
|
buffer.append("START******" + this.getDataTime().getLegendString()
|
|
+ "*******/n");
|
|
for (SoundingLayer l : layerData) {
|
|
buffer.append(l + "\n");
|
|
}
|
|
buffer.append("END ******" + this.getDataTime().getLegendString()
|
|
+ "*******/n");
|
|
return buffer.toString();
|
|
}
|
|
|
|
public SurfaceObsLocation getSpatialInfo() {
|
|
return spatialInfo;
|
|
}
|
|
|
|
public void setSpatialInfo(SurfaceObsLocation spatialInfo) {
|
|
this.spatialInfo = spatialInfo;
|
|
}
|
|
|
|
/**
|
|
* Iterate the sounding to recompute the max and min data. This method must
|
|
* be used when edits have been performed.
|
|
*/
|
|
private void updateMaxMinTemp() {
|
|
SoundingLayer maxTempLayer = null;
|
|
SoundingLayer minTempLayer = null;
|
|
|
|
double minTemp = Double.MAX_VALUE;
|
|
double maxTemp = -Double.MAX_VALUE;
|
|
|
|
for (SoundingLayer layer : layerData) {
|
|
double t = layer.getTemperature();
|
|
if (isValid(t)) {
|
|
if (t > maxTemp) {
|
|
maxTemp = t;
|
|
maxTempLayer = layer;
|
|
}
|
|
if (t < minTemp) {
|
|
minTemp = t;
|
|
minTempLayer = layer;
|
|
}
|
|
}
|
|
}
|
|
maxTemperatureLayer = maxTempLayer;
|
|
minTemperatureLayer = minTempLayer;
|
|
}
|
|
|
|
public int size() {
|
|
return layerData.size();
|
|
}
|
|
|
|
/**
|
|
* Interpolate the desired value at specified pressure
|
|
*
|
|
* @param pressure
|
|
* @param dataType
|
|
* @return the interpolated value
|
|
*/
|
|
public float interpolateValue(float pressure, DATA_TYPE dataType) {
|
|
|
|
// find the bracketing layers
|
|
SoundingLayer layer, prevLayer;
|
|
layer = prevLayer = null;
|
|
float press = (float) Math.floor(pressure);
|
|
for (SoundingLayer l : layerData) {
|
|
if (l.getPressure() < MISSING && l.getValue(dataType) < MISSING) {
|
|
if (Math.floor(l.getPressure()) <= press) {
|
|
layer = l;
|
|
break;
|
|
}
|
|
prevLayer = l;
|
|
}
|
|
}
|
|
|
|
// if bracketing layers not found
|
|
if (layer == null || prevLayer == null) {
|
|
return MISSING;
|
|
}
|
|
|
|
// compute weighting factors
|
|
double w1 = Math.log(prevLayer.getPressure() / layer.getPressure());
|
|
double w2 = Math.log(prevLayer.getPressure() / pressure) / w1;
|
|
w1 = Math.log(pressure / layer.getPressure()) / w1;
|
|
|
|
// interpolate desired value
|
|
double interpVal = w1 * prevLayer.getValue(dataType) + w2
|
|
* layer.getValue(dataType);
|
|
|
|
return (float) interpVal;
|
|
}
|
|
|
|
public void sortByPressure() {
|
|
Collections.sort(layerData, SoundingLayer.getPressureComparator());
|
|
}
|
|
|
|
public float totalTotals() {
|
|
float totals = -9999.0f;
|
|
|
|
// TT = (T850 - T500) + (Td850 - T500)
|
|
SoundingLayer lyr850 = null;
|
|
SoundingLayer lyr500 = null;
|
|
|
|
Float val = new Float(850.0f);
|
|
if (layerMap.containsKey(val)) {
|
|
lyr850 = layerMap.get(val);
|
|
}
|
|
val = new Float(500.0f);
|
|
if (layerMap.containsKey(val)) {
|
|
lyr500 = layerMap.get(val);
|
|
}
|
|
if ((lyr850 != null) && (lyr500 != null)) {
|
|
// 500 temperature is required for cross and vertical totals.
|
|
float t500 = lyr500.getTemperature();
|
|
float t850 = lyr850.getTemperature();
|
|
float td850 = lyr850.getTemperature();
|
|
if (t500 > -9999.0f) {
|
|
if (t850 > -9999.0f) {
|
|
if (td850 > -9999.0f) {
|
|
totals = (t850 - t500) + (td850 - t500);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return totals;
|
|
}
|
|
|
|
public float getWindSpeed700() {
|
|
float ws = -9999.0f;
|
|
|
|
// TT = (T850 - T500) + (Td850 - T500)
|
|
SoundingLayer lyr700 = null;
|
|
|
|
Float val = new Float(700.0f);
|
|
if (layerMap.containsKey(val)) {
|
|
lyr700 = layerMap.get(val);
|
|
}
|
|
if (lyr700 != null) {
|
|
ws = lyr700.getWindSpeed();
|
|
}
|
|
return ws;
|
|
}
|
|
|
|
public float getWindUComp500() {
|
|
float uComp = -9999.0f;
|
|
|
|
SoundingLayer lyr500 = null;
|
|
|
|
Float val = new Float(500.0f);
|
|
if (layerMap.containsKey(val)) {
|
|
lyr500 = layerMap.get(val);
|
|
}
|
|
if (lyr500 != null) {
|
|
float ws = lyr500.getWindSpeed();
|
|
float wd = lyr500.getWindDirection();
|
|
if ((ws >= 0) && (wd > 0) && (wd < MISSING)) {
|
|
uComp = -ws * (float) Math.sin(Math.toRadians(wd));
|
|
|
|
}
|
|
}
|
|
return uComp;
|
|
}
|
|
|
|
/**
|
|
* Find the height in meters of the freezing level. 1. Surface is the
|
|
* freezing level.
|
|
*
|
|
*
|
|
*
|
|
*
|
|
* @return
|
|
*/
|
|
public float firstFreezingLevel() {
|
|
|
|
float fzlLevel = 0;
|
|
|
|
// Create a list of layers that have both temperature and
|
|
// height data.
|
|
List<SoundingLayer> data = new ArrayList<SoundingLayer>();
|
|
for (SoundingLayer layer : layerData) {
|
|
if ((layer.getGeoHeight() != MISSING)
|
|
&& (layer.getTemperature() != MISSING)) {
|
|
data.add(layer);
|
|
}
|
|
}
|
|
if (data.size() > 0) {
|
|
// Scenerio 1 - Freezing level at the surface.
|
|
if (data.get(0).getTemperature() == FREEZING) {
|
|
fzlLevel = getElevation().floatValue();
|
|
} else {
|
|
if (data.size() > 2) {
|
|
for (int i = 1; i < data.size(); i++) {
|
|
float ta = data.get(i).getTemperature();
|
|
float tb = data.get(i - 1).getTemperature();
|
|
float ha = data.get(i).getGeoHeight();
|
|
float hb = data.get(i - 1).getGeoHeight();
|
|
|
|
if ((ta < FREEZING) && (tb > FREEZING)) {
|
|
fzlLevel = interp(hb, tb, ha, ta);
|
|
} else if ((ta > FREEZING) && (tb < FREEZING)) {
|
|
fzlLevel = interp(hb, tb, ha, ta);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return fzlLevel;
|
|
}
|
|
|
|
/**
|
|
* Interpolate the temperature between tlo and thi at heights hlo and hhi
|
|
* respectively.
|
|
*
|
|
* @param hlo
|
|
* @param tlo
|
|
* @param hhi
|
|
* @param thi
|
|
* @return
|
|
*/
|
|
private static final float interp(float hlo, float tlo, float hhi, float thi) {
|
|
return hlo + ((FREEZING - tlo) * (hhi - hlo) / (thi - tlo));
|
|
}
|
|
|
|
public static final void main(String[] args) {
|
|
|
|
VerticalSounding sounding = new VerticalSounding();
|
|
sounding.setElevation(350);
|
|
sounding.setInitialLoad(true);
|
|
|
|
sounding.addLayer(new SoundingLayer(1000.0f, 76f, -9999.0f, -9999.0f,
|
|
-9999.0f, -9999.0f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(969.0f, 350f, 294.3f, 290.73f,
|
|
6.18f, 80f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(925.0f, 757f, 294.1f, 292.7f,
|
|
14.93f, 105f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(850.0f, 1490f, 293.7f, 293.6f,
|
|
11.33f, 195f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(700.0f, 3155f, 285.1f, 277.1f,
|
|
16.99f, 245f, -9999.0f));
|
|
|
|
sounding.addLayer(new SoundingLayer(639.0f, 3910f, 278.3f, 276.3f,
|
|
16.99f, 245f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(611.5f, 4267f, 275.3f, 273.7f,
|
|
16.99f, 245f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(591.0f, 4543f, 273.0f, 271.6f,
|
|
16.99f, 245f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(566.8f, 4877f, 270.4f, 265.9f,
|
|
16.99f, 245f, -9999.0f));
|
|
|
|
sounding.addLayer(new SoundingLayer(500.0f, 5860f, 265.6f, 254.6f,
|
|
11.8f, 295f, -9999.0f));
|
|
sounding.addLayer(new SoundingLayer(493.0f, 5970f, 265.4f, 252.4f, 38f,
|
|
260f, -9999.0f));
|
|
|
|
sounding.setInitialLoad(false);
|
|
sounding.invalidate();
|
|
|
|
for (SoundingLayer layer : sounding) {
|
|
System.out.println(layer);
|
|
}
|
|
System.out.println("Total totals = " + sounding.totalTotals());
|
|
System.out.println("700mb Wind Speed = " + sounding.getWindSpeed700());
|
|
System.out.println("500mb u comp = " + sounding.getWindUComp500());
|
|
System.out.println("Freezing level 1 = "
|
|
+ sounding.firstFreezingLevel());
|
|
|
|
sounding = new VerticalSounding();
|
|
sounding.setElevation(350);
|
|
sounding.setInitialLoad(true);
|
|
|
|
sounding.addLayer(new SoundingLayer(969.0f, 350f, 273.15f, MISSING,
|
|
MISSING, MISSING, MISSING));
|
|
sounding.addLayer(new SoundingLayer(925.0f, 757f, 275f, MISSING,
|
|
MISSING, MISSING, MISSING));
|
|
sounding.addLayer(new SoundingLayer(925.0f, 757f, 278f, MISSING,
|
|
MISSING, MISSING, MISSING));
|
|
|
|
sounding.setInitialLoad(false);
|
|
sounding.invalidate();
|
|
System.out.println("Freezing level 2 = "
|
|
+ sounding.firstFreezingLevel());
|
|
|
|
sounding = new VerticalSounding();
|
|
sounding.setElevation(350);
|
|
sounding.setInitialLoad(true);
|
|
|
|
sounding.addLayer(new SoundingLayer(969.0f, 350f, 263.15f, MISSING,
|
|
MISSING, MISSING, MISSING));
|
|
sounding.addLayer(new SoundingLayer(925.0f, 757f, 271f, MISSING,
|
|
MISSING, MISSING, MISSING));
|
|
sounding.addLayer(new SoundingLayer(925.0f, 1500f, 275f, MISSING,
|
|
MISSING, MISSING, MISSING));
|
|
|
|
sounding.setInitialLoad(false);
|
|
sounding.invalidate();
|
|
System.out.println("Freezing level 3 = "
|
|
+ sounding.firstFreezingLevel());
|
|
|
|
}
|
|
} |