/*
* 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);
}
}