/* * Copyright (C) 2006 Steve Ratcliffe * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. * * This program 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 General Public License for more details. * * * Author: Steve Ratcliffe * Create date: 03-Dec-2006 */ package uk.me.parabola.imgfmt; import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Calendar; import java.util.Date; import java.util.zip.GZIPInputStream; import uk.me.parabola.imgfmt.app.Coord; /** * Some miscellaneous functions that are used within the .img code. * * @author Steve Ratcliffe */ public class Utils { /** * Routine to convert a string to bytes and pad with a character up * to a given length. * Only to be used for strings that are expressible in latin1. * * @param s The original string. * @param len The length to pad to. * @param pad The byte used to pad. * @return An array created from the string. */ public static byte[] toBytes(String s, int len, byte pad) { if (s == null) throw new IllegalArgumentException("null string provided"); byte[] out = new byte[len]; for (int i = 0; i < len; i++) { if (i > s.length()) { out[i] = pad; } else { out[i] = (byte) s.charAt(i); } } return out; } public static byte[] toBytes(String s) { return toBytes(s, s.length(), (byte) 0); } /** * Convert from bytes to a string. Only to be used when the character set * is ascii or latin1. * * @param buf A byte buffer to get the bytes from. Should be ascii or latin1. * @param off The offset into buf. * @param len The length to get. * @return A string. */ public static String bytesToString(ByteBuffer buf, int off, int len) { if (buf == null) throw new IllegalArgumentException("null byte buffer provided"); byte[] bbuf = new byte[len]; buf.position(off); buf.get(bbuf); char[] cbuf = new char[len]; for (int i = 0; i < bbuf.length; i++) { cbuf[i] = (char) bbuf[i]; } return new String(cbuf); } /** * Set the creation date. Note that the year is encoded specially. * * @param buf The buffer to write into. It must have been properly positioned * beforehand. * @param date The date to set. */ public static void setCreationTime(ByteBuffer buf, Date date) { Calendar cal = Calendar.getInstance(); if (date != null) cal.setTime(date); fillBufFromTime(buf, cal); } /** * A map unit is an integer value that is 1/(2^24) degrees of latitude or * longitude. * * @param l The lat or long as decimal degrees. * @return An integer value in map units. */ public static int toMapUnit(double l) { double DELTA = 360.0D / (1 << 24) / 2; //Correct rounding if (l > 0) return (int) ((l + DELTA) * (1 << 24)/360); else return (int) ((l - DELTA) * (1 << 24)/360); } /** * Convert a date into the in-file representation of a date. * * @param date The date. * @return A byte stream in .img format. */ public static byte[] makeCreationTime(Date date) { Calendar cal = Calendar.getInstance(); if (date != null) cal.setTime(date); byte[] ret = new byte[7]; ByteBuffer buf = ByteBuffer.wrap(ret); buf.order(ByteOrder.LITTLE_ENDIAN); fillBufFromTime(buf, cal); return ret; } private static void fillBufFromTime(ByteBuffer buf, Calendar cal) { buf.putChar((char) cal.get(Calendar.YEAR)); buf.put((byte) (cal.get(Calendar.MONTH)+1)); buf.put((byte) cal.get(Calendar.DAY_OF_MONTH)); buf.put((byte) cal.get(Calendar.HOUR_OF_DAY)); buf.put((byte) cal.get(Calendar.MINUTE)); buf.put((byte) cal.get(Calendar.SECOND)); } /** * Make a date from the garmin representation. * @param date The bytes representing the date. * @return A java date. */ public static Date makeCreationTime(byte[] date) { Calendar cal = Calendar.getInstance(); int y = ((date[1] & 0xff) << 8) + (date[0] & 0xff); cal.set(y, date[2]-1, date[3], date[4], date[5], date[6]); return cal.getTime(); } /** * Convert an angle in map units to degrees. */ public static double toDegrees(int val) { return (double) val * (360.0 / (1 << 24)); } /** * Convert an angle in map units to radians. */ public static double toRadians(int mapunits) { return toDegrees(mapunits) * (Math.PI / 180); } public static void closeFile(Closeable f) { if (f != null) { try { f.close(); } catch (IOException e) { e.printStackTrace(); } } } /** * Open a file and apply filters necessary for reading it such as * decompression. * * @param name The file to open. * @return A stream that will read the file, positioned at the beginning. * @throws FileNotFoundException If the file cannot be opened for any reason. */ public static InputStream openFile(String name) throws FileNotFoundException { InputStream is = new FileInputStream(name); if (name.endsWith(".gz")) { try { is = new GZIPInputStream(is); } catch (IOException e) { throw new FileNotFoundException( "Could not read as compressed file"); } } return is; } public static String joinPath(String dir, String basename, String ext) { return joinPath(dir, basename + "." + ext); } public static String joinPath(String dir, String basename) { File file = new File(dir, basename); return file.getAbsolutePath(); } /** * Rounds an integer up to the nearest multiple of {@code 2^shift}. * Works with both positive and negative integers. * @param val the integer to round up. * @param shift the power of two to round up to. * @return the rounded integer. */ public static int roundUp(int val, int shift) { return (val + (1 << shift) - 1) >>> shift << shift; } /** * Calculates the angle between the two segments (c1,c2),(c2,c3). * It is assumed that the segments are rhumb lines, not great circle paths. * @param c1 first point * @param c2 second point * @param c3 third point * @return angle between the two segments in degree [-180;180] */ public static double getAngle(Coord c1, Coord c2, Coord c3) { double a = c2.bearingTo(c1); double b = c2.bearingTo(c3); double angle = b - (a - 180); while(angle > 180) angle -= 360; while(angle < -180) angle += 360; return angle; } /** * Calculates the angle between the two segments (c1,c2),(c2,c3) * using the coords in map units. * @param c1 first point * @param c2 second point * @param c3 third point * @return angle between the two segments in degree [-180;180] */ public static double getDisplayedAngle(Coord c1, Coord c2, Coord c3) { return getAngle(c1.getDisplayedCoord(), c2.getDisplayedCoord(), c3.getDisplayedCoord()); } public final static int NOT_STRAIGHT = 0; public final static int STRAIGHT_SPIKE = 1; public final static int STRICTLY_STRAIGHT = 2; /** * Checks if the two segments (c1,c2),(c2,c3) form a straight line. * @param c1 first point * @param c2 second point * @param c3 third point * @return NOT_STRAIGHT, STRAIGHT_SPIKE or STRICTLY_STRAIGHT */ public static int isStraight(Coord c1, Coord c2, Coord c3) { if (c1.equals(c3)) return STRAIGHT_SPIKE; long area; // calculate the area that is enclosed by the three points // (as if a closing line is drawn from c3 back to c1) area = ((long)c1.getLongitude() * c2.getLatitude() - (long)c2.getLongitude() * c1.getLatitude()); area += ((long)c2.getLongitude() * c3.getLatitude() - (long)c3.getLongitude() * c2.getLatitude()); area += ((long)c3.getLongitude() * c1.getLatitude() - (long)c1.getLongitude() * c3.getLatitude()); if (area == 0){ // area is empty-> points lie on a straight line int delta1 = c1.getLatitude() - c2.getLatitude(); int delta2 = c2.getLatitude() - c3.getLatitude(); if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0) return STRAIGHT_SPIKE; delta1 = c1.getLongitude() - c2.getLongitude(); delta2 = c2.getLongitude() - c3.getLongitude(); if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0) return STRAIGHT_SPIKE; return STRICTLY_STRAIGHT; } // line is not straight return NOT_STRAIGHT; } /** * Checks if the two segments (c1,c2),(c2,c3) form a straight line * using high precision coordinates. * @param c1 first point * @param c2 second point * @param c3 third point * @return NOT_STRAIGHT, STRAIGHT_SPIKE or STRICTLY_STRAIGHT */ public static int isHighPrecStraight(Coord c1, Coord c2, Coord c3) { if (c1.highPrecEquals(c3)) return STRAIGHT_SPIKE; long area; long c1Lat = c1.getHighPrecLat(); long c2Lat = c2.getHighPrecLat(); long c3Lat = c3.getHighPrecLat(); long c1Lon = c1.getHighPrecLon(); long c2Lon = c2.getHighPrecLon(); long c3Lon = c3.getHighPrecLon(); // calculate the area that is enclosed by the three points // (as if a closing line is drawn from c3 back to c1) area = c1Lon * c2Lat - c2Lon * c1Lat; area += c2Lon * c3Lat - c3Lon * c2Lat; area += c3Lon * c1Lat - c1Lon * c3Lat; if (area == 0){ // area is empty-> points lie on a straight line long delta1 = c1Lat - c2Lat; long delta2 = c2Lat - c3Lat; if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0) return STRAIGHT_SPIKE; delta1 = c1Lon - c2Lon; delta2 = c2Lon - c3Lon; if (delta1 < 0 && delta2 > 0 || delta1 > 0 && delta2 < 0) return STRAIGHT_SPIKE; return STRICTLY_STRAIGHT; } // line is not straight return NOT_STRAIGHT; } /** * approximate atan2, much faster than Math.atan2() * Based on a 50-year old arctan approximation due to Hastings */ private final static double PI_BY_2 = Math.PI / 2; // |error| < 0.005 public static double atan2_approximation( double y, double x ) { if ( x == 0.0f ) { if ( y > 0.0f ) return PI_BY_2 ; if ( y == 0.0f ) return 0.0f; return -PI_BY_2 ; } double atan; double z = y/x; if ( Math.abs( z ) < 1.0f ) { atan = z/(1.0f + 0.28f*z*z); if ( x < 0.0f ) { if ( y < 0.0f ) return atan - Math.PI; return atan + Math.PI; } } else { atan = PI_BY_2 - z/(z*z + 0.28f); if ( y < 0.0f ) return atan - Math.PI; } return atan; } /** * calculate a long value for the latitude and longitude of a coord * in high precision. * @param co * @return a long that can be used as a key in HashMaps */ public static long coord2Long(Coord co){ int lat30 = co.getHighPrecLat(); int lon30 = co.getHighPrecLon(); return (long)(lat30 & 0xffffffffL) << 32 | (lon30 & 0xffffffffL); } }