Graticule.java :  » Geo » geohashdroid » net » exclaimindustries » geohashdroid » Android Open Source

Android Open Source » Geo » geohashdroid 
geohashdroid » net » exclaimindustries » geohashdroid » Graticule.java
/**
 * Graticule.java
 * Copyright (C)2009 Nicholas Killewald
 * 
 * This file is distributed under the terms of the BSD license.
 * The source package should have a LICENCE file at the toplevel.
 */
package net.exclaimindustries.geohashdroid;

import com.google.android.maps.GeoPoint;

import android.location.Location;
import android.os.Parcel;
import android.os.Parcelable;

/**
 * <p>
 * A <code>Graticule</code> represents, well, a graticule. A 1x1 square degree
 * space on the earth's surface. The very heart of Geohashing*.  The base
 * implementation of a Graticule is designed to be immutable owing to a few odd
 * things that happen around the equator and Prime Meridian.
 * </p>
 * 
 * <p>
 * Note that Graticules are immutable.
 * </p>
 * 
 * <p>
 * *: Well, maybe not the heart. At least the kidneys for sure.
 * </p>
 * 
 * @author Nicholas Killewald
 */
public class Graticule implements Parcelable {
    private int mLatitude;
    private int mLongitude;

    // These are to account for the "negative zero" graticules.
    private boolean mSouth = false;
    private boolean mWest = false;
    
    /**
     * Constructs a new Graticule with the given Location object. This seems
     * like it's most likely what you want, but in the current version of this,
     * the Graticule is most likely made through the int constructor due to how
     * the information is gathered in the main interface.
     * 
     * @param location
     *            Location to make a new Graticule out of
     */
    public Graticule(Location location) {
        if (location.getLatitude() < 0)
            mSouth = true;
        if (location.getLongitude() < 0)
            mWest = true;
        this.setLatitude(Math.abs((int)location.getLatitude()));
        this.setLongitude(Math.abs((int)location.getLongitude()));
    }

    /**
     * Constructs a new Graticule with the given GeoPoint object. Similar deal
     * to the Location version of this.
     * 
     * @param point
     *            GeoPoint to make a new Graticule out of
     */
    public Graticule(GeoPoint point) {
        if (point.getLatitudeE6() < 0)
            mSouth = true;
        if (point.getLongitudeE6() < 0)
            mWest = true;
        setLatitude(Math.abs((int)(point.getLatitudeE6() / 1000000)));
        setLongitude(Math.abs((int)(point.getLongitudeE6() / 1000000)));
    }

    /**
     * <p>
     * Constructs a new Graticule with the given latitude and longitude. Note
     * that values that shoot around the planet will be clamped to 89 degrees
     * latitude and 179 degrees longitude (positive or negative).
     * </p>
     * 
     * <p>
     * With this constructor, you <b>MUST</b> specify if this is south or west
     * (that is, negative values). This is to account for the "negative zero"
     * graticules, for those living on the Prime Meridian or equator, as you
     * can't very well input -0 as a Java int and have it distinct from 0.
     * </p>
     * 
     * <p>
     * This will also ignore any negatives in your inputs (-75 will become 75).
     * </p>
     * 
     * @param latitude
     *            latitude to set
     * @param south
     *            true if south, false if north
     * @param longitude
     *            longitude to set
     * @param west
     *            true if west, false if east
     */
    public Graticule(int latitude, boolean south, int longitude, boolean west) {
        this.mSouth = south;
        this.mWest = west;
        this.setLatitude(Math.abs(latitude));
        this.setLongitude(Math.abs(longitude));
    }

    /**
     * <p>
     * Constructs a new Graticule with the given latitude and longitude as
     * doubles. This can thus make Graticules directly from GPS inputs. Note
     * that values that shoot around the planet will be clamped to 89 degrees
     * latitude and 179 degrees longitude (positive or negative).
     * </p>
     * 
     * <p>
     * Negative values will be interpreted as south and west. Please don't use
     * this if you're standing directly on the equator and/or Prime Meridian and
     * GPS gives you a direct zero.
     * </p>
     * 
     * @param latitude
     *            latitude to set
     * @param longitude
     *            longitude to set
     */
    public Graticule(double latitude, double longitude) {
        if (latitude < 0)
            mSouth = true;
        else
            mSouth = false;
        if (longitude < 0)
            mWest = true;
        else
            mWest = false;
        this.setLatitude(Math.abs((int)latitude));
        this.setLongitude(Math.abs((int)longitude));
    }

    /**
     * Constructs a new Graticule with the given String forms of the latitude
     * and longitude.
     * 
     * @param latitude
     *            latitude to set
     * @param longitude
     *            longitude to set
     * @throws NullPointerException
     *             either of the input strings were empty
     * @throws NumberFormatException
     *             either of the input strings weren't numbers
     */
    public Graticule(String latitude, String longitude)
            throws NullPointerException, NumberFormatException {
        if (latitude.charAt(0) == '-')
            mSouth = true;
        else
            mSouth = false;
        if (longitude.charAt(0) == '-')
            mWest = true;
        else
            mWest = false;
        this.setLatitude(Math.abs(new Integer(latitude)));
        this.setLongitude(Math.abs(new Integer(longitude)));
    }

    /**
     * <p>
     * Constructs a new Graticule offset from an existing one.  That is to say,
     * copy an existing Graticule and move it by however many degrees as is
     * specified.  Under the current implementation, if this gets offset past
     * the edges of the earth, it will attempt to wrap around.  This allows
     * people in the far eastern regions of Russia to see the nearby meetup
     * points if they happen to live near the 180E/W longitude line.  It does
     * not, however, allow for penguins and Santa Claus yet, so don't try to
     * fling yourself over the poles.
     * </p>
     *
     * <p>
     * Note carefully that moving one degree west of zero longitude will go to
     * "negative zero" longitude.  Same with latitude.  There is a distinction.
     * Therefore, be very careful when crossing the Prime Meridian and/or the
     * equator.
     * </p>
     *
     * @param g
     *            Graticule to copy
     * @param latOff
     *            number of degrees north to offset (negative is south)
     * @param lonOff
     *            number of degrees east to offset (negative is west)
     * @return
     *             a brand spakin' new Graticule, offset as per suggestion
     */
    public static Graticule createOffsetFrom(Graticule g, int latOff, int lonOff) {
        // We already have all the data we need from the old Graticule.  But,
        // we need to account for passing through the Prime Meridian and/or
        // equator.  If the sign changes, decrement the amount of the change by
        // one.  This logic is gratiutously loopy.
        boolean goingSouth = (latOff < 0);
        latOff = Math.abs(latOff);

        int finalLat = g.getLatitude();
        int finalLon = g.getLongitude();
        boolean finalSouth = g.isSouth();
        boolean finalWest = g.isWest();

        // Skip the following if latitude is unaffected.
        if (latOff != 0) {
            if (g.isSouth() == goingSouth) {
                // Going the same direction, no equator-hacking needed.
                finalLat = g.getLatitude() + latOff;
            } else {
                // Going opposite directions, check for equator-hacking.
                if (g.getLatitude() < latOff) {
                    // We cross the equator!
                    latOff--;
                    finalSouth = !finalSouth;
                }
                finalLat = Math.abs(g.getLatitude() - latOff);
            }
        }

        // Meridian hacking can be handled differently to also cover planet-
        // wrapping at the same time.  This entire stunt depends on treating
        // the longitude as a value between 0 and 359, inclusive.  In this,
        // 179W is 0, 0W is 179, 0E is 180, and 179E is 359.
        
        // Adjust us properly.  Remember the negative zero graticules!
        if(finalWest)
            finalLon = -finalLon + 179;
        else
            finalLon += 180;
        
        finalLon += lonOff;
        finalLon %= 360;
        
        if(finalLon < 0) finalLon = 360 - Math.abs(finalLon);
        
        if(finalLon >= 180) {
            finalWest = false;
            finalLon -= 180;
        } else {
            finalWest = true;
            finalLon -= 179;
        }
        
        finalLon = Math.abs(finalLon);
        
        // Now make the new Graticule object and return it.
        return new Graticule(finalLat, finalSouth, finalLon, finalWest);
    }
    
    /**
     * Deparcelizinate a Graticule.
     * 
     * @param in the parcel to deparcelize
     */
    private Graticule(Parcel in) {
        readFromParcel(in);
    }
    
    public static final Parcelable.Creator<Graticule> CREATOR = new Parcelable.Creator<Graticule>() {
        public Graticule createFromParcel(Parcel in) {
            return new Graticule(in);
        }

        public Graticule[] newArray(int size) {
            return new Graticule[size];
        }
    };
    
    /**
     * Deparcel.  Read from parcel.  Deparcelize.  This constructs a Graticule
     * from a Parcel.
     * 
     * @param in parcel to deparcelize
     */
    public void readFromParcel(Parcel in) {
        // For the sake of efficiency, we store exactly two things in the
        // parcel.  Specifically, the latitude and longitude, represented from
        // 0-179 and 0-356, respectively, going from 89 south to 89 north and
        // 179 west to 179 east (both including a negative zero).  We can
        // determine mSouth and mWest from there.
        int absLat = in.readInt();
        int absLon = in.readInt();
        
        // I swear, if these wind up not being valid, I reserve the right to
        // dope slap you.
        if(absLat < 90) {
            mSouth = true;
            setLatitude(89 - absLat);
        } else {
            mSouth = false;
            setLatitude(absLat - 90);
        }
        
        if(absLon < 180) {
            mWest = true;
            setLongitude(179 - absLon);
        } else {
            mWest = false;
            setLongitude(absLon - 180);
        }
    }
    
    @Override
    public int describeContents() {
        // BLAH BLAH BLAH
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        // Hey!  We've got a parcel to write out!  To compress this down a bit
        // further, we want to only store two ints (instead of two ints and two
        // booleans).  See the comments in readFromParcel for details.  To wit:
        
        // Latitude!
        if(mSouth)
            dest.writeInt(Math.abs(mLatitude - 89));
        else
            dest.writeInt(mLatitude + 90);
        
        if(mWest)
            dest.writeInt(Math.abs(mLongitude - 179));
        else
            dest.writeInt(mLongitude + 180);
    }

    /**
     * Returns true if the 30W Rule is in effect. Which is to say, anything east
     * of -30 longitude uses yesterday's stock value, regardless of if the DJIA
     * was updated to that point.  Note that this only determines if the
     * graticule itself abides by the 30W Rule; if the date is May 26, 2008 or
     * earlier, 30W is ignored.
     * 
     * @return true if the 30W Rule is in effect, false otherwise
     */
    public boolean uses30WRule() {
        return ((mLongitude < 30 && isWest()) || !isWest());
    }

    private void setLatitude(int latitude) {
        // Work out invalid entries by clamping 'em down.
        if (latitude > 89)
            latitude = 89;
        
        this.mLatitude = latitude;
    }

    /**
     * Returns the absolute value of the current latitude. Run this against
     * isSouth() to figure out what the negative should be.
     * 
     * @return the absolute value of the current latitude
     */
    public int getLatitude() {
        return mLatitude;
    }

    /**
     * Returns the current latitude as a String to account for negative zero
     * graticule wackiness.
     * @param useNegativeValues true to return values as negative for south and positive for north, false to return values with N and S indicators
     * @return the current latitude as a String
     */
    public String getLatitudeString(boolean useNegativeValues) {
        if (mSouth) {
            if(useNegativeValues) {
                return "-" + mLatitude;                
            } else {
                return mLatitude + "S";
            }
        } else {
            if(useNegativeValues) {
                return new Integer(mLatitude).toString();                
            } else {
                return mLatitude + "N";
            }
        }
    }
    
    private void setLongitude(int longitude) {
        // Clamp!  Clamp!  Clamp!
        if (longitude > 179)
            longitude = 179;
       
        this.mLongitude = longitude;
    }

    /**
     * Returns the absolute value of the current longitude. Run this against
     * isEast() to figure out what the negative should be.
     * 
     * @return the absolute value of the current longitude
     */
    public int getLongitude() {
        return mLongitude;
    }

    /**
     * Returns the current longitude as a String to account for negative zero
     * graticule madness.
     * 
     * @param useNegativeValues true to return values as negative for west and positive for east, false to return values with E and W indicators
     * @return the current longitude as a String
     */
    public String getLongitudeString(boolean useNegativeValues) {
        if (mWest) {
            if(useNegativeValues) {
                return "-" + mLongitude;
            } else {
                return mLongitude + "W";
            }
        } else {
            if(useNegativeValues) {
                return new Integer(mLongitude).toString();
            } else {
                return mLongitude + "E";
            }
        }
    }

    /**
     * Returns whether or not this is a southern latitude (negative).
     * 
     * @return true if south, false if north
     */
    public boolean isSouth() {
        return mSouth;
    }

    /**
     * Returns whether or not this is an western longitude (negative).
     * 
     * @return true if west, false if east.
     */
    public boolean isWest() {
        return mWest;
    }

    /**
     * Returns the center of this Graticule as a GeoPoint.
     * 
     * @return a GeoPoint representing the center of this Graticule.
     */
    public GeoPoint getCenter() {
        int lat;
        int lon;

        // The concept of "center" changes when we're dealing with negative
        // graticules. So...
        if (isSouth()) {
            lat = (getLatitude() * -1000000) - 500000;
        } else {
            lat = (getLatitude() * 1000000) + 500000;
        }

        if (isWest()) {
            lon = (getLongitude() * -1000000) - 500000;
        } else {
            lon = (getLongitude() * 1000000) + 500000;
        }

        return new GeoPoint(lat, lon);
    }
    
    /**
     * Grab the top-left GeoPoint of this Graticule.  Or, well, the northwest.
     * 
     * @return a GeoPoint representing the top-left corner of this Graticule
     */
    public GeoPoint getTopLeft() {
        int top;
        int left;
        
        if (isSouth()) {
            top = (-1 * getLatitude()) * 1000000;
        } else {
            top = (getLatitude() + 1) * 1000000;
        }

        if (isWest()) {
            left = (-1 * getLongitude()) * 1000000;
        } else {
            left = (getLongitude() + 1) * 1000000;
        }
        
        return new GeoPoint(top, left);
    }
    
    /**
     * Grab the bottom-right GeoPoint of this Graticule.  Or, well, the southeast.
     * 
     * @return a GeoPoint representing the bottom-right corner of this Graticule
     */
    public GeoPoint getBottomRight() {
        int bottom;
        int right;
        if (isSouth()) {
            bottom = (-1 * (getLatitude() + 1)) * 1000000;
        } else {
            bottom = getLatitude() * 1000000;
        }

        if (isWest()) {
            right = (-1 * (getLongitude() + 1)) * 1000000;
        } else {
            right = getLongitude() * 1000000;
        }
        
        return new GeoPoint(bottom, right);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object o) {
        // First, this better be a Graticule.
        if (!(o instanceof Graticule))
            return false;

        Graticule g = (Graticule)o;

        // If everything matches up, these are identical. Two int checks and
        // two boolean checks are probably a lot faster than two String checks,
        // right?
        if (g.getLatitude() != getLatitude()
                || g.getLongitude() != getLongitude()
                || g.isSouth() != isSouth() || g.isWest() != isWest())
            return false;
        else
            return true;
    }
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.