diff --git a/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetAttrDlg.java b/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetAttrDlg.java index 75530c0f06..c96a56552d 100644 --- a/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetAttrDlg.java +++ b/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetAttrDlg.java @@ -101,6 +101,9 @@ import com.vividsolutions.jts.geom.Polygon; * 03/13 #928 B. Yin Made the button bar smaller. * 04/13 #977 S. Gilbert PGEN Database support * 09/13 TTR656 J. Wu Display for INTL_SIGMET converted from VGF. + * 09/14 TTR974 J. Wu update "editableAttrFromLine" in "setSigmet()". + * 10/14 TTR433 J. Wu Set input verification/output format for Phenom Lat/Lon. + * 10/14 TTR722 J. Wu Display TC center/Movement/FL level for ISOLATED TC. * * * @author gzhang @@ -209,6 +212,16 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { private HashMap controlEnablerMap = new HashMap(); + /** + * Colors to indicate if Phenom lat/lon input is in correct format. + */ + private final Color wrongFormatColor = Color.red; + + private final Color rightFormatColor = Color.green; + + /** + * Constructor. + */ protected SigmetAttrDlg(Shell parShell) throws VizException { super(parShell); } @@ -764,11 +777,19 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { @Override public void focusLost(FocusEvent e) { - if (SigmetAttrDlg.this.getEditableAttrPhenomLat() != null) + if (SigmetAttrDlg.this.getEditableAttrPhenomLat() != null) { txtPheLat.setText(SigmetAttrDlg.this .getEditableAttrPhenomLat()); - else - txtPheLat.setText("???"); + setBackgroundColor(txtPheLat, rightFormatColor); + } else { + /* + * "???" causes inconvenience for copy/paste. Instead, + * use Color as hint. + */ + // txtPheLat.setText("???"); + txtPheLat.setText(""); + setBackgroundColor(txtPheLat, wrongFormatColor); + } } }); @@ -796,11 +817,19 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { @Override public void focusLost(FocusEvent e) { - if (SigmetAttrDlg.this.getEditableAttrPhenomLon() != null) + if (SigmetAttrDlg.this.getEditableAttrPhenomLon() != null) { txtPheLon.setText(SigmetAttrDlg.this .getEditableAttrPhenomLon()); - else - txtPheLon.setText("???"); + setBackgroundColor(txtPheLon, rightFormatColor); + } else { + /* + * "???" causes inconvenience for copy/paste. Instead, + * use Color as hint. + */ + // txtPheLon.setText("???"); + txtPheLon.setText(""); + setBackgroundColor(txtPheLon, wrongFormatColor); + } } }); @@ -1397,8 +1426,8 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { .getPoints().toArray(new Coordinate[] {}); /* - * Added "trim()" since SIGMETs VGFs has no "editableAttrFromLine" and it is - * defaulted as " " when converted into XML - (J. Wu). + * Added "trim()" since SIGMETs VGFs has no "editableAttrFromLine" + * and it is defaulted as " " when converted into XML - (J. Wu). */ if (coors != null) { if (editableAttrFromLine == null @@ -1522,7 +1551,6 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { private String convertTimeStringPlusHourInHMS(String timeString, int plusHour, boolean dayNeeded) { Calendar c = Calendar.getInstance(); - System.out.println("____________timeString: " + timeString); c.set(Calendar.DAY_OF_MONTH, Integer.parseInt(timeString.substring(0, 2))); c.set(Calendar.HOUR_OF_DAY, @@ -1996,14 +2024,14 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { @Override public void createButtonsForButtonBar(Composite parent) { - - ((GridLayout) parent.getLayout()).verticalSpacing = 0; - ((GridLayout) parent.getLayout()).marginHeight = 3; - + + ((GridLayout) parent.getLayout()).verticalSpacing = 0; + ((GridLayout) parent.getLayout()).marginHeight = 3; + createButton(parent, IDialogConstants.OK_ID, "Save", true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); - + getButton(IDialogConstants.OK_ID).setLayoutData( new GridData(ctrlBtnWidth, ctrlBtnHeight)); getButton(IDialogConstants.CANCEL_ID).setLayoutData( @@ -2204,18 +2232,25 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { Sigmet sig = ((Sigmet) SigmetAttrDlg.this.drawingLayer .getSelectedDE()); - if (SigmetAttrDlg.this.ISOLATED.equals(sig.getType())) { + + /* + * TTR 722 - This "if" causes "ISOLATED" TC center not + * displaying. So we commented it out. + * + * if (SigmetAttrDlg.this.ISOLATED.equals(sig.getType())) { + * sb.append(" ").append("NEAR"); // + * sb.append(" ").append(sig.getLinePoints()[0].y); // + * sb.append(" ").append(sig.getLinePoints()[0].x); } else { + */ + if (SigmetAttrDlg.this.getEditableAttrPhenomLat() != null + && SigmetAttrDlg.this.getEditableAttrPhenomLon() != null) { sb.append(" ").append("NEAR"); - // sb.append(" ").append(sig.getLinePoints()[0].y); - // sb.append(" ").append(sig.getLinePoints()[0].x); - } else { - if ( SigmetAttrDlg.this.getEditableAttrPhenomLat() != null - && SigmetAttrDlg.this.getEditableAttrPhenomLon() != null ) { - sb.append(" ").append("NEAR"); - sb.append(" ").append(SigmetAttrDlg.this.getEditableAttrPhenomLat()); - sb.append(SigmetAttrDlg.this.getEditableAttrPhenomLon()); - } + sb.append(" ").append( + SigmetAttrDlg.this.getEditableAttrPhenomLat()); + sb.append(" ").append( + SigmetAttrDlg.this.getEditableAttrPhenomLon()); } + // } sb.append(" ").append("AT "); sb.append(getTimeStringPlusHourInHMS(0).substring(0, 4));// C @@ -2227,6 +2262,11 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { // --------------- movement String movement = SigmetAttrDlg.this.getEditableAttrMovement(); + + if (movement == null) { + movement = "STNRY"; + } + if ("STNRY".equals(movement)) { sb.append(" ").append("STNR. "); } else if ("MVG".equals(movement)) { @@ -2292,7 +2332,8 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { String tops = SigmetAttrDlg.this.getEditableAttrLevel(); - if ("FCST".equals(tops) || isTropCyc) { + if ("FCST".equals(tops)) { + // if ("FCST".equals(tops) || isTropCyc) { sb.append(tops.equals("-none-") ? "" : tops).append(" "); sb.append(SigmetAttrDlg.this.getEditableAttrLevelInfo1()) .append(" "); @@ -2339,7 +2380,7 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { sb.append(" ").append("NM CENTER."); } else { sb.append(" ").append("WI "); - sb.append((int)SigmetAttrDlg.this.getWidth()); + sb.append((int) SigmetAttrDlg.this.getWidth()); sb.append(" ").append("NM OF "); for (int i = 0; i < lineArray.length - 1; i++) sb.append(" ").append(lineArray[i]); @@ -2363,7 +2404,7 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { } else {// line with LINE_SEPERATER sb.append(" ").append("WI "); - sb.append((int)SigmetAttrDlg.this.getWidth()); + sb.append((int) SigmetAttrDlg.this.getWidth()); sb.append(" ").append("NM "); sb.append(getLineTypeForSOL(lineType));// [lineArray.length-2]);//watch // out for index @@ -2648,9 +2689,15 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { // ((Sigmet)sigmet).getType() ) ); } } + if (txtInfo != null && !txtInfo.isDisposed() && s != null) this.resetText(s, txtInfo); + // TTR 974 - "editableAttrFromLine" needs update as well. + if (sigmet != null && s != null) { + ((Sigmet) sigmet).setEditableAttrFromLine(s); + } + } private void init() { @@ -2787,9 +2834,45 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { withExpandedArea = false;// ??? } + /* + * This method validates the input lat or lon are in the specified formats + * as below since AWC receives info in various formats from TACs around the + * world) and be convertyed later into specified formats. + * + * This a new requirement from AWC as described in AWC Sept 2014 FIT test + * report for TTR 433 - J. Wu, Oct. 7th, 2014. + * + * "N", "S", and "–" are literal; "dd" or "ddd" is degree; "mm" is minute; + * "p" is decimal degree; "(d)" is an optional degree longitude digit for + * longitudes with absolute value >= 100.0): + * + * A) "Phenom Lat" text box accepts a latitude north: Nddmm, ddmmN, dd.pN, + * Ndd.p, dd.p; Output is Nddmm. + * + * B)"Phenom Lat" text box accepts a latitude south: Sddmm, ddmmS, dd.pS, + * Sdd.p, dd.p; Output is Sddmm. + * + * C)"Phenom Lon" text box accepts a longitude west: W(d)ddmm, (d)ddmmW, + * (d)dd.pW, W(d)dd.p, -(d)dd.p; Output is W[0|1]ddmm. + * + * D)"Phenom Lon" text box accepts a longitude east: E(d)ddmm, (d)ddmmE, + * (d)dd.pE, E(d)dd.p, (d)dd.p; Output is E[0|1]ddmm. + * + * Note: + * + * (1) "N", "S", "E", "W" is not case-sensitive. + * + * (2) "N"/"S"/"E"/"W" should have been stripped of before sending in here. + * + * (3) A "-" should be placed at the beginning if "S" or "W" found in the + * input. + */ private boolean validateLatLon(String coor, boolean isLat) { - String regexLat = "(-?[0-8]?[0-9](\\.\\d*)?)|-?90(\\.[0]*)?"; - String regexLon = "(-?([1]?[0-7][1-9]|[1-9]?[0-9])?(\\.\\d*)?)|-?180(\\.[0]*)?"; + // String regexLat = "(-?[0-8]?[0-9](\\.\\d*)?)|-?90(\\.[0]*)?"; + // String regexLon = + // "(-?([1]?[0-7][1-9]|[1-9]?[0-9])?(\\.\\d*)?)|-?180(\\.[0]*)?"; + String regexLat = "(-?[0-8]?[0-9](\\.)?(\\d*)?)|-?90(\\.)?([0]*)?"; + String regexLon = "(-?([1]?[0-7][0-9]|[0]?[0-9]?[0-9])?(\\.)?(\\d*)?)|-?180(\\.)?([0]*)?"; java.util.regex.Matcher m; if (isLat) { @@ -2860,11 +2943,13 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { Coordinate coor = coors[i]; result.append(coor.y >= 0 ? "N" : "S"); - long y = ((int) Math.abs(coor.y)*100) + Math.round( Math.abs(coor.y-(int)(coor.y))*60); + long y = ((int) Math.abs(coor.y) * 100) + + Math.round(Math.abs(coor.y - (int) (coor.y)) * 60); result.append(new DecimalFormat(FOUR_ZERO).format(y)); result.append(coor.x >= 0 ? " E" : " W"); - long x = ((int) Math.abs(coor.x))*100 + Math.round(Math.abs(coor.x-(int)(coor.x))*60); + long x = ((int) Math.abs(coor.x)) * 100 + + Math.round(Math.abs(coor.x - (int) (coor.x)) * 60); result.append(new DecimalFormat(FIVE_ZERO).format(x)); @@ -2943,28 +3028,143 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { return null; } + /* + * This method validates the input lat or lon are in the specified formats + * and format it into a specfied form as described below: + * + * This a new requirement from AWC as described in AWC Sept 2014 FIT test + * report for TTR 433 - J. Wu, Oct. 7th, 2014. + * + * "N", "S", and "–" are literal; "dd" or "ddd" is degree; "mm" is minute; + * "p" is decimal degree; "(d)" is an optional degree longitude digit for + * longitudes with absolute value >= 100.0): + * + * A) "Phenom Lat" text box accepts a latitude north: Nddmm, ddmmN, dd.pN, + * Ndd.p, dd.p; Output is Nddmm. + * + * B)"Phenom Lat" text box accepts a latitude south: Sddmm, ddmmS, dd.pS, + * Sdd.p, dd.p; Output is Sddmm. + * + * C)"Phenom Lon" text box accepts a longitude west: W(d)ddmm, (d)ddmmW, + * (d)dd.pW, W(d)dd.p, -(d)dd.p; Output is W[0|1]ddmm. + * + * D)"Phenom Lon" text box accepts a longitude east: E(d)ddmm, (d)ddmmE, + * (d)dd.pE, E(d)dd.p, (d)dd.p; Output is E[0|1]ddmm. + * + * Note: "N", "S", "E", "W" is not case-sensitive here. + */ private String getPhenomLatLon(String input, boolean isLat) { - if (input.startsWith("S") || input.startsWith("s") - || input.startsWith("W") || input.startsWith("w") - || input.endsWith("S") || input.endsWith("s") - || input.endsWith("W") || input.endsWith("w")) + input = input.toUpperCase(); + if ((isLat && (input.startsWith("S") || input.endsWith("S"))) + || (!isLat && (input.startsWith("W") || input.endsWith("W")))) { input = "-" + input; + } + + /* + * remove characters that is not a digit, -, or decimal point. + */ input = input.replaceAll("[^-0-9.]", ""); + /* + * Format the output to the desired form. + */ StringBuilder result = new StringBuilder(); if (!"".equals(input) && !"-".equals(input) && validateLatLon(input, isLat)) { - Double value = Double.parseDouble(input); - if (isLat) { - result.append(value >= 0.0 ? "N" : "S"); - double y = (double) Math.abs(value); - result.append(y); + if (!input.contains(".")) { + + /* + * Padding to make lat as "ddmm" and lon as "dddmm". + */ + String istr = input.replaceAll("-", ""); + int len = istr.length(); + String ostr = ""; + int val = Integer.parseInt(istr); + if (isLat) { + if (len == 1) { + ostr += ("0" + istr + "00"); + } else if (len == 2) { + if (val <= 90) + ostr += (istr + "00"); + } else if (len == 3) { + if (val <= 900) + ostr += (istr + "0"); + } else { + String tmp = istr.substring(0, 4); + if (Integer.parseInt(tmp) <= 9000) { + ostr += tmp; + } + } + } else { + if (len == 1) { + ostr += ("00" + istr + "00"); + } else if (len == 2) { + ostr += ("0" + istr + "00"); + } else if (len == 3) { + if (val <= 180) { + ostr += (istr + "00"); + } else { + ostr += ("0" + istr + "0"); + } + } else if (len == 4) { + if (val > 180) { + ostr += ("0" + istr); + } else if (val >= 100) { + ostr += (val + "00"); + } else if (val >= 10) { + ostr += ("0" + val + "00"); + } else { + ostr += ("00" + val + "00"); + } + + } else { + String tmp = istr.substring(0, 5); + if (Integer.parseInt(tmp) <= 18000) { + ostr += tmp; + } + } + } + + if (ostr.length() > 0) { + if (isLat) { + result.append(input.startsWith("-") ? "S" : "N"); + } else { + result.append(input.startsWith("-") ? "W" : "E"); + } + } + + result.append(ostr); + } else { - result.append(value >= 0.0 ? " E" : " W"); - double x = (double) Math.abs(value); - result.append(x); + /* + * Convert to degree and minutes and then padding to make lat as + * "ddmm" and lon as "dddmm". + */ + Double value = Double.parseDouble(input); + int deg = value.intValue(); + + Double minute = (Math.abs(value - deg)) * 60.0; + int mm = (int) Math.round(minute); + + deg = Math.abs(deg); + + if (isLat) { + result.append(value >= 0.0 ? "N" : "S"); + String dstr = (deg < 10) ? ("0" + deg) : ("" + deg); + result.append(dstr); + String mstr = (mm < 10) ? ("0" + mm) : ("" + mm); + result.append(mstr); + + } else { + result.append(value >= 0.0 ? "E" : "W"); + String dstr = (deg < 10) ? ("00" + deg) + : ((deg < 100) ? ("0" + deg) : ("" + deg)); + result.append(dstr); + String mstr = (mm < 10) ? ("0" + mm) : ("" + mm); + result.append(mstr); + } } return result.toString().trim(); @@ -3108,4 +3308,15 @@ public class SigmetAttrDlg extends AttrDlg implements ISigmet { this.setEditableAttrSeqNum(sig.getEditableAttrSeqNum()); } + + /* + * Sets the background color for a SWT control. + */ + private void setBackgroundColor(Control ww, Color color) { + org.eclipse.swt.graphics.Color clr = new org.eclipse.swt.graphics.Color( + ww.getDisplay(), color.getRed(), color.getGreen(), + color.getBlue()); + ww.setBackground(clr); + clr.dispose(); + } } \ No newline at end of file diff --git a/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetCommAttrDlg.java b/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetCommAttrDlg.java index 9946b7cd14..0bb4cf191d 100644 --- a/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetCommAttrDlg.java +++ b/ncep/gov.noaa.nws.ncep.ui.pgen/src/gov/noaa/nws/ncep/ui/pgen/attrdialog/SigmetCommAttrDlg.java @@ -81,6 +81,7 @@ import com.vividsolutions.jts.io.WKBReader; * 11/12 #873 B. Yin Pass sigmet type "CONV_SIGMET" for snapping. * 03/13 #928 B. Yin Made the button bar smaller. * 04/29 #977 S. Gilbert PGEN Database support + * 04/29 #726 J. Wu Remove the line breaker when saving vor list into file. * * * @author gzhang @@ -538,7 +539,7 @@ public class SigmetCommAttrDlg extends AttrDlg implements ISigmet { txtInfo = new Text(top2, style); txtInfo.setFont(txtfont); attrControlMap.put("editableAttrFromLine", txtInfo); - GridData gData = new GridData(600, 48); + GridData gData = new GridData(650, 48); gData.horizontalSpan = 8; txtInfo.setLayoutData(gData); txtInfo.setText(this.getEditableAttrFromLine()); @@ -740,13 +741,13 @@ public class SigmetCommAttrDlg extends AttrDlg implements ISigmet { @Override public void createButtonsForButtonBar(Composite parent) { - ((GridLayout) parent.getLayout()).verticalSpacing = 0; - ((GridLayout) parent.getLayout()).marginHeight = 3; - + ((GridLayout) parent.getLayout()).verticalSpacing = 0; + ((GridLayout) parent.getLayout()).marginHeight = 3; + createButton(parent, IDialogConstants.OK_ID, "Save", true); createButton(parent, IDialogConstants.CANCEL_ID, IDialogConstants.CANCEL_LABEL, false); - + getButton(IDialogConstants.OK_ID).setLayoutData( new GridData(ctrlBtnWidth, ctrlBtnHeight)); getButton(IDialogConstants.CANCEL_ID).setLayoutData( @@ -775,7 +776,8 @@ public class SigmetCommAttrDlg extends AttrDlg implements ISigmet { if (dataURI != null) { try { StorageUtils.storeDerivedProduct(dataURI, - txtSave.getText(), "TEXT", txtInfo.getText()); + txtSave.getText(), "TEXT", txtInfo.getText() + .replaceAll("-\n", "-")); } catch (PgenStorageException e) { StorageUtils.showError(e); } @@ -801,13 +803,32 @@ public class SigmetCommAttrDlg extends AttrDlg implements ISigmet { String forecaster = System.getProperty("user.name"); ProductTime refTime = new ProductTime(); - Product defaultProduct = new Product( - SigmetCommAttrDlg.this.pgenType, - SigmetCommAttrDlg.this.pgenType, forecaster, null, refTime, - layerList); - // defaultProduct.addLayer(defaultLayer); - defaultProduct.setOutputFile(SigmetCommAttrDlg.this.drawingLayer - .buildActivityLabel(defaultProduct)); + String pname = SigmetCommAttrDlg.this.drawingLayer + .getActiveProduct().getName(); + String ptype = SigmetCommAttrDlg.this.drawingLayer + .getActiveProduct().getType(); + + pname = (pname == null) ? "Default" : pname; + ptype = (ptype == null) ? "Default" : ptype; + + Product defaultProduct = new Product(pname, ptype, forecaster, + null, refTime, layerList); + /* + * + * Product defaultProduct = new Product( + * SigmetCommAttrDlg.this.pgenType, SigmetCommAttrDlg.this.pgenType, + * forecaster, null, refTime, layerList); + */ + String plabel = SigmetCommAttrDlg.this.drawingLayer + .getActiveProduct().getOutputFile(); + if (plabel == null) { + plabel = SigmetCommAttrDlg.this.drawingLayer + .buildActivityLabel(defaultProduct); + } + defaultProduct.setOutputFile(plabel); + // defaultProduct.setOutputFile(SigmetCommAttrDlg.this.drawingLayer + // .buildActivityLabel(defaultProduct)); + defaultProduct.setCenter(PgenUtil.getCurrentOffice()); try { @@ -838,7 +859,7 @@ public class SigmetCommAttrDlg extends AttrDlg implements ISigmet { txtInfo = new Text(top, SWT.MULTI | SWT.BORDER | SWT.READ_ONLY | SWT.WRAP); txtInfo.setFont(txtfont); - GridData gData = new GridData(512, 300); + GridData gData = new GridData(672, 300); gData.horizontalSpan = 3; txtInfo.setLayoutData(gData); txtInfo.setText(getFileContent()); @@ -1048,9 +1069,9 @@ public class SigmetCommAttrDlg extends AttrDlg implements ISigmet { try { stateGeo = (MultiPolygon) wkbReader.read(wkb); } catch (Exception e) { - System.out - .println("___ Error: SigmetCommAttrDlg: getAreaString(): " - + e.getMessage()); + // System.out + // .println("___ Error: SigmetCommAttrDlg: getAreaString(): " + // + e.getMessage()); } if (stateGeo != null) { diff --git a/ncep/gov.noaa.nws.ncep.viz.common/src/gov/noaa/nws/ncep/viz/common/SnapUtil.java b/ncep/gov.noaa.nws.ncep.viz.common/src/gov/noaa/nws/ncep/viz/common/SnapUtil.java index c317cbf3ab..db728ffaac 100644 --- a/ncep/gov.noaa.nws.ncep.viz.common/src/gov/noaa/nws/ncep/viz/common/SnapUtil.java +++ b/ncep/gov.noaa.nws.ncep.viz.common/src/gov/noaa/nws/ncep/viz/common/SnapUtil.java @@ -1,17 +1,26 @@ package gov.noaa.nws.ncep.viz.common; -import gov.noaa.nws.ncep.viz.common.dbQuery.NcDirectDbQuery; import gov.noaa.nws.ncep.edex.common.stationTables.Station; import gov.noaa.nws.ncep.edex.common.stationTables.StationTable; +import gov.noaa.nws.ncep.viz.common.dbQuery.NcDirectDbQuery; import gov.noaa.nws.ncep.viz.localization.NcPathManager; import gov.noaa.nws.ncep.viz.localization.NcPathManager.NcPathConstants; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + import org.geotools.referencing.GeodeticCalculator; import org.geotools.referencing.datum.DefaultEllipsoid; -import java.io.*; -import java.util.*; - import com.raytheon.uf.viz.core.catalog.DirectDbQuery.QueryLanguage; import com.raytheon.uf.viz.core.exception.VizException; import com.vividsolutions.jts.geom.Coordinate; @@ -29,885 +38,991 @@ import com.vividsolutions.jts.geom.Envelope; * 03/2012 S. Gurung Fixed a bug while getting VOR text * 11/2012 873 B. Yin When snapping, check sigmet type to make sure * no space between distance and direction for CONV_SIGMET. - * + * 10/2014 TTR726 J. Wu Add "-" or " TO " at the end of vor text line. + * * * - * @author sgurung + * @author sgurung */ public class SnapUtil { - - public static List VOR_STATION_LIST; - - public static final String GFA_TEXT = new String( "GFA_TYPE" ); - - public static final String[] DIRECT_ARRAY = new String[]{ "N","NNE","NE","ENE","E","ESE","SE","SSE", - "S","SSW","SW","WSW","W","WNW","NW","NNW"}; - private static final Map compassPtsAzimuths = new HashMap(); - private static final Map compassPtsAzimuthsMinus = new HashMap(); - - /* - * Nautical miles to meters - */ - public static final float NM2M = 1852.0f; - - static{ - File stnFile = NcPathManager.getInstance().getStaticFile( - NcPathConstants.VORS_STN_TBL ); - VOR_STATION_LIST = new StationTable( stnFile.getAbsolutePath() ).getStationList(); + public static List VOR_STATION_LIST; - initCompassPtsAzimuths(); - } - - public static String getAzimuthInNSWEString(double azimuth){ - if(azimuth > -11.25 && azimuth <= 11.25) return DIRECT_ARRAY[0]; - if(azimuth > 11.25 && azimuth <= 33.75) return DIRECT_ARRAY[1]; - if(azimuth > 33.75 && azimuth <= 56.25) return DIRECT_ARRAY[2]; - if(azimuth > 56.25 && azimuth <= 78.75) return DIRECT_ARRAY[3]; - if(azimuth > 78.75 && azimuth <= 101.25) return DIRECT_ARRAY[4]; - if(azimuth > 101.25 && azimuth <= 123.75) return DIRECT_ARRAY[5]; - if(azimuth > 123.75 && azimuth <= 146.25) return DIRECT_ARRAY[6]; - if(azimuth > 146.25 && azimuth <= 168.75) return DIRECT_ARRAY[7]; - if(azimuth > 168.75 || azimuth <= -168.75) return DIRECT_ARRAY[8]; - if(azimuth > -168.75&& azimuth <= -146.25) return DIRECT_ARRAY[9]; - if(azimuth > -146.25&& azimuth <= -123.75) return DIRECT_ARRAY[10]; - if(azimuth > -123.75&& azimuth <= -101.25) return DIRECT_ARRAY[11]; - if(azimuth > -101.25&& azimuth <= -78.75) return DIRECT_ARRAY[12]; - if(azimuth > -78.75 && azimuth <= -56.25) return DIRECT_ARRAY[13]; - if(azimuth > -56.25 && azimuth <= -33.75) return DIRECT_ARRAY[14]; - else /* azimuth > -33.75&&<= -11.25 */ return DIRECT_ARRAY[15]; - } + public static final String GFA_TEXT = new String("GFA_TYPE"); - /** - * Parameters: - * - * List coors: the points to be snapped - * List stnList: (NOT USED for reference: the stations to snap upon) - * int rounding: 5nm, 10nm,... - * boolean compassPts8: true: 8-point; false: 16-point. - */ - - public static ArrayList getSnapWithStation(List coors, - List stnList, int rounding, int numOfCompassPts){ - return getSnapWithStation(coors, stnList, rounding, numOfCompassPts, true); - } + public static final String[] DIRECT_ARRAY = new String[] { "N", "NNE", + "NE", "ENE", "E", "ESE", "SE", "SSE", "S", "SSW", "SW", "WSW", "W", + "WNW", "NW", "NNW" }; - /** - * Parameters: - * - * @param coors List the points to be snapped - * @param stnList List (NOT USED for reference: the stations to snap upon) - * @param rounding int 5nm, 10nm,... - * @param numOfCompassPts - * @param useJTS - * @return - */ - public static ArrayList getSnapWithStation(List coors, - List stnList, int rounding, int numOfCompassPts, boolean useJTS){ - -// String nonSnappingMsg = "SOME OR ALL POINTS NOT SNAPPED, ORIGINAL POINTS USED!"; - - if( ! SnapVOR.isSnappable(coors) || stnList==null) { -// SnapVOR.openMsgBox(nonSnappingMsg); - ArrayList nlist = new ArrayList(); - nlist.addAll( coors ); - return nlist; - } - - ArrayList snapPtsList = new ArrayList(); + private static final Map compassPtsAzimuths = new HashMap(); - GeodeticCalculator gc = new GeodeticCalculator(DefaultEllipsoid.WGS84); - TreeMap treeMap = new TreeMap(); - - Coordinate coor = null; - - //coor NOT actually used, keep for reference. - stnList=SnapVOR.getSnapStns(coor, numOfCompassPts); - - for(int i=0; coors!=null && i) coors; - - }else{//use pre-calculated VOR-snapping points, wrapped as Stations too. - - double distance = treeMap.firstKey(); - Station vorStn = treeMap.get(distance); - snapPtsList.add(new Coordinate(vorStn.getLongitude(),vorStn.getLatitude())); - } - - treeMap.clear(); - } - - return snapPtsList; - } - - /** - * Populates treeMap with the stations sorted by distance. - * - * @param treeMap - * @param stnList - * @param gc - * @param coor - * @param useJTS - */ - public static void populateStationsTreeMap(TreeMap treeMap, - List stnList, GeodeticCalculator gc, Coordinate coor, boolean useJTS) { - double geoDistance = Double.NaN; - for (Station stn : stnList) { - if(useJTS) { - gc.setStartingGeographicPoint(stn.getLongitude(), stn.getLatitude()); - gc.setDestinationGeographicPoint(coor.x, coor.y); - try { - geoDistance = gc.getOrthodromicDistance(); - } catch (ArithmeticException e) { - try { - geoDistance = DefaultEllipsoid.WGS84.orthodromicDistance(stn.getLongitude(), - stn.getLatitude(), coor.x, coor.y); - } catch (ArithmeticException ee) { - geoDistance = Double.NaN; - } - } - } else { - double dx = coor.x -stn.getLongitude(); - double dy = coor.y -stn.getLatitude(); - geoDistance = dx*dx + dy*dy; - } + private static final Map compassPtsAzimuthsMinus = new HashMap(); - treeMap.put(geoDistance, stn);// TODO: handle gc ArithmeticException + /* + * Nautical miles to meters + */ + public static final float NM2M = 1852.0f; - } - } - - /* - * Parameters: - * - * List coors: the points to be snapped - * List coorList: the points to snap upon in Coordinate format - * int rounding: 5nm, 10nm,... - * boolean compassPts8: true: 8-point; false: 16-point. - */ - public static ArrayList getSnapWithCoordinates(ArrayList coors, - List coorList, int rounding, int numOfCompassPts, boolean useJTS){ - - List stnList = new ArrayList(); - - for(Coordinate coor : coorList){ - Station s = new Station(); - s.setLongitude((float)coor.x); - s.setLatitude((float)coor.y); - stnList.add(s); - } - - return getSnapWithStation(coors, stnList, rounding, numOfCompassPts, useJTS); - } - - /* - * parameters: - * - * azimuth: the actual direction - * numOfCompassPts: common Compass Point: 4/8/16/32, etc; a non-regular point will be coerced into - * the closest regular one - */ - - public static double getSnapDir(double azimuth, int numOfCompassPts){ - - double[] ap = getAzimuths(numOfCompassPts, true), am = getAzimuths(numOfCompassPts,false); - TreeMap treeMap = new TreeMap(); - - if(azimuth == 0){ - return 0; - }else if(azimuth > 0){ - for(double d : ap) treeMap.put(Math.abs(d - azimuth),d); - }else{ - for(double dd : am) treeMap.put(Math.abs(dd - azimuth),dd); - } - - return treeMap.get(treeMap.firstKey()); - } - - /* - * parameters: - * - * d: the actual distance to be rounded - * rounding: 10nm, 5nm, etc - */ - - public static double getSnapDistance(double d, int rounding){ - - if(rounding <= 0) rounding = 10;//default - - int distance = (int)(d/NM2M); - int nm = distance / rounding; - double remain = distance % rounding; - if(remain >= ( ((double)rounding)/2) ) nm++; - - return rounding*nm*NM2M; - } - - private static void initCompassPtsAzimuths(){ + static { + File stnFile = NcPathManager.getInstance().getStaticFile( + NcPathConstants.VORS_STN_TBL); + VOR_STATION_LIST = new StationTable(stnFile.getAbsolutePath()) + .getStationList(); - compassPtsAzimuths.put(1, new double[]{0}); - compassPtsAzimuthsMinus.put(1, new double[]{0}); - - compassPtsAzimuths.put(2, new double[]{0,180}); - compassPtsAzimuthsMinus.put(2, new double[]{0,-180}); - - compassPtsAzimuths.put(4, new double[]{0,90,180}); - compassPtsAzimuthsMinus.put(4, new double[]{0,-90,-180}); - - compassPtsAzimuths.put(6, new double[]{0,60,120,180}); - compassPtsAzimuthsMinus.put(6, new double[]{0,-60,-120,-180}); - - compassPtsAzimuths.put(8, new double[]{0,45,90,135,180}); - compassPtsAzimuthsMinus.put(8, new double[]{0,-45,-90,-135,-180}); - - compassPtsAzimuths.put(10, new double[]{0,36,72,108,144,180}); - compassPtsAzimuthsMinus.put(10, new double[]{0,-36,-72,-108,-144,-180}); - - compassPtsAzimuths.put(12, new double[]{0,30,60,90,120,150,180}); - compassPtsAzimuthsMinus.put(12, new double[]{0,-30,-60,-90,-120,-150,-180}); - - compassPtsAzimuths.put(16, new double[]{0,22.5,45,67.5,90,112.5,135,157.5,180}); - compassPtsAzimuthsMinus.put(16, new double[]{0,-22.5,-45,-67.5,-90,-112.5,-135,-157.5,-180}); - - compassPtsAzimuths.put(18, new double[]{0,20,40,60,80,100,120,140,160,180}); - compassPtsAzimuthsMinus.put(18, new double[]{0,-20,-40,-60,-80,-100,-120,-140,-160,-180}); - - compassPtsAzimuths.put(32, - new double[]{0,11.25,22.5,33.75,45,56.25,67.5,78.75,90,101.25,112.5,123.75,135,146.25,157.5,168.75,180}); - compassPtsAzimuthsMinus.put(32, - new double[]{0,-11.25,-22.5,-33.75,-45,-56.25,-67.5,-78.75,-90,-101.25,-112.5,-123.75,-135,-146.25,-157.5,-168.75,-180}); - - } - - private static int getCompassPoint(int pts){ - - Set keys = compassPtsAzimuths.keySet(); - - if(keys.contains( pts ) ) - return pts; - - if(pts <= 0 || pts > 360) - return 8;//default; - - TreeMap map = new TreeMap(); - - for(Integer i : keys) - map.put(Math.abs(pts-i), i); - - return map.get(map.firstKey()); - } - - - public static double[] getAzimuths(int numOfCompassPts, boolean positive){ - - int key = getCompassPoint(numOfCompassPts); - - return positive - ? compassPtsAzimuths.get(key) - : compassPtsAzimuthsMinus.get(key); - } - - /** - * Creates a text from the coordinates with respect to VOR stations. - * - * @param coors - * @param vorConnector - * @param lineType - * @param numPerLines number of coordinates per line, if negative, then Integer.MAX_VALUE is used instead. - * @param isSnapped boolean if the coors have been snapped - * @return VOR text string - */ - public static String getVORText(Coordinate[] coors, String vorConnector, String lineType, int numPerLines, boolean isSnapped) { - return getVORText(coors, vorConnector, lineType, numPerLines, isSnapped, true, false ); - } - - /** - * Creates a text from the coordinates with respect to VOR stations. - * - * @param coors - * @param vorConnector - * @param lineType - * @param numPerLines number of coordinates per line, if negative, then Integer.MAX_VALUE is used instead. - * @param isSnapped boolean if the coors have been snapped - * @param useJTS boolean false to speed up the calculation (bypass the use of JTS library), true by default - * @return VOR text string - */ - public static String getVORText(Coordinate[] coors, String vorConnector, String lineType, int numPerLines, - boolean isSnapped, boolean useJTS, boolean isGfa ) { - return getVORText(coors, vorConnector, lineType, numPerLines, isSnapped, true, isGfa, null); - } - - /** - * Creates a text from the coordinates with respect to VOR stations. - * - * @param coors - * @param vorConnector - * @param lineType - * @param numPerLines number of coordinates per line, if negative, then Integer.MAX_VALUE is used instead. - * @param isSnapped boolean if the coors have been snapped - * @param useJTS boolean false to speed up the calculation (bypass teh use of JTS library), true by default - * @param sigmetType the type of sigmet - * @return VOR text string - */ - public static String getVORText(Coordinate[] coors, String vorConnector, String lineType, int numPerLines, - boolean isSnapped, boolean useJTS, boolean isGfa, String sigmetType) { - - if (lineType != null && lineType.startsWith("Line")) { - coors = reorderLineCoordinates(coors); - } else if ("Area".equals(lineType)) { - coors = reorderAreaCoordinates(coors); - } - - if( isSnapped ) - return SnapVOR.getSnapVORTxt(coors, vorConnector, lineType, useJTS, isGfa ); - - List list = VOR_STATION_LIST; - GeodeticCalculator gc = new GeodeticCalculator(DefaultEllipsoid.WGS84); - - TreeMap treeMap = new TreeMap(); - ArrayList resultList = new ArrayList(); - - for(Coordinate coor : coors){ - - populateStationsTreeMap(treeMap, list, gc, coor, useJTS); - - double distance = treeMap.firstKey(); - Station vorStn = treeMap.get(distance); - gc.setStartingGeographicPoint(vorStn.getLongitude(), vorStn.getLatitude()); - gc.setDestinationGeographicPoint(coor.x, coor.y); - - String azimuth = getAzimuthInNSWEString(gc.getAzimuth()); - - //resultList.add(new VORStation(vorStn.getStid(),azimuth,""+(int)(Math.round(distance/PgenUtil.NM2M)))); - /* - * Round distance to the nearest 10 nautical miles; - * If convective outlook and less than 30 nm, set to 0. - */ - distance = getSnapDistance(distance,10)/NM2M; - if ("OUTL_SIGMET".equals(sigmetType) && (int)distance < 30) - distance = 0; - - resultList.add(new VORStation(vorStn.getStid(),azimuth,""+(int) distance)); - treeMap.clear(); - } - - if(numPerLines < 0) numPerLines=Integer.MAX_VALUE; - - StringBuilder result = new StringBuilder(); - String first =""; - int count = 0; - for(VORStation vs : resultList) { - if ( isGfa ) { - vs.setPgenType( GFA_TEXT ); - } - else if ("CONV_SIGMET".equals(sigmetType) || "NCON_SIGMET".equals(sigmetType) || "OUTL_SIGMET".equals(sigmetType)){ - vs.setPgenType( sigmetType); - } - - if(count==0) first = vs.toString();//first vor at the end for AREA - result.append(vs.toString() + ( ((++count)%numPerLines)==0 ? "\n" : vorConnector )); - } - String resultString = "Area".equals(lineType) - ? result.append(first).toString() - : result.substring(0, result.lastIndexOf(vorConnector));; - return resultString; + initCompassPtsAzimuths(); } - /* - * This function reorders (Line) processing of points to do either west-to-east - * or north-to-south. West-to-east defined as all points within W2ELIM - * degrees of one another. - * - * @param coors the coordinates containing the lats and lons - * @return newCoors reordered coordinates - * - */ - public static Coordinate[] reorderLineCoordinates(Coordinate[] coors) { - - if (coors == null) - return coors; - - final int N2S = 0, W2E = 1; - final double W2ELIM = 0.1; - - int lineOrder = N2S; - boolean reverse = false; - double minLat, maxLat, dLat; - int numPoints = coors.length; - - minLat = coors[0].y; - maxLat = minLat; - - for ( Coordinate coor : coors ) { - minLat = Math.min(minLat, coor.y); - maxLat = Math.max(maxLat, coor.y); - } - - dLat = Math.abs(maxLat - minLat); - - if ( dLat <= W2ELIM ) - lineOrder = W2E; - - if ( lineOrder == N2S && coors[0].y < coors[numPoints -1].y ) - reverse = true; - if ( lineOrder == W2E && coors[0].x > coors[numPoints - 1].x ) - reverse = true; - - Coordinate[] newCoors = new Coordinate[numPoints]; - if ( reverse ) { - for ( int i=0; i maxLat ) { - iptr = i; - maxLat = coor.y; - } - } - - /* - * Check directions for each adjacent point; the point with - * the smallest angle from north is in the clockwise direction. - */ - if ( iptr != 0 ) - dirPrev = getDirection (coors[iptr-1], coors[iptr]); + public static String getAzimuthInNSWEString(double azimuth) { + if (azimuth > -11.25 && azimuth <= 11.25) + return DIRECT_ARRAY[0]; + if (azimuth > 11.25 && azimuth <= 33.75) + return DIRECT_ARRAY[1]; + if (azimuth > 33.75 && azimuth <= 56.25) + return DIRECT_ARRAY[2]; + if (azimuth > 56.25 && azimuth <= 78.75) + return DIRECT_ARRAY[3]; + if (azimuth > 78.75 && azimuth <= 101.25) + return DIRECT_ARRAY[4]; + if (azimuth > 101.25 && azimuth <= 123.75) + return DIRECT_ARRAY[5]; + if (azimuth > 123.75 && azimuth <= 146.25) + return DIRECT_ARRAY[6]; + if (azimuth > 146.25 && azimuth <= 168.75) + return DIRECT_ARRAY[7]; + if (azimuth > 168.75 || azimuth <= -168.75) + return DIRECT_ARRAY[8]; + if (azimuth > -168.75 && azimuth <= -146.25) + return DIRECT_ARRAY[9]; + if (azimuth > -146.25 && azimuth <= -123.75) + return DIRECT_ARRAY[10]; + if (azimuth > -123.75 && azimuth <= -101.25) + return DIRECT_ARRAY[11]; + if (azimuth > -101.25 && azimuth <= -78.75) + return DIRECT_ARRAY[12]; + if (azimuth > -78.75 && azimuth <= -56.25) + return DIRECT_ARRAY[13]; + if (azimuth > -56.25 && azimuth <= -33.75) + return DIRECT_ARRAY[14]; else - dirPrev = getDirection (coors[numPoints-1], coors[iptr]); + /* azimuth > -33.75&&<= -11.25 */return DIRECT_ARRAY[15]; + } - if ( iptr != numPoints-1 ) - dirNext = getDirection (coors[iptr+1], coors[iptr]); - else - dirNext = getDirection (coors[0], coors[iptr]); + /** + * Parameters: + * + * List coors: the points to be snapped List stnList: + * (NOT USED for reference: the stations to snap upon) int rounding: 5nm, + * 10nm,... boolean compassPts8: true: 8-point; false: 16-point. + */ + + public static ArrayList getSnapWithStation( + List coors, List stnList, int rounding, + int numOfCompassPts) { + return getSnapWithStation(coors, stnList, rounding, numOfCompassPts, + true); + } + + /** + * Parameters: + * + * @param coors + * List the points to be snapped + * @param stnList + * List (NOT USED for reference: the + * stations to snap upon) + * @param rounding + * int 5nm, 10nm,... + * @param numOfCompassPts + * @param useJTS + * @return + */ + public static ArrayList getSnapWithStation( + List coors, List stnList, int rounding, + int numOfCompassPts, boolean useJTS) { + + // String nonSnappingMsg = + // "SOME OR ALL POINTS NOT SNAPPED, ORIGINAL POINTS USED!"; + + if (!SnapVOR.isSnappable(coors) || stnList == null) { + // SnapVOR.openMsgBox(nonSnappingMsg); + ArrayList nlist = new ArrayList(); + nlist.addAll(coors); + return nlist; + } + + ArrayList snapPtsList = new ArrayList(); + + GeodeticCalculator gc = new GeodeticCalculator(DefaultEllipsoid.WGS84); + TreeMap treeMap = new TreeMap(); + + Coordinate coor = null; + + // coor NOT actually used, keep for reference. + stnList = SnapVOR.getSnapStns(coor, numOfCompassPts); + + for (int i = 0; coors != null && i < coors.size(); i++) {// keep the + // order in + // snapPtsList + coor = coors.get(i); + + populateStationsTreeMap(treeMap, stnList, gc, coor, useJTS); + + if (treeMap.isEmpty()) { + + // SnapVOR.openMsgBox(nonSnappingMsg); + return (ArrayList) coors; + + } else {// use pre-calculated VOR-snapping points, wrapped as + // Stations too. + + double distance = treeMap.firstKey(); + Station vorStn = treeMap.get(distance); + snapPtsList.add(new Coordinate(vorStn.getLongitude(), vorStn + .getLatitude())); + } + + treeMap.clear(); + } + + return snapPtsList; + } + + /** + * Populates treeMap with the stations sorted by distance. + * + * @param treeMap + * @param stnList + * @param gc + * @param coor + * @param useJTS + */ + public static void populateStationsTreeMap( + TreeMap treeMap, List stnList, + GeodeticCalculator gc, Coordinate coor, boolean useJTS) { + double geoDistance = Double.NaN; + for (Station stn : stnList) { + if (useJTS) { + gc.setStartingGeographicPoint(stn.getLongitude(), + stn.getLatitude()); + gc.setDestinationGeographicPoint(coor.x, coor.y); + try { + geoDistance = gc.getOrthodromicDistance(); + } catch (ArithmeticException e) { + try { + geoDistance = DefaultEllipsoid.WGS84 + .orthodromicDistance(stn.getLongitude(), + stn.getLatitude(), coor.x, coor.y); + } catch (ArithmeticException ee) { + geoDistance = Double.NaN; + } + } + } else { + double dx = coor.x - stn.getLongitude(); + double dy = coor.y - stn.getLatitude(); + geoDistance = dx * dx + dy * dy; + } + + treeMap.put(geoDistance, stn);// TODO: handle gc ArithmeticException + + } + } + + /* + * Parameters: + * + * List coors: the points to be snapped List + * coorList: the points to snap upon in Coordinate format int rounding: 5nm, + * 10nm,... boolean compassPts8: true: 8-point; false: 16-point. + */ + public static ArrayList getSnapWithCoordinates( + ArrayList coors, List coorList, + int rounding, int numOfCompassPts, boolean useJTS) { + + List stnList = new ArrayList(); + + for (Coordinate coor : coorList) { + Station s = new Station(); + s.setLongitude((float) coor.x); + s.setLatitude((float) coor.y); + stnList.add(s); + } + + return getSnapWithStation(coors, stnList, rounding, numOfCompassPts, + useJTS); + } + + /* + * parameters: + * + * azimuth: the actual direction numOfCompassPts: common Compass Point: + * 4/8/16/32, etc; a non-regular point will be coerced into the closest + * regular one + */ + + public static double getSnapDir(double azimuth, int numOfCompassPts) { + + double[] ap = getAzimuths(numOfCompassPts, true), am = getAzimuths( + numOfCompassPts, false); + TreeMap treeMap = new TreeMap(); + + if (azimuth == 0) { + return 0; + } else if (azimuth > 0) { + for (double d : ap) + treeMap.put(Math.abs(d - azimuth), d); + } else { + for (double dd : am) + treeMap.put(Math.abs(dd - azimuth), dd); + } + + return treeMap.get(treeMap.firstKey()); + } + + /* + * parameters: + * + * d: the actual distance to be rounded rounding: 10nm, 5nm, etc + */ + + public static double getSnapDistance(double d, int rounding) { + + if (rounding <= 0) + rounding = 10;// default + + int distance = (int) (d / NM2M); + int nm = distance / rounding; + double remain = distance % rounding; + if (remain >= (((double) rounding) / 2)) + nm++; + + return rounding * nm * NM2M; + } + + private static void initCompassPtsAzimuths() { + + compassPtsAzimuths.put(1, new double[] { 0 }); + compassPtsAzimuthsMinus.put(1, new double[] { 0 }); + + compassPtsAzimuths.put(2, new double[] { 0, 180 }); + compassPtsAzimuthsMinus.put(2, new double[] { 0, -180 }); + + compassPtsAzimuths.put(4, new double[] { 0, 90, 180 }); + compassPtsAzimuthsMinus.put(4, new double[] { 0, -90, -180 }); + + compassPtsAzimuths.put(6, new double[] { 0, 60, 120, 180 }); + compassPtsAzimuthsMinus.put(6, new double[] { 0, -60, -120, -180 }); + + compassPtsAzimuths.put(8, new double[] { 0, 45, 90, 135, 180 }); + compassPtsAzimuthsMinus + .put(8, new double[] { 0, -45, -90, -135, -180 }); + + compassPtsAzimuths.put(10, new double[] { 0, 36, 72, 108, 144, 180 }); + compassPtsAzimuthsMinus.put(10, new double[] { 0, -36, -72, -108, -144, + -180 }); + + compassPtsAzimuths.put(12, + new double[] { 0, 30, 60, 90, 120, 150, 180 }); + compassPtsAzimuthsMinus.put(12, new double[] { 0, -30, -60, -90, -120, + -150, -180 }); + + compassPtsAzimuths.put(16, new double[] { 0, 22.5, 45, 67.5, 90, 112.5, + 135, 157.5, 180 }); + compassPtsAzimuthsMinus.put(16, new double[] { 0, -22.5, -45, -67.5, + -90, -112.5, -135, -157.5, -180 }); + + compassPtsAzimuths.put(18, new double[] { 0, 20, 40, 60, 80, 100, 120, + 140, 160, 180 }); + compassPtsAzimuthsMinus.put(18, new double[] { 0, -20, -40, -60, -80, + -100, -120, -140, -160, -180 }); + + compassPtsAzimuths.put(32, new double[] { 0, 11.25, 22.5, 33.75, 45, + 56.25, 67.5, 78.75, 90, 101.25, 112.5, 123.75, 135, 146.25, + 157.5, 168.75, 180 }); + compassPtsAzimuthsMinus.put(32, new double[] { 0, -11.25, -22.5, + -33.75, -45, -56.25, -67.5, -78.75, -90, -101.25, -112.5, + -123.75, -135, -146.25, -157.5, -168.75, -180 }); + + } + + private static int getCompassPoint(int pts) { + + Set keys = compassPtsAzimuths.keySet(); + + if (keys.contains(pts)) + return pts; + + if (pts <= 0 || pts > 360) + return 8;// default; + + TreeMap map = new TreeMap(); + + for (Integer i : keys) + map.put(Math.abs(pts - i), i); + + return map.get(map.firstKey()); + } + + public static double[] getAzimuths(int numOfCompassPts, boolean positive) { + + int key = getCompassPoint(numOfCompassPts); + + return positive ? compassPtsAzimuths.get(key) : compassPtsAzimuthsMinus + .get(key); + } + + /** + * Creates a text from the coordinates with respect to VOR stations. + * + * @param coors + * @param vorConnector + * @param lineType + * @param numPerLines + * number of coordinates per line, if negative, then + * Integer.MAX_VALUE is used instead. + * @param isSnapped + * boolean if the coors have been snapped + * @return VOR text string + */ + public static String getVORText(Coordinate[] coors, String vorConnector, + String lineType, int numPerLines, boolean isSnapped) { + return getVORText(coors, vorConnector, lineType, numPerLines, + isSnapped, true, false); + } + + /** + * Creates a text from the coordinates with respect to VOR stations. + * + * @param coors + * @param vorConnector + * @param lineType + * @param numPerLines + * number of coordinates per line, if negative, then + * Integer.MAX_VALUE is used instead. + * @param isSnapped + * boolean if the coors have been snapped + * @param useJTS + * boolean false to speed up the calculation (bypass + * the use of JTS library), true by default + * @return VOR text string + */ + public static String getVORText(Coordinate[] coors, String vorConnector, + String lineType, int numPerLines, boolean isSnapped, + boolean useJTS, boolean isGfa) { + return getVORText(coors, vorConnector, lineType, numPerLines, + isSnapped, true, isGfa, null); + } + + /** + * Creates a text from the coordinates with respect to VOR stations. + * + * @param coors + * @param vorConnector + * @param lineType + * @param numPerLines + * number of coordinates per line, if negative, then + * Integer.MAX_VALUE is used instead. + * @param isSnapped + * boolean if the coors have been snapped + * @param useJTS + * boolean false to speed up the calculation (bypass + * teh use of JTS library), true by default + * @param sigmetType + * the type of sigmet + * @return VOR text string + */ + public static String getVORText(Coordinate[] coors, String vorConnector, + String lineType, int numPerLines, boolean isSnapped, + boolean useJTS, boolean isGfa, String sigmetType) { + + if (lineType != null && lineType.startsWith("Line")) { + coors = reorderLineCoordinates(coors); + } else if ("Area".equals(lineType)) { + coors = reorderAreaCoordinates(coors); + } + + if (isSnapped) + return SnapVOR.getSnapVORTxt(coors, vorConnector, lineType, useJTS, + isGfa); + + List list = VOR_STATION_LIST; + GeodeticCalculator gc = new GeodeticCalculator(DefaultEllipsoid.WGS84); + + TreeMap treeMap = new TreeMap(); + ArrayList resultList = new ArrayList(); + + for (Coordinate coor : coors) { + + populateStationsTreeMap(treeMap, list, gc, coor, useJTS); + + double distance = treeMap.firstKey(); + Station vorStn = treeMap.get(distance); + gc.setStartingGeographicPoint(vorStn.getLongitude(), + vorStn.getLatitude()); + gc.setDestinationGeographicPoint(coor.x, coor.y); + + String azimuth = getAzimuthInNSWEString(gc.getAzimuth()); + + // resultList.add(new + // VORStation(vorStn.getStid(),azimuth,""+(int)(Math.round(distance/PgenUtil.NM2M)))); + /* + * Round distance to the nearest 10 nautical miles; If convective + * outlook and less than 30 nm, set to 0. + */ + distance = getSnapDistance(distance, 10) / NM2M; + if ("OUTL_SIGMET".equals(sigmetType) && (int) distance < 30) + distance = 0; + + resultList.add(new VORStation(vorStn.getStid(), azimuth, "" + + (int) distance)); + treeMap.clear(); + } + + if (numPerLines < 0) + numPerLines = Integer.MAX_VALUE; + + StringBuilder result = new StringBuilder(); + String first = ""; + int count = 0; + for (VORStation vs : resultList) { + if (isGfa) { + vs.setPgenType(GFA_TEXT); + } else if ("CONV_SIGMET".equals(sigmetType) + || "NCON_SIGMET".equals(sigmetType) + || "OUTL_SIGMET".equals(sigmetType)) { + vs.setPgenType(sigmetType); + } + + if (count == 0) + first = vs.toString();// first vor at the end for AREA + // TTR 726 (10/2014): still need a vorConnect at the end of line. + result.append(vs.toString() + + (((++count) % numPerLines) == 0 ? (vorConnector + "\n") + : vorConnector)); + } + String resultString = "Area".equals(lineType) ? result.append(first) + .toString() : result.substring(0, + result.lastIndexOf(vorConnector)); + ; + return resultString; + } + + /* + * This function reorders (Line) processing of points to do either + * west-to-east or north-to-south. West-to-east defined as all points within + * W2ELIM degrees of one another. + * + * @param coors the coordinates containing the lats and lons + * + * @return newCoors reordered coordinates + */ + public static Coordinate[] reorderLineCoordinates(Coordinate[] coors) { + + if (coors == null) + return coors; + + final int N2S = 0, W2E = 1; + final double W2ELIM = 0.1; + + int lineOrder = N2S; + boolean reverse = false; + double minLat, maxLat, dLat; + int numPoints = coors.length; + + minLat = coors[0].y; + maxLat = minLat; + + for (Coordinate coor : coors) { + minLat = Math.min(minLat, coor.y); + maxLat = Math.max(maxLat, coor.y); + } + + dLat = Math.abs(maxLat - minLat); + + if (dLat <= W2ELIM) + lineOrder = W2E; + + if (lineOrder == N2S && coors[0].y < coors[numPoints - 1].y) + reverse = true; + if (lineOrder == W2E && coors[0].x > coors[numPoints - 1].x) + reverse = true; Coordinate[] newCoors = new Coordinate[numPoints]; - - if ( dirNext < dirPrev ) { - i = 0; - for ( j = iptr; j < numPoints; j++ ) { - newCoors[i] = coors[j]; - i++; - } - for ( j = 0; j < iptr; j++ ) { - newCoors[i] = coors[j]; - i++; - } + if (reverse) { + for (int i = 0; i < numPoints; i++) { + newCoors[i] = coors[numPoints - 1 - i]; + } + } else { + newCoors = coors; } - else { - i = 0; - for ( j = iptr; j >= 0; j-- ) { - newCoors[i] = coors[j]; - i++; - } - for ( j = numPoints-1; j > iptr; j-- ) { - newCoors[i] = coors[j]; - i++; - } - } - + return newCoors; - } - - /* - * This function figures out the direction from point 1 to point 2. - * - * @param coor1 coordinates for point 1 - * @param coor2 coordinates for point 2 - * @return dir direction from point 1 to point 2 - * - */ - public static double getDirection(Coordinate coor1, Coordinate coor2) { - - final double PI = 3.14159265, - HALFPI = PI / 2.0, - RTD = 180.0 / PI, - DTR = PI / 180.0; - - double lat1d, lat2d, lon1d = 0.0,lon2d = 0.0; - double dir, dLon, theta, alpha, val, tang; - - lat1d = (double) coor1.y * DTR; - lat2d = (double) coor2.y * DTR; - - if (Math.abs(coor1.x - coor2.x) < 180.0F) { - lon1d = ((double) (coor1.x + 360.0) % 360.0) * DTR; - lon2d = ((double) (coor2.x + 360.0) % 360.0) * DTR; - } - - dLon = lon1d - lon2d; - - if (Math.abs(lat2d - HALFPI) < 0.000001) { - dir = 180.0F; - } - else if (Math.abs(-lat2d - HALFPI) < 0.000001) { - dir = 0.0F; - } - else { - val = (double)( Math.sin(lat1d) * Math.sin(lat2d) + - Math.cos(lat1d) * Math.cos(lat2d) * Math.cos(dLon) ); - - if ( -1.0 <= val && val <= 1.0 ) { - theta = Math.acos(val); - - if (Math.abs(theta - 0.0) < 0.000001) { - dir = 0.0F; - } - else { - tang = ( Math.sin(lat1d) - Math.sin(lat2d) * Math.cos(theta) ) / - ( Math.cos(lat2d) * Math.sin(theta) ); - tang = Math.min( tang, 1.0 ); - tang = Math.max( tang, -1.0 ); - alpha = Math.acos( tang ); - - dir = (float)(alpha*RTD); - if ( dLon < 0.0 ) dir = 360.0 - dir; - } - } - else { - dir = 0.0; - } - } - - return dir; - } - - public static class VORStation extends Station{ - String name; - String azimuth; - String distance; - String pgenType=""; - - VORStation(String n,String z, String d){name=n;azimuth=z;distance=d;} - public void setPgenType(String pt){ this.pgenType = pt;} - - public String toString() { - if ("0".equals(distance)) { - return name; - } - - if("CONV_SIGMET".equalsIgnoreCase(pgenType) || - "NCON_SIGMET".equalsIgnoreCase(pgenType) || - "OUTL_SIGMET".equalsIgnoreCase(pgenType) ){ - return distance + azimuth + " " + name; - } - //For GFA - should be (30NNW LGC"), not "30 NNW LGC" - else if ( GFA_TEXT.equalsIgnoreCase( pgenType ) ){ - return distance + azimuth + " " + name; - } - - return distance + " " + azimuth + " " + name; - } } - - /** - * class for the Snapping to pre-calculated VOR points. - * - * @author gzhang - * - */ - public static class SnapVOR{ - - //query string parts - private static final String dbTable = "stns.snap";//"stns.snap_8"; - private static final String nameField = "station_name"; - private static final String latField = "latitude"; - private static final String lonField = "longitude"; - - //for snapped point-to-vor lookup - public static final Map coorStnMap = new HashMap(); - //envelops cannot cover all corner cases - private static List stnList = new ArrayList(); - - static{ - initSnapData(); - stnList.addAll(coorStnMap.values()); - } - - /** - * load the pre-calculted snapping VOR stations - * from the DB - * @return - */ - public static void initSnapData(){ - List allBounds = null; - StringBuilder query = new StringBuilder(); - - query.append( - "Select " - +nameField - + " , " - +latField - + " , " - +lonField - + " FROM " - + dbTable); - - try { - allBounds = NcDirectDbQuery.executeQuery( - query.toString(), "ncep", QueryLanguage.SQL); - }catch (VizException ve ){ - ve.printStackTrace(); - }catch (Throwable te){ - System.out.println("ERROR: "+te.getMessage()); - } - - if(allBounds == null || allBounds.size() == 0){ - System.out.println("*** Error loading data! "); - return; - } - - for(Object[] obs : allBounds){ - if(obs == null || obs.length < 3){ - continue; - } - - String n = (String)obs[0]; - if(n == null){ - n = ""; - }else{ - if(n.contains("_")){ - n = n.substring(n.indexOf("_")+1); - } - } - - String azimuth = getVORNameDirDist((String)obs[0])[1]; - - if(azimuth != null){ - - VORStation stn = new VORStation( - getVORNameDirDist((String)obs[0])[0], - azimuth, - getVORNameDirDist((String)obs[0])[2]); - - stn.setLatitude(((Double)obs[1]).floatValue()); - stn.setLongitude(((Double)obs[2]).floatValue()); - - Coordinate nCoor = new Coordinate( ((Double)obs[2]).floatValue(),((Double)obs[1]).floatValue()); - - coorStnMap.put(nCoor, stn); - } - } - } - - /** - * parse the stn name to get name, dir, dist - * - * @param String: station_name field from the snap table in ncep DB; - * @return String[]: elements: name,azimuth,distance; - */ - private static String[] getVORNameDirDist(String n){ - String[] ndd = null; - - if(n == null){ - ndd = new String[]{"","",""}; - }else{ - if(n.contains("_")){// i.e. 20N_YSJ - String name = n.substring(n.indexOf("_")+1); - - String dirDist = n.substring(0,n.indexOf("_")); - char[] c = dirDist.toCharArray(); - int dirIndex = -1; - for(int i=0; i 0 && dirIndex < n.indexOf("_")){ - dist = n.substring(0, dirIndex); - dir = n.substring(dirIndex, n.indexOf("_")); - } - - ndd = new String[]{name, dir, dist }; - - }else{//i.e. YSJ - ndd = new String[]{n,"","" }; - } - } - - return ndd; - } - - /** - * check the coordinate to get snap stations. - * @param stn: NOT USED (for Envlope:coordinate to be checked) - * @param numOfCompassPts: 8, 16,etc - * @return List all the snapping point stations - */ - public static List getSnapStns(Coordinate stn, int numOfCompassPts){ - - if(numOfCompassPts == 16) - return stnList; - - List list = stnList; - List list8 = null; - - /* - * list8 calculated for space consummation consideration - */ - if(numOfCompassPts == 8){ - list8 = new ArrayList(); - for( Station s : list){ - String a = ((VORStation)s).azimuth; - if( a != null && a.length() < 3 ){ - list8.add(s); - } - } - return list8; - } - - //being defensive - return new ArrayList(); - } - - /** - * Write out snap points location in the format of a "lpi" file to be - * loaded as an overlay. - * - * @param filename file to be written into - */ - public static void writeSnapStations ( String filename ){ - - Writer output = null; - File file = new File( "/export/cdbsrv/jwu/workbak/pgen/snaps.txt" ); - try { - FileWriter fw = new FileWriter( file ); - output = new BufferedWriter( fw ); + /* + * This function reorders a closed polygon into a clockwise fashion and the + * first point is the northernmost point. + * + * @param coors the coordinates containing the lats and lons + * + * @return newCoors reordered coordinates + */ + public static Coordinate[] reorderAreaCoordinates(Coordinate[] coors) { - for( Station s : stnList ){ - - String c = new String( ((VORStation)s).getLatitude() + "\t" + - ((VORStation)s).getLongitude() + "\t\t160\t\tnull\n" ); - output.write( c ); - } - - output.close(); - - } - catch ( IOException e ) { - e.printStackTrace(); - } - - } + if (coors == null) + return coors; - /** - * return the snapped VOR stations texts - * @param Coordinate[]: snapped points - * @param connector: "-" or " TO " - * @param lineType: Area or Line:ESOL - * @return String: VOR text - */ - public static String getSnapVORTxt(Coordinate[] coors, String connector, String lineType, boolean useJts, - boolean isGfa ){ - String txt = ""; - String pgenType = "-".equals(connector) ? "CONV_SIGMET" : "";//2010-05-14 work around TODO: - - StringBuilder sb = new StringBuilder(); - - for(int i=0; i coors){ - - if( coors == null || coors.size() == 0) - return false; - - boolean snappable = true; - - /* - * check if the points located inside one of - * the three envelopes: - * continental US, Alask, and Hawaii. - */ - Envelope envContUS = new Envelope(-130,-60,20,55); - //Envelope envAK = new Envelope(180, -130, 45,75);//double check - //Envelope envHA = new Envelope(-165,-150,15,27); //double check - - for(Coordinate c : coors){ - if( ! envContUS.contains(c)){// && ! envAK.contains(c) && ! envHA.contains(c) ){ - snappable = false; - break; - } - } - return snappable; - } - - } + int numPoints = coors.length; + int iptr = 0, i, j; + double maxLat, dirNext, dirPrev; - public static Coordinate getNorthMostPoint(Coordinate[] coors){ - if(coors == null || coors.length == 0) return new Coordinate();// or null? - - TreeMap map = new TreeMap(); - - for(Coordinate coor : coors) map.put(coor.y, coor); - - return map.get(map.lastKey()); - } + /* + * Re-order processing of points to do northernmost first and proceed + * clockwise. + */ + maxLat = -90.0; + for (i = 0; i < numPoints; i++) { + Coordinate coor = coors[i]; + if (coor.y > maxLat) { + iptr = i; + maxLat = coor.y; + } + } + + /* + * Check directions for each adjacent point; the point with the smallest + * angle from north is in the clockwise direction. + */ + if (iptr != 0) + dirPrev = getDirection(coors[iptr - 1], coors[iptr]); + else + dirPrev = getDirection(coors[numPoints - 1], coors[iptr]); + + if (iptr != numPoints - 1) + dirNext = getDirection(coors[iptr + 1], coors[iptr]); + else + dirNext = getDirection(coors[0], coors[iptr]); + + Coordinate[] newCoors = new Coordinate[numPoints]; + + if (dirNext < dirPrev) { + i = 0; + for (j = iptr; j < numPoints; j++) { + newCoors[i] = coors[j]; + i++; + } + for (j = 0; j < iptr; j++) { + newCoors[i] = coors[j]; + i++; + } + } else { + i = 0; + for (j = iptr; j >= 0; j--) { + newCoors[i] = coors[j]; + i++; + } + for (j = numPoints - 1; j > iptr; j--) { + newCoors[i] = coors[j]; + i++; + } + } + + return newCoors; + } + + /* + * This function figures out the direction from point 1 to point 2. + * + * @param coor1 coordinates for point 1 + * + * @param coor2 coordinates for point 2 + * + * @return dir direction from point 1 to point 2 + */ + public static double getDirection(Coordinate coor1, Coordinate coor2) { + + final double PI = 3.14159265, HALFPI = PI / 2.0, RTD = 180.0 / PI, DTR = PI / 180.0; + + double lat1d, lat2d, lon1d = 0.0, lon2d = 0.0; + double dir, dLon, theta, alpha, val, tang; + + lat1d = (double) coor1.y * DTR; + lat2d = (double) coor2.y * DTR; + + if (Math.abs(coor1.x - coor2.x) < 180.0F) { + lon1d = ((double) (coor1.x + 360.0) % 360.0) * DTR; + lon2d = ((double) (coor2.x + 360.0) % 360.0) * DTR; + } + + dLon = lon1d - lon2d; + + if (Math.abs(lat2d - HALFPI) < 0.000001) { + dir = 180.0F; + } else if (Math.abs(-lat2d - HALFPI) < 0.000001) { + dir = 0.0F; + } else { + val = (double) (Math.sin(lat1d) * Math.sin(lat2d) + Math.cos(lat1d) + * Math.cos(lat2d) * Math.cos(dLon)); + + if (-1.0 <= val && val <= 1.0) { + theta = Math.acos(val); + + if (Math.abs(theta - 0.0) < 0.000001) { + dir = 0.0F; + } else { + tang = (Math.sin(lat1d) - Math.sin(lat2d) * Math.cos(theta)) + / (Math.cos(lat2d) * Math.sin(theta)); + tang = Math.min(tang, 1.0); + tang = Math.max(tang, -1.0); + alpha = Math.acos(tang); + + dir = (float) (alpha * RTD); + if (dLon < 0.0) + dir = 360.0 - dir; + } + } else { + dir = 0.0; + } + } + + return dir; + } + + public static class VORStation extends Station { + String name; + + String azimuth; + + String distance; + + String pgenType = ""; + + VORStation(String n, String z, String d) { + name = n; + azimuth = z; + distance = d; + } + + public void setPgenType(String pt) { + this.pgenType = pt; + } + + public String toString() { + if ("0".equals(distance)) { + return name; + } + + if ("CONV_SIGMET".equalsIgnoreCase(pgenType) + || "NCON_SIGMET".equalsIgnoreCase(pgenType) + || "OUTL_SIGMET".equalsIgnoreCase(pgenType)) { + return distance + azimuth + " " + name; + } + // For GFA - should be (30NNW LGC"), not "30 NNW LGC" + else if (GFA_TEXT.equalsIgnoreCase(pgenType)) { + return distance + azimuth + " " + name; + } + + return distance + " " + azimuth + " " + name; + } + } + + /** + * class for the Snapping to pre-calculated VOR points. + * + * @author gzhang + * + */ + public static class SnapVOR { + + // query string parts + private static final String dbTable = "stns.snap";// "stns.snap_8"; + + private static final String nameField = "station_name"; + + private static final String latField = "latitude"; + + private static final String lonField = "longitude"; + + // for snapped point-to-vor lookup + public static final Map coorStnMap = new HashMap(); + + // envelops cannot cover all corner cases + private static List stnList = new ArrayList(); + + static { + initSnapData(); + stnList.addAll(coorStnMap.values()); + } + + /** + * load the pre-calculted snapping VOR stations from the DB + * + * @return + */ + public static void initSnapData() { + List allBounds = null; + StringBuilder query = new StringBuilder(); + + query.append("Select " + nameField + " , " + latField + " , " + + lonField + " FROM " + dbTable); + + try { + allBounds = NcDirectDbQuery.executeQuery(query.toString(), + "ncep", QueryLanguage.SQL); + } catch (VizException ve) { + ve.printStackTrace(); + } catch (Throwable te) { + System.out.println("ERROR: " + te.getMessage()); + } + + if (allBounds == null || allBounds.size() == 0) { + System.out.println("*** Error loading data! "); + return; + } + + for (Object[] obs : allBounds) { + if (obs == null || obs.length < 3) { + continue; + } + + String n = (String) obs[0]; + if (n == null) { + n = ""; + } else { + if (n.contains("_")) { + n = n.substring(n.indexOf("_") + 1); + } + } + + String azimuth = getVORNameDirDist((String) obs[0])[1]; + + if (azimuth != null) { + + VORStation stn = new VORStation( + getVORNameDirDist((String) obs[0])[0], azimuth, + getVORNameDirDist((String) obs[0])[2]); + + stn.setLatitude(((Double) obs[1]).floatValue()); + stn.setLongitude(((Double) obs[2]).floatValue()); + + Coordinate nCoor = new Coordinate( + ((Double) obs[2]).floatValue(), + ((Double) obs[1]).floatValue()); + + coorStnMap.put(nCoor, stn); + } + } + } + + /** + * parse the stn name to get name, dir, dist + * + * @param String + * : station_name field from the snap table in ncep DB; + * @return String[]: elements: name,azimuth,distance; + */ + private static String[] getVORNameDirDist(String n) { + String[] ndd = null; + + if (n == null) { + ndd = new String[] { "", "", "" }; + } else { + if (n.contains("_")) {// i.e. 20N_YSJ + String name = n.substring(n.indexOf("_") + 1); + + String dirDist = n.substring(0, n.indexOf("_")); + char[] c = dirDist.toCharArray(); + int dirIndex = -1; + for (int i = 0; i < c.length; i++) { + if (c[i] == 'N' || c[i] == 'E' || c[i] == 'S' + || c[i] == 'W') { + dirIndex = i; + break; + } + } + + String dist = "";// n.contains("0") ? n.substring(0, + // n.indexOf("0")+1) : ""; + String dir = "";// n.substring(n.indexOf("0")+1, + // n.indexOf("_")); + + if (dirIndex > 0 && dirIndex < n.indexOf("_")) { + dist = n.substring(0, dirIndex); + dir = n.substring(dirIndex, n.indexOf("_")); + } + + ndd = new String[] { name, dir, dist }; + + } else {// i.e. YSJ + ndd = new String[] { n, "", "" }; + } + } + + return ndd; + } + + /** + * check the coordinate to get snap stations. + * + * @param stn + * : NOT USED (for Envlope:coordinate to be checked) + * @param numOfCompassPts + * : 8, 16,etc + * @return List all the snapping point stations + */ + public static List getSnapStns(Coordinate stn, + int numOfCompassPts) { + + if (numOfCompassPts == 16) + return stnList; + + List list = stnList; + List list8 = null; + + /* + * list8 calculated for space consummation consideration + */ + if (numOfCompassPts == 8) { + list8 = new ArrayList(); + for (Station s : list) { + String a = ((VORStation) s).azimuth; + if (a != null && a.length() < 3) { + list8.add(s); + } + } + return list8; + } + + // being defensive + return new ArrayList(); + } + + /** + * Write out snap points location in the format of a "lpi" file to be + * loaded as an overlay. + * + * @param filename + * file to be written into + */ + public static void writeSnapStations(String filename) { + + Writer output = null; + File file = new File("/export/cdbsrv/jwu/workbak/pgen/snaps.txt"); + try { + FileWriter fw = new FileWriter(file); + output = new BufferedWriter(fw); + + for (Station s : stnList) { + + String c = new String(((VORStation) s).getLatitude() + "\t" + + ((VORStation) s).getLongitude() + + "\t\t160\t\tnull\n"); + output.write(c); + } + + output.close(); + + } catch (IOException e) { + e.printStackTrace(); + } + + } + + /** + * return the snapped VOR stations texts + * + * @param Coordinate + * []: snapped points + * @param connector + * : "-" or " TO " + * @param lineType + * : Area or Line:ESOL + * @return String: VOR text + */ + public static String getSnapVORTxt(Coordinate[] coors, + String connector, String lineType, boolean useJts, boolean isGfa) { + String txt = ""; + String pgenType = "-".equals(connector) ? "CONV_SIGMET" : "";// 2010-05-14 + // work + // around + // TODO: + + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < coors.length; i++) { + VORStation vStn = coorStnMap.get(coors[i]);// TODO: + // getVORStn(Coordinate + // stn, int + // numOfCompassPts) + + // for out of range of VOR stations elements + if (vStn == null) { + return getVORText(coors, connector, lineType, 6, false, + useJts, isGfa); + } + + vStn.setPgenType(pgenType);// for getting different string + // formatting + if (isGfa) { + vStn.setPgenType(GFA_TEXT); + } + sb.append(vStn.toString().trim());// TODO: text format + // differences + sb.append(connector); + } + if ("Area".equals(lineType)) { + txt = sb.append(sb.substring(0, sb.indexOf(connector))) + .toString(); + } else { + txt = sb.toString().substring(0, sb.lastIndexOf(connector)); + } + + return txt; + } + + /** + * check if the points within reasonable(250NM) snapping range. + * + * @param coors + * : points to be checked + * @return: true snappable; false otherwise. + */ + public static boolean isSnappable(List coors) { + + if (coors == null || coors.size() == 0) + return false; + + boolean snappable = true; + + /* + * check if the points located inside one of the three envelopes: + * continental US, Alask, and Hawaii. + */ + Envelope envContUS = new Envelope(-130, -60, 20, 55); + // Envelope envAK = new Envelope(180, -130, 45,75);//double check + // Envelope envHA = new Envelope(-165,-150,15,27); //double check + + for (Coordinate c : coors) { + if (!envContUS.contains(c)) {// && ! envAK.contains(c) && ! + // envHA.contains(c) ){ + snappable = false; + break; + } + } + return snappable; + } + + } + + public static Coordinate getNorthMostPoint(Coordinate[] coors) { + if (coors == null || coors.length == 0) + return new Coordinate();// or null? + + TreeMap map = new TreeMap(); + + for (Coordinate coor : coors) + map.put(coor.y, coor); + + return map.get(map.lastKey()); + } }