// // McIDASUtil.java // /* This source file is part of the edu.wisc.ssec.mcidas package and is Copyright (C) 1998 - 2017 by Tom Whittaker, Tommy Jasmin, Tom Rink, Don Murray, James Kelly, Bill Hibbard, Dave Glowacki, Curtis Rueden and others. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA */ package edu.wisc.ssec.mcidas; import java.util.Calendar; import java.util.GregorianCalendar; import java.util.Date; import java.util.TimeZone; import java.io.*; import edu.wisc.ssec.mcidas.AreaFile; /** * Class for static McIDAS utility methods. In many cases, these * methods are the Java equivalents of McIDAS library functions. * @see <A HREF="http://www.ssec.wisc.edu/mcidas/doc/prog_man.html"> * McIDAS Programmer's Manual</A> * * @author Don Murray, Unidata * @author Tom Whittaker, SSEC */ public final class McIDASUtil { /** McIDAS missing value for 4-byte integers */ public static final int MCMISSING = 0x80808080; /** * Converts a packed integer (SIGN DDD MM SS) latitude/longitude to double. * Java version of McIDAS <code>flalo</code> function except returns a * double instead of a float. * * @param value integer containing the packed data * @return double representation of value */ public static double integerLatLonToDouble(int value) { return mcPackedIntegerToDouble(value); } /** * Converts a double latitude/longitude to a packed integer (SIGN DDD MM SS) * Java version of McIDAS <code>ilalo</code> function. * * @param dvalue double value of lat/lon * * @return packed integer representation of value */ public static int doubleLatLonToInteger(double dvalue) { return mcDoubleToPackedInteger(dvalue); } /** * Converts a packed integer (SIGN DDD/HH MM SS) latitude/longitude * or time (hours) to double. * Java replacements of McIDAS <code>flalo</code> and <code>ftime</code> * functions except returns a double instead of a float. * * @param value integer containing the packed data * @return double representation of value */ public static double mcPackedIntegerToDouble(int value) { int val = value < 0 ? -value : value; double dvalue = ((double)(val / 10000) + ((double)((val / 100) % 100)) / 60.0 + (double)(val % 100) / 3600.0); return (value < 0) ? -dvalue : dvalue; } /** * Converts a double latitude/longitude or time (hours) to a * packed integer (SIGN DDD/HH MM SS). Java replacements of McIDAS * <code>ilalo</code> and <code>m0itime</code> functions. * * @param dvalue double value of lat/lon or time * * @return packed integer representation of value */ public static int mcDoubleToPackedInteger(double dvalue) { double dval = dvalue < 0 ? -dvalue : dvalue; int j = (int)(3600.0 * dval + 0.5); int value = 10000 * (j / 3600) + 100 * ((j / 60) % 60) + j % 60; return (dvalue < 0.0) ? -value : value; } /** * Calculate difference in minutes between two dates/times. Java * version of timdif.for * * @param yrday1 Year/day of first time (yyddd or yyyyddd) * @param hms1 Hours/minutes/seconds of first time (hhmmss). * @param yrday2 Year/day of second time (yyddd). * @param hms2 Hours/minutes/seconds of second time (hhmmss). * * @return The difference between the two times (time2 - time1), * in minutes. If the first time is greater than the second, * the result will be negative. */ public static double timdif(int yrday1, int hms1, int yrday2, int hms2) { long secs1 = mcDayTimeToSecs(yrday1, hms1); long secs2 = mcDayTimeToSecs(yrday2, hms2); return (double)(secs2 - secs1) / 60.; } /** * Create a calendar to be used for mcDayTimeToSecs. * Use this to minimize object creation overhead when calling the method * many times. * * @return A calendar to use for mcDayTimeToSecs. */ public static GregorianCalendar makeCalendarForDayTimeToSecs() { GregorianCalendar cal = new GregorianCalendar(); cal.setTimeZone(TimeZone.getTimeZone("GMT")); cal.set(Calendar.ERA, GregorianCalendar.AD); /* allow us to specify # of days since the year began without having worry about leap years and seconds since the day began, instead of in the minute. Saves on some calculations. */ cal.setLenient(true); return cal; } /** * * Convert day (yyddd or yyyyddd) and time (hhmmss) to seconds since * the epoch (January 1, 1970, 00:00GMT). Java version of 'mcdaytimetosecs' * except it returns a long instead of an int. * * @param yearday year/day in either yyddd or yyyyddd format. * Only works for years > 1900. * @param time time in packed integer format (hhmmss) * * @return seconds since the epoch * */ public static long mcDayTimeToSecs(int yearday, int time) { return mcDayTimeToSecs(yearday, time, null); } /** * Convert day (yyddd or yyyyddd) and time (hhmmss) to seconds since * the epoch (January 1, 1970, 00:00GMT). Java version of 'mcdaytimetosecs' * except it returns a long instead of an int. * * @param yearday year/day in either yyddd or yyyyddd format. * Only works for years > 1900. * @param time time in packed integer format (hhmmss) * @param cal If non-null then use this calendar to do the formatting. * else create a new one. Note: The calendar you pass in should be * one created from makeCalendarForDayTimeToSecs * * @return seconds since the epoch * */ public static long mcDayTimeToSecs(int yearday, int time, GregorianCalendar cal) { //jeffmc: Add the cal parameter to this method if (cal == null) { cal = makeCalendarForDayTimeToSecs(); } int year = ((yearday / 1000) % 1900) + 1900; // convert to yyyyddd first int day = yearday % 1000; double seconds = mcPackedIntegerToDouble(time) * 3600.; cal.clear(); cal.set(Calendar.DAY_OF_YEAR, day); cal.set(Calendar.YEAR, year); int secs = ((int)Math.round(seconds * 1000)) / 1000; cal.set(Calendar.SECOND, secs); cal.set(Calendar.MILLISECOND, 0); //jeffmc: Change: // return cal.getTime().getTime()/1000; //to: return cal.getTimeInMillis() / 1000; } /** * Convert date (yymmdd or yyyymmdd) and hms (hhmmss) to seconds since * the epoch (January 1, 1970, 00:00GMT). * * @param date year/day in yyymmdd format. * Only works for years > 1900. * @param time time in packed integer format (hhmmss) * * @return seconds since the epoch * */ public static long mcDateHmsToSecs(int date, int time) { int year = date / 10000; if (year < 50) { year = year + 2000; } else if (year < 1000) { year = year + 1900; } int month = (date % 10000) / 100; int day = date % 100; /** * jeffmc: Comment these out? * System.out.println("year = " + year); * System.out.println("month = " + month); * System.out.println("day = " + day); */ double seconds = mcPackedIntegerToDouble(time) * 3600.; GregorianCalendar cal = new GregorianCalendar(); cal.clear(); cal.setTimeZone(TimeZone.getTimeZone("GMT")); cal.set(Calendar.ERA, GregorianCalendar.AD); cal.set(Calendar.YEAR, year); cal.set(Calendar.MONTH, month - 1); // stupid Calendar.MONTH is 0 based cal.set(Calendar.DAY_OF_MONTH, day); int secs = ((int)Math.round(seconds * 1000)) / 1000; cal.set(Calendar.SECOND, secs); cal.set(Calendar.MILLISECOND, 0); return cal.getTime().getTime() / 1000; } /** * Convert seconds since the epoch (January 1, 1970, 00:00GMT) to * day (yyyyddd) and time (hhmmss). Java version of * 'mcsecstodaytime' except it returns an int array instead of pointers * * @param secs seconds since the epoch * * @return int[2] array with day (yyyyddd) as first element and * time (hhmmss - packed integer) as second element. */ public static int[] mcSecsToDayTime(long secs) { int[] retvals = new int[2]; GregorianCalendar cal = new GregorianCalendar(); cal.clear(); cal.setTimeZone(TimeZone.getTimeZone("GMT")); cal.setTime(new Date(secs * 1000)); retvals[0] = (cal.get(cal.YEAR) * 1000) + cal.get(cal.DAY_OF_YEAR); retvals[1] = (cal.get(cal.HOUR_OF_DAY) * 10000) + (cal.get(cal.MINUTE) * 100) + cal.get(cal.SECOND); return retvals; } /** * Convert an HMS integer to a string of form hh:mm:ss. * @param hms integer hhmmss * @return string representation */ public static String mcHmsToStr(int hms) { StringBuffer buf = new StringBuffer(); int hours = (int)hms / 10000; int mins = (int)(hms % 10000) / 100; int secs = hms % 100; buf.append(padZero(hours, 2)); buf.append(":"); buf.append(padZero(mins, 2)); buf.append(":"); buf.append(padZero(secs, 2)); return buf.toString(); } /** * Left pad the given value with zeros up to the number of digits * * @param value The value. * @param numDigits number of digits * @return The String represenation of the value, padded with * leading "0"-s if value < 10E(numDigits-1) */ public static String padZero(int value, int numDigits) { return padLeft(String.valueOf(value), numDigits, "0"); } /** * Pad the given string with padString on the left up to the given length. * * @param s String to pad * @param desiredLength ending length * @param padString String to pad with (e.g, " ") * @return padded String */ public static String padLeft(String s, int desiredLength, String padString) { while(s.length() < desiredLength) { s = padString + s; } return s; } /** * Flip the bytes of an integer. * * @param val value to swap * * @return the flipped integer */ public static int swbyt4(int val) { int[] vals = new int[] {val}; flip(vals, 0, 0); return vals[0]; } /** * Flip the bytes of an integer array. Java version of 'm0swbyt4'. * * @param array array of integers to be flipped * @param first starting element of the array * @param num number of values to swap */ public static void swbyt4(int[] array, int first, int num) { flip(array, first, first + num - 1); } /** * Flip the bytes of an integer array. Java version of 'm0swbyt4'. * * @param array array of integers to be flipped * @param first starting element of the array * @param last last element of array to flip * */ public static void flip(int array[], int first, int last) { int i, k; for (i = first; i <= last; i++) { k = array[i]; array[i] = ((k >>> 24) & 0xff) | ((k >>> 8) & 0xff00) | ((k & 0xff) << 24) | ((k & 0xff00) << 8); } } /** * convert four consequtive bytes into a (signed) int. This * is useful in dealing with McIDAS data files. * * @param b array of 4 bytes * @param off is the offset into the byte array * * @return the integer value */ public static int bytesToInteger(byte[] b, int off) { int k = (b[off] << 24) + ((b[off + 1] << 16) & 0xff0000) + ((b[off + 2] << 8) & 0xff00) + ((b[off + 3] << 0) & 0xff); return k; } /** * convert consecutive bytes into a (signed) int array. This * is useful in dealing with McIDAS data files. * * @param b array of bytes * @param off is the offset into the byte array * @param num number of integers to create * * @return the array of values as integers */ public static int[] bytesToIntegerArray(byte[] b, int off, int num) { int[] values = new int[num]; for (int i = 0; i < num; i++) { byte[] bytes = new byte[4]; System.arraycopy(b, i * 4, bytes, 0, 4); values[i] = bytesToInteger(bytes, 0); } return values; } /** * convert signed int to a String representation. This is useful * in dealing with McIDAS data files. Java version of 'clit'. * * @param value - integer representation of a string * * @return String representation of the int */ public static String intBitsToString(int value) { byte[] bval = new byte[4]; bval[0] = (byte)((value & 0xff000000) >>> 24); bval[1] = (byte)((value & 0x00ff0000) >>> 16); bval[2] = (byte)((value & 0x0000ff00) >>> 8); bval[3] = (byte)((value & 0x000000ff) >>> 0); return new String(bval); } /** * convert signed int array to a String representation. This is useful * in dealing with McIDAS data files. Java version of 'movwc'. * * @param values - integer array representation of a string * * @return String representation of the int array */ public static String intBitsToString(int[] values) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < values.length; i++) sb.append(intBitsToString(values[i])); return sb.toString(); } /** * Check to see if the int value is the representation of a * string or not. Java version of ischar_.c (sort of). * * @param value integer representation * @return true if the int represents a string */ public static boolean isChar(int value) { String valueString = intBitsToString(value); char[] chars = valueString.toCharArray(); for (int i = 0; i < 4; i++) { if (!Character.UnicodeBlock.of(chars[i]).equals( Character.UnicodeBlock.BASIC_LATIN) || Character.isISOControl( chars[i])) return false; } return true; } /** * Serialize an AreaFile object to disk * * @param filename - name of disk file to write to * @param af * @return true if no Exception; false otherwise */ public static boolean putAreaFile(String filename, AreaFile af) { try { OutputStream os = new FileOutputStream(filename); ObjectOutput oo = new ObjectOutputStream(os); oo.writeObject(af); oo.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } } /** * De-serialize an AreaFile object from disk * * @param filename - name of disk file to read * * @return AreaFile if okay; null otherwise */ public static AreaFile getAreaFile(String filename) { try { InputStream is = new FileInputStream(filename); ObjectInput oi = new ObjectInputStream(is); AreaFile af = (AreaFile)oi.readObject(); oi.close(); return af; } catch (Exception ei) { ei.printStackTrace(); return null; } } }