//********************************************************************** // //<copyright> // //BBN Technologies, a Verizon Company //10 Moulton Street //Cambridge, MA 02138 //(617) 873-8000 // //Copyright (C) BBNT Solutions LLC. All rights reserved. // //</copyright> //********************************************************************** // //$Source: ///cvs/darwars/ambush/aar/src/com/bbn/ambush/mission/MissionHandler.java,v //$ //$RCSfile: GeoProj.java,v $ //$Revision: 1.7 $ //$Date: 2009/02/25 22:34:04 $ //$Author: dietrick $ // //********************************************************************** package com.bbn.openmap.proj; import java.awt.Point; import java.awt.geom.Arc2D; import java.awt.geom.Point2D; import java.util.ArrayList; import com.bbn.openmap.Environment; import com.bbn.openmap.MoreMath; import com.bbn.openmap.proj.coords.GeoCoordTransformation; import com.bbn.openmap.proj.coords.LatLonGCT; import com.bbn.openmap.proj.coords.LatLonPoint; import com.bbn.openmap.util.Debug; /** * GeoProj is the base class of all Projections that deal with coordinates on * the world sphere. * <p> * You probably don't want to use this class unless you are hacking your own * projections, or need extended functionality. To be safe you will want to use * the Projection interface. * * <h3>Notes:</h3> * * <ul> * * <li>We deal in radians internally. The outside world usually deals in decimal * degrees. If you have data in radians, DON'T bother converting it into DD's * since we'll convert it right back into radians for the projection step. For * more optimization tips, see the OMPoly class. * * <li>We default to projecting our data using the WGS84 datum. You can change * the appropriate parameters of the projection after construction if you need * to use a different datum. And of course you can derive your own projections * from this class as you see fit. * * <li>The forward() and inverse() methods are currently implemented using the * algorithms given in John Synder's <i>Map Projections --A Working Manual </i> * for the sphere. This is sufficient for display purposes, but you should use * ellipsoidal algorithms in the GreatCircle class to calculate distance and * azimuths on the ellipsoid. See each projection individually for more * information. * * <li>This class is not thread safe. If two or more threads are using the same * Proj, then they could disrupt each other. Occasionally you may need to call a * <code>set</code> method of this class. This might interfere with another * thread that's using the same projection for <code>forwardPoly</code> or * another Projection interface method. In general, you should not need to call * any of the <code>set</code> methods directly, but let the MapBean do it for * you. * * <li>All the various <code>forwardOBJ()</code> methods for ArrayList graphics * ultimately go through <code>forwardPoly()</code>. * * </ul> * * @see Projection * @see Cylindrical * @see Mercator * @see CADRG * @see Azimuth * @see Orthographic * @see Planet * @see GreatCircle * @see com.bbn.openmap.omGraphics.OMPoly * */ public abstract class GeoProj extends Proj { // Used for generating segments of List objects protected static transient int NUM_DEFAULT_CIRCLE_VERTS = 64; // SOUTH_POLE <= phi <= NORTH_POLE (radians) // -DATELINE <= lambda <= DATELINE (radians) /** * North pole latitude in radians. */ public final static transient float NORTH_POLE = ProjMath.NORTH_POLE_F; /** * South pole latitude in radians. */ public final static transient float SOUTH_POLE = ProjMath.SOUTH_POLE_F; /** * Dateline longitude in radians. */ public final static transient float DATELINE = ProjMath.DATELINE_F; // Used for generating segments of ArrayList objects protected static transient int NUM_DEFAULT_GREAT_SEGS = 512; // pixels per meter (an extra scaling factor). protected double pixelsPerMeter; // PPM protected double planetRadius;// EARTH_RADIUS protected double planetPixelRadius; // EARTH_PIX_RADIUS protected double planetPixelCircumference; // EARTH_PIX_CIRCUMFERENCE protected double scaled_radius; protected Mercator mercator = null; // for rhumbline calculations public GeoProj(LatLonPoint center, float s, int w, int h) { super(center, s, w, h); // for rhumbline projecting if (!(this instanceof Mercator)) { mercator = new Mercator(center, (float) scale, width, height); } } protected void init() { centerX = wrapLongitude(Math.toRadians(centerX)); centerY = normalizeLatitude(Math.toRadians(centerY)); // pixels per meter (an extra scaling factor). pixelsPerMeter = Planet.defaultPixelsPerMeter; // PPM planetRadius = Planet.wgs84_earthEquatorialRadiusMeters_D;// EARTH_RADIUS planetPixelRadius = planetRadius * pixelsPerMeter; // EARTH_PIX_RADIUS planetPixelCircumference = MoreMath.TWO_PI_D * planetPixelRadius; // EARTH_PIX_CIRCUMFERENCE // the scaled_radius should also be set in computeParameters with the // scale that has been checked against min/max scale scaled_radius = planetPixelRadius / scale; /* good for cylindrical */ maxscale = planetPixelCircumference / width; } /** * Set the pixels per meter constant. * * @param ppm int Pixels Per Meter scale-factor constant */ public void setPPM(double ppm) { pixelsPerMeter = ppm; if (pixelsPerMeter < 1) { pixelsPerMeter = 1; } computeParameters(); } /** * Get the pixels-per-meter constant. * * @return int Pixels Per Meter scale-factor constant */ public double getPPM() { return pixelsPerMeter; } /** * Set the planet radius. * * @param radius float planet radius in meters */ public void setPlanetRadius(double radius) { planetRadius = radius; if (planetRadius < 1.0f) { planetRadius = 1.0f; } computeParameters(); } /** * Get the planet radius. * * @return float radius of planet in meters */ public double getPlanetRadius() { return planetRadius; } /** * Get the planet pixel radius. * * @return float radius of planet in pixels */ public double getPlanetPixelRadius() { return planetPixelRadius; } /** * Get the planet pixel circumference. * * @return float circumference of planet in pixels */ public double getPlanetPixelCircumference() { return planetPixelCircumference; } /** * Set center point of projection. * * @param lat float latitude in decimal degrees * @param lon float longitude in decimal degrees */ public void setCenter(float lat, float lon) { setCenter((double) lat, (double) lon, false); } /** * Set center point of projection. * * @param lat double latitude in decimal degrees * @param lon double longitude in decimal degrees */ public void setCenter(double lat, double lon) { setCenter(lat, lon, false); } /** * Set center point of projection. * * @param lat double latitude in decimal degrees * @param lon double longitude in decimal degrees */ public void setCenter(double lat, double lon, boolean isRadians) { if (!isRadians) { lat = Math.toRadians(lat); lon = Math.toRadians(lon); } centerX = wrapLongitude(lon); centerY = normalizeLatitude(lat); computeParameters(); projID = null; } /** * Get center point of projection. * * @return LatLonPoint center of projection, created just for you. */ public LatLonPoint getCenter() { return new LatLonPoint.Double(centerY, centerX, true); } /** * Returns a center LatLonPoint that was provided, with the location filled * into the LatLonPoint object. Calls Point2D.setLocation(x, y). */ public <T extends Point2D> T getCenter(T center) { center.setLocation(Math.toDegrees(centerX), Math.toDegrees(centerY)); return center; } /** * Sets radian latitude to something sane. * <p> * Normalizes the latitude according to the particular projection. * * @param lat float latitude in radians * @return float latitude (-PI/2 <= y <= PI/2) * @see ProjMath#normalizeLatitude(float, float) * @see LatLonPoint#normalizeLatitude(float) */ public float normalizeLatitude(float lat) { return (float) normalizeLatitude((double) lat); } abstract public double normalizeLatitude(double lat); /** * Sets radian longitude to something sane. * * @param lon float longitude in radians * @return float longitude (-PI <= x < PI) * @see ProjMath#wrapLongitude(float) * @see LatLonPoint#wrapLongitude(float) */ public final static float wrapLongitude(float lon) { return ProjMath.wrapLongitude(lon); } public final static double wrapLongitude(double lon) { return ProjMath.wrapLongitude(lon); } public final static double wrapLongitudeDeg(double lon) { return ProjMath.wrapLongitudeDeg(lon); } /** * @deprecated use normalizeLatitude() instead. */ public final double normalize_latitude(double lat) { return normalizeLatitude(lat); } /** * @deprecated use wrapLongitude() instead. */ public final static double wrap_longitude(double lon) { return wrapLongitude(lon); } /** * Pan the map/projection. * <p> * Example pans: * <ul> * <li><code>pan(180, c)</code> pan south `c' degrees * <li><code>pan(-90, c)</code> pan west `c' degrees * <li><code>pan(0, c)</code> pan north `c' degrees * <li><code>pan(90, c)</code> pan east `c' degrees * </ul> * * @param Az azimuth "east of north" in decimal degrees: * <code>-180 <= Az <= 180</code> * @param c arc distance in decimal degrees */ public void pan(double Az, double c) { setCenter(RhumbCalculator.calculatePointOnRhumbLine(getCenter(), Math.toRadians(Az), Math.toRadians(c))); } /** * Pan the map/projection. Figures on an ellipse based around the center of * the map in the direction provided. * * @param Az azimuth "east of north" in decimal degrees: * <code>-180 <= Az <= 180</code> */ public void pan(double Az) { double angle = Math.toRadians(90 - Az); int w2 = getWidth() / 2; int h2 = getHeight() / 2; LatLonPoint newLoc = inverse(w2 + (w2 * Math.cos(angle)), h2 + (h2 * Math.sin(angle))); double dist = RhumbCalculator.getDistanceBetweenPoints(getCenter(), newLoc); pan(Az, Math.toDegrees(dist)); } /** * Stringify the projection. * * @return stringified projection * @see #getProjectionID */ public String toString() { return (" radius=" + planetRadius + " ppm=" + pixelsPerMeter + " center(" + centerY + ":" + centerX + ") scale=" + scale + " maxscale=" + maxscale + " minscale=" + minscale + " width=" + width + " height=" + height + "]"); } /** * Copies this projection. * * @return a copy of this projection. */ public Object clone() { GeoProj proj = (GeoProj) super.clone(); if (mercator != null) { proj.mercator = (Mercator) mercator.clone(); } return proj; } public boolean isPlotable(Point2D point) { if (point instanceof LatLonPoint) { return isPlotable(point.getY(), point.getX()); } // Well, then, what is it? return false; } /** * Forward project a rhumbline poly. * <p> * Draws rhumb lines between vertices of poly. Remember to specify vertices * in radians! Check in-code comments for details about the algorithm. * * @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS! * @param nsegs number of segments to draw for greatcircle or rhumb lines * (if < 1, this value is generated internally). * @param isFilled filled poly? * @return ArrayList float[] of x[], y[], x[], y[], ... projected poly */ protected ArrayList<float[]> forwardRhumbPoly(float[] rawllpts, int nsegs, boolean isFilled) { // IDEA: // Rhumblines are straight in the Mercator projection. // So we can use the Mercator projection to calculate // vertices along the rhumbline between two points. But // if there's a better way to calculate loxodromes, // someone please chime in (openmap@bbn.com)... // // ALG: // Project pairs of vertices through the Mercator // projection into screen XY space, pick intermediate // segment points along the straight XY line, then // convert all vertices back into LatLon space. Pass the // augmented vertices to _forwardPoly() to be drawn as // straight segments. // // WARNING: // The algorithm fixes the Cylindrical-wrapping // problem, and thus duplicates some code in // Cylindrical._forwardPoly() if (this instanceof Mercator) {// simple return _forwardPoly(rawllpts, LineType.Straight, nsegs, isFilled); } int i, n, xp, flag = 0, xadj = 0, totalpts = 0; Point from = new Point(0, 0); Point to = new Point(0, 0); int len = rawllpts.length; float[][] augllpts = new float[len >>> 1][0]; // lock access to object global, since this is probably not // cloned and different layers may be accessing this. // synchronized (mercator) { // we now create a clone of the mercator variable in // makeClone(), so since different objects should be using // their clone instead of the main projection, the // synchronization should be unneeded. // use mercator projection to calculate rhumblines. // mercator.setParms( // new LatLonPoint(ctrLat, ctrLon, true), // scale, width, height); // Unnecessary to set parameters !! ^^^^^ // project everything through the Mercator projection, // building up lat/lon points along the original rhumb // line between vertices. mercator.forward(rawllpts[0], rawllpts[1], from, true); xp = from.x; for (i = 0, n = 2; n < len; i++, n += 2) { mercator.forward(rawllpts[n], rawllpts[n + 1], to, true); // segment crosses longitude along screen edge if (Math.abs(xp - to.x) >= mercator.half_world) { flag += (xp < to.x) ? -1 : 1;// inc/dec the wrap // count xadj = flag * mercator.world.x;// adjustment to x // coordinates // Debug.output("flag=" + flag + " xadj=" + xadj); } xp = to.x; if (flag != 0) { to.x += xadj;// adjust x coordinate } augllpts[i] = mercator.rhumbProject(from, to, false, nsegs); totalpts += augllpts[i].length; from.x = to.x; from.y = to.y; } LatLonPoint llp = new LatLonPoint.Double(); mercator.inverse(from.x, from.y, llp); // }// end synchronized around mercator augllpts[i] = new float[2]; augllpts[i][0] = (float) llp.getRadLat(); augllpts[i][1] = (float) llp.getRadLon(); totalpts += 2; // put together all the points float[] newllpts = new float[totalpts]; int pos = 0; for (i = 0; i < augllpts.length; i++) { // Debug.output("copying " + augllpts[i].length + " // floats"); System.arraycopy( /* src */augllpts[i], 0, /* dest */newllpts, pos, augllpts[i].length); pos += augllpts[i].length; } // Debug.output("done copying " + totalpts + " total floats"); // free unused variables augllpts = null; // now delegate the work to the regular projection code. return _forwardPoly(newllpts, LineType.Straight, -1, isFilled); } /** * Forward project a rhumbline poly. * <p> * Draws rhumb lines between vertices of poly. Remember to specify vertices * in radians! Check in-code comments for details about the algorithm. * * @param rawllpts double[] of lat,lon,lat,lon,... in RADIANS! * @param nsegs number of segments to draw for greatcircle or rhumb lines * (if < 1, this value is generated internally). * @param isFilled filled poly? * @return ArrayList float[] of x[], y[], x[], y[], ... projected poly */ protected ArrayList<float[]> forwardRhumbPoly(double[] rawllpts, int nsegs, boolean isFilled) { // IDEA: // Rhumblines are straight in the Mercator projection. // So we can use the Mercator projection to calculate // vertices along the rhumbline between two points. But // if there's a better way to calculate loxodromes, // someone please chime in (openmap@bbn.com)... // // ALG: // Project pairs of vertices through the Mercator // projection into screen XY space, pick intermediate // segment points along the straight XY line, then // convert all vertices back into LatLon space. Pass the // augmented vertices to _forwardPoly() to be drawn as // straight segments. // // WARNING: // The algorithm fixes the Cylindrical-wrapping // problem, and thus duplicates some code in // Cylindrical._forwardPoly() if (this instanceof Mercator) {// simple return _forwardPoly(rawllpts, LineType.Straight, nsegs, isFilled); } int i, n, xp, flag = 0, xadj = 0, totalpts = 0; Point from = new Point(0, 0); Point to = new Point(0, 0); int len = rawllpts.length; double[][] augllpts = new double[len >>> 1][0]; // lock access to object global, since this is probably not // cloned and different layers may be accessing this. // synchronized (mercator) { // we now create a clone of the mercator variable in // makeClone(), so since different objects should be using // their clone instead of the main projection, the // synchronization should be unneeded. // use mercator projection to calculate rhumblines. // mercator.setParms( // new LatLonPoint(ctrLat, ctrLon, true), // scale, width, height); // Unnecessary to set parameters !! ^^^^^ // project everything through the Mercator projection, // building up lat/lon points along the original rhumb // line between vertices. mercator.forward(rawllpts[0], rawllpts[1], from, true); xp = from.x; for (i = 0, n = 2; n < len; i++, n += 2) { mercator.forward(rawllpts[n], rawllpts[n + 1], to, true); // segment crosses longitude along screen edge if (Math.abs(xp - to.x) >= mercator.half_world) { flag += (xp < to.x) ? -1 : 1;// inc/dec the wrap // count xadj = flag * mercator.world.x;// adjustment to x // coordinates // Debug.output("flag=" + flag + " xadj=" + xadj); } xp = to.x; if (flag != 0) { to.x += xadj;// adjust x coordinate } augllpts[i] = mercator.rhumbProjectDouble(from, to, false, nsegs); totalpts += augllpts[i].length; from.x = to.x; from.y = to.y; } LatLonPoint llp = new LatLonPoint.Double(); mercator.inverse(from, llp); // }// end synchronized around mercator augllpts[i] = new double[2]; augllpts[i][0] = llp.getRadLat(); augllpts[i][1] = llp.getRadLon(); totalpts += 2; // put together all the points double[] newllpts = new double[totalpts]; int pos = 0; for (i = 0; i < augllpts.length; i++) { // Debug.output("copying " + augllpts[i].length + " // floats"); System.arraycopy( /* src */augllpts[i], 0, /* dest */newllpts, pos, augllpts[i].length); pos += augllpts[i].length; } // Debug.output("done copying " + totalpts + " total floats"); // free unused variables augllpts = null; // now delegate the work to the regular projection code. return _forwardPoly(newllpts, LineType.Straight, -1, isFilled); } /** * Forward project a greatcircle poly. * <p> * Draws great circle lines between vertices of poly. Remember to specify * vertices in radians! * * @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS! * @param nsegs number of segments to draw for greatcircle or rhumb lines * (if < 1, this value is generated internally). * @param isFilled filled poly? * @return ArrayList float[] of x[], y[], x[], y[], ... projected poly */ protected ArrayList<float[]> forwardGreatPoly(float[] rawllpts, int nsegs, boolean isFilled) { int i, j, k, totalpts = 0; Point from = new Point(); Point to = new Point(); int end = rawllpts.length >>> 1; float[][] augllpts = new float[end][0]; end -= 1;// stop before last segment // calculate extra vertices between all the original segments. forward(rawllpts[0], rawllpts[1], from, true); for (i = 0, j = 0, k = 2; i < end; i++, j += 2, k += 2) { forward(rawllpts[k], rawllpts[k + 1], to, true); augllpts[i] = getGreatVertices(rawllpts[j], rawllpts[j + 1], rawllpts[k], rawllpts[k + 1], from, to, false, nsegs); from.x = to.x; from.y = to.y; totalpts += augllpts[i].length; } augllpts[i] = new float[2]; augllpts[i][0] = rawllpts[j]; augllpts[i][1] = rawllpts[j + 1]; totalpts += 2; // put together all the points float[] newllpts = new float[totalpts]; int pos = 0; for (i = 0; i < augllpts.length; i++) { System.arraycopy( /* src */augllpts[i], 0, /* dest */newllpts, pos, augllpts[i].length); pos += augllpts[i].length; } // free unused variables augllpts = null; // now delegate the work to the regular projection code. return _forwardPoly(newllpts, LineType.Straight, -1, isFilled); } /** * Forward project a greatcircle poly. * <p> * Draws great circle lines between vertices of poly. Remember to specify * vertices in radians! * * @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS! * @param nsegs number of segments to draw for greatcircle or rhumb lines * (if < 1, this value is generated internally). * @param isFilled filled poly? * @return ArrayList float[] of x[], y[], x[], y[], ... projected poly */ protected ArrayList<float[]> forwardGreatPoly(double[] rawllpts, int nsegs, boolean isFilled) { int i, j, k, totalpts = 0; Point from = new Point(); Point to = new Point(); int end = rawllpts.length >>> 1; double[][] augllpts = new double[end][0]; end -= 1;// stop before last segment // calculate extra vertices between all the original segments. forward(rawllpts[0], rawllpts[1], from, true); for (i = 0, j = 0, k = 2; i < end; i++, j += 2, k += 2) { forward(rawllpts[k], rawllpts[k + 1], to, true); augllpts[i] = getGreatVertices(rawllpts[j], rawllpts[j + 1], rawllpts[k], rawllpts[k + 1], from, to, false, nsegs); from.x = to.x; from.y = to.y; totalpts += augllpts[i].length; } augllpts[i] = new double[2]; augllpts[i][0] = rawllpts[j]; augllpts[i][1] = rawllpts[j + 1]; totalpts += 2; // put together all the points double[] newllpts = new double[totalpts]; int pos = 0; for (i = 0; i < augllpts.length; i++) { System.arraycopy( /* src */augllpts[i], 0, /* dest */newllpts, pos, augllpts[i].length); pos += augllpts[i].length; } // free unused variables augllpts = null; // now delegate the work to the regular projection code. return _forwardPoly(newllpts, LineType.Straight, -1, isFilled); } /** * Get the vertices along the great circle between two points. * * @param latp previous float latitude * @param lonp previous float longitude * @param latn next float latitude * @param lonn next float longitude * @param from Point * @param to Point * @param include_last include n or n+1 points of the n segments? * @return float[] lat/lon points in RADIANS! * */ private float[] getGreatVertices(float latp, float lonp, float latn, float lonn, Point from, Point to, boolean include_last, int nsegs) { if (nsegs < 1) { // calculate pixel distance int dist = DrawUtil.pixel_distance(from.x, from.y, to.x, to.y); /* * determine what would be a decent number of segments to draw. * HACK: this is hardcoded calculated by what might look ok on * screen. We also put a cap on the number of extra segments we * draw. */ nsegs = dist >> 3;// dist/8 if (nsegs == 0) { nsegs = 1; } else if (nsegs > NUM_DEFAULT_GREAT_SEGS) { nsegs = NUM_DEFAULT_GREAT_SEGS; } // Debug.output( // "("+from.x+","+from.y+")("+to.x+","+to.y+") // dist="+dist+" nsegs="+nsegs); } // both of these return float[] radian coordinates! return GreatCircle.greatCircle(latp, lonp, latn, lonn, nsegs, include_last); } /** * Get the vertices along the great circle between two points. * * @param latp previous double latitude * @param lonp previous double longitude * @param latn next double latitude * @param lonn next double longitude * @param from Point * @param to Point * @param include_last include n or n+1 points of the n segments? * @param nsegs number of segments to create, or -1 to let algorithm figure * it out * @return double[] lat/lon points in RADIANS! * */ private double[] getGreatVertices(double latp, double lonp, double latn, double lonn, Point from, Point to, boolean include_last, int nsegs) { if (nsegs < 1) { // calculate pixel distance int dist = DrawUtil.pixel_distance(from.x, from.y, to.x, to.y); /* * determine what would be a decent number of segments to draw. * HACK: this is hardcoded calculated by what might look ok on * screen. We also put a cap on the number of extra segments we * draw. */ nsegs = dist >> 3;// dist/8 if (nsegs == 0) { nsegs = 1; } else if (nsegs > NUM_DEFAULT_GREAT_SEGS) { nsegs = NUM_DEFAULT_GREAT_SEGS; } // Debug.output( // "("+from.x+","+from.y+")("+to.x+","+to.y+") // dist="+dist+" nsegs="+nsegs); } // both of these return float[] radian coordinates! return GreatCircle.greatCircle(latp, lonp, latn, lonn, nsegs, include_last); } /** * Check for complicated linetypes. * <p> * This depends on the line and this projection. * * @param ltype int LineType * @return boolean */ public boolean isComplicatedLineType(int ltype) { switch (ltype) { case LineType.Straight: return false; case LineType.Rhumb: return (getClass() == Mercator.class) ? false : true; case LineType.GreatCircle: return true/* * (getProjectionType() == Gnomonic.GnomonicType) ? false * : true */; default: Debug.error("Proj.isComplicatedLineType: invalid LineType!"); return false; } } /** * Generates a complicated poly. * * @param rawllpts LatLonPofloat[] * @param ltype line type * @param nsegs number of segments to draw for greatcircle or rhumb lines * (if < 1, this value is generated internally). * @param isFilled filled poly? * @return ArrayList */ protected ArrayList<float[]> doPolyDispatch(float[] rawllpts, int ltype, int nsegs, boolean isFilled) { switch (ltype) { case LineType.Rhumb: return forwardRhumbPoly(rawllpts, nsegs, isFilled); case LineType.GreatCircle: return forwardGreatPoly(rawllpts, nsegs, isFilled); case LineType.Straight: Debug.error("Proj.doPolyDispatch: Bad Dispatch!\n"); return new ArrayList<float[]>(0); default: Debug.error("Proj.doPolyDispatch: Invalid LType!\n"); return new ArrayList<float[]>(0); } } /** * Generates a complicated poly. * * @param rawllpts LatLonPofloat[] * @param ltype line type * @param nsegs number of segments to draw for greatcircle or rhumb lines * (if < 1, this value is generated internally). * @param isFilled filled poly? * @return ArrayList int[] */ protected ArrayList<float[]> doPolyDispatch(double[] rawllpts, int ltype, int nsegs, boolean isFilled) { switch (ltype) { case LineType.Rhumb: return forwardRhumbPoly(rawllpts, nsegs, isFilled); case LineType.GreatCircle: return forwardGreatPoly(rawllpts, nsegs, isFilled); case LineType.Straight: Debug.error("Proj.doPolyDispatch: Bad Dispatch!\n"); return new ArrayList<float[]>(0); default: Debug.error("Proj.doPolyDispatch: Invalid LType!\n"); return new ArrayList<float[]>(0); } } // /** // * Given a couple of points representing a bounding box, find out what the // * scale should be in order to make those points appear at the corners of // * the projection. // * // * @param ll1 the upper left coordinates of the bounding box. // * @param ll2 the lower right coordinates of the bounding box. // * @param point1 a java.awt.Point reflecting a pixel spot on the // projection // * that matches the ll1 coordinate, the upper left corner of the area // * of interest. // * @param point2 a java.awt.Point reflecting a pixel spot on the // projection // * that matches the ll2 coordinate, usually the lower right corner of // * the area of interest. // */ // public float getScale(Point2D ll1, Point2D ll2, Point2D point1, // Point2D point2) { // if (ll1 instanceof LatLonPoint && ll2 instanceof LatLonPoint) { // return getScale(LatLonPoint.getDouble(ll1), // LatLonPoint.getDouble(ll2), // point1, // point2); // } // // return getScale(); // } /** * Given a couple of points representing a bounding box, find out what the * scale should be in order to make those points appear at the corners of * the projection. * * @param ll1 the upper left coordinates of the bounding box. * @param ll2 the lower right coordinates of the bounding box. * @param point1 a java.awt.Point reflecting a pixel spot on the projection * that matches the ll1 coordinate, the upper left corner of the area * of interest. * @param point2 a java.awt.Point reflecting a pixel spot on the projection * that matches the ll2 coordinate, usually the lower right corner of * the area of interest. */ public float getScale(Point2D ll1, Point2D ll2, Point2D point1, Point2D point2) { try { double deltaDegrees; double pixPerDegree; //double deltaPix; //double dx = Math.abs(point2.getX() - point1.getX()); //double dy = Math.abs(point2.getY() - point1.getY()); // TODO: mercator getScale is wrong for screens in portrait mode, // that is dx<dy. why does this handle portrait different than // landscape? /* * if (dx < dy) { double dlat = Math.abs(ll1.getY() - ll2.getY()); * deltaDegrees = dlat; deltaPix = dy; * * // This might not be correct for all projection types * pixPerDegree = getPlanetPixelCircumference() / 360.0; } else { */ double dlon; double lat1, lon1, lon2; // point1 is to the right of point2. switch the // LatLonPoints so that ll1 is west (left) of ll2. if (point1.getX() > point2.getX()) { lat1 = ll1.getY(); lon1 = ll1.getX(); ll1.setLocation(ll2); ll2.setLocation(lon1, lat1); } lon1 = ll1.getX(); lon2 = ll2.getX(); // allow for crossing dateline if (lon1 > lon2) { dlon = (180 - lon1) + (180 + lon2); } else { dlon = lon2 - lon1; } deltaDegrees = dlon; //deltaPix = dx; // This might not be correct for all projection types pixPerDegree = getPlanetPixelCircumference() / 360.0; // } // The new scale, need it to match the current projection width. return (float) (pixPerDegree / (getWidth() / deltaDegrees)); } catch (NullPointerException npe) { com.bbn.openmap.util.Debug.error("ProjMath.getScale(): caught null pointer exception."); return Float.MAX_VALUE; } } /** * Forward project a point. */ public Point2D forward(Point2D llp, Point2D pt) { return forward(llp.getY(), llp.getX(), pt, false); } /** * Forward project a LatLonPoint. * <p> * Forward projects a LatLon point into XY space. Returns a Point. * * @param llp LatLonPoint to be projected * @return Point (new) */ public Point2D forward(Point2D llp) { return forward(llp.getY(), llp.getX(), new Point2D.Double(), false); } /** * Project the point into view space. * * @param lat latitude in decimal degrees. * @param lon longitue in decimal degrees. */ public Point2D forward(double lat, double lon, Point2D pt) { return forward(lat, lon, pt, false); } /** * Project the point into view space. * * @param lat latitude * @param lon longitude * @param pt return point * @param isRadian true if lat/lon are radians instead of decimal degrees * @return Point2D for projected point */ public Point2D forward(float lat, float lon, Point2D pt, boolean isRadian) { return forward((double) lat, (double) lon, pt, isRadian); } /** * Project the point into view space. * * @param lat latitude * @param lon longitude * @param pt return point * @param isRadian true if lat/lon are radians instead of decimal degrees * @return Point2D for projected point */ abstract public Point2D forward(double lat, double lon, Point2D pt, boolean isRadian); /** * Inverse project a Point from x,y space to LatLon space. * * @param point x,y Point * @return LatLonPoint (new) */ public LatLonPoint inverse(Point2D point) { return inverse(point.getX(), point.getY(), new LatLonPoint.Double()); } /** * Inverse project x,y coordinates. * * @param x integer x coordinate * @param y integer y coordinate * @return LatLonPoint (new) * @see #inverse(Point2D) */ public LatLonPoint inverse(double x, double y) { return inverse(x, y, new LatLonPoint.Double()); } /** * Returns the Point2D provided if it is a LatLonPoint, otherwise it creates * a LatLonPoint.Double and transfers the values from the provided Point2D * object. */ protected LatLonPoint assertLatLonPoint(Point2D p2d) { if (p2d instanceof LatLonPoint) { return (LatLonPoint) p2d; } else { return new LatLonPoint.Double(p2d.getY(), p2d.getX()); } } /** * Forward project a LatLon Line. * <p> * Returns a ArrayList of (x[], y[]) coordinate pair(s) of the projected * line(s). * * <a name="line_restrictions"></a> * <b>RESTRICTIONS:</b> * A line segment must be less than 180 degrees of arc (half the * circumference of the world). If you need to draw a longer line, then draw * several several individual segments of less than 180 degrees, or draw a * single polyline of those segments. * <p> * We make this restriction because from any point on a sphere, you can * reach any other point with a maximum traversal of 180degrees of arc. * <p> * Furthermore, for the Cylindrical family of projections, a line must be * < 180 degrees of arc in longitudinal extent. In other words, the * difference of longitudes between both vertices must be < 180 degrees. * Same as above: if you need a long line, you must break it into several * segments. * * @param ll1 LatLonPoint * @param ll2 LatLonPoint * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @return ArrayList int[] * @see LineType#Straight * @see LineType#Rhumb * @see LineType#GreatCircle * */ public ArrayList<float[]> forwardLine(LatLonPoint ll1, LatLonPoint ll2, int ltype, int nsegs) { double[] rawllpts = { ll1.getRadLat(), ll1.getRadLon(), ll2.getRadLat(), ll2.getRadLon() }; return forwardPoly(rawllpts, ltype, nsegs, false); } /** * Forward project a lat/lon Line. * * @see #forwardLine(LatLonPoint, LatLonPoint, int, int) */ public ArrayList<float[]> forwardLine(LatLonPoint ll1, LatLonPoint ll2, int ltype) { return forwardLine(ll1, ll2, ltype, -1); } /** * Forward project a rectangle defined by an upper left point and a lower * right point. * <p> * Returns a ArrayList of (x[], y[]) coordinate pairs of the projected * points. * <p> * Rects have the same restrictions as <a href="#poly_restrictions"> polys * </a> and <a href="#line_restrictions">lines </a>. * * @param ll1 LatLonPoint of northwest corner * @param ll2 LatLonPoint of southeast corner * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @see #forwardPoly * @return ArrayList int[] */ public ArrayList<float[]> forwardRect(LatLonPoint ll1, LatLonPoint ll2, int ltype, int nsegs, boolean isFilled) { double[] rawllpts = { ll1.getRadLat(), ll1.getRadLon(), ll1.getRadLat(), ll2.getRadLon(), ll2.getRadLat(), ll2.getRadLon(), ll2.getRadLat(), ll1.getRadLon(), // connect: ll1.getRadLat(), ll1.getRadLon() }; return forwardPoly(rawllpts, ltype, nsegs, isFilled); } /** * Forward project a lat/lon Rectangle. * * @see #forwardRect(LatLonPoint, LatLonPoint, int, int) */ public ArrayList<float[]> forwardRect(LatLonPoint ll1, LatLonPoint ll2, int ltype) { return forwardRect(ll1, ll2, ltype, -1, false); } /** * Forward project a lat/lon Rectangle. * * * @param ll1 LatLonPoint of northwest corner * @param ll2 LatLonPoint of southeast corner * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @see #forwardRect(LatLonPoint, LatLonPoint, int, int) */ public ArrayList<float[]> forwardRect(LatLonPoint ll1, LatLonPoint ll2, int ltype, int nsegs) { return forwardRect(ll1, ll2, ltype, nsegs, false); } /** * Forward project an arc. * * @param c LatLonPoint center * @param radians boolean radius in radians? * @param radius radius in radians or decimal degrees * @param start the starting angle of the arc, zero being North up. Units * are dependent on radians parameter - the start parameter is in * radians if radians equals true, decimal degrees if not. * @param extent the angular extent angle of the arc, zero being no length. * Units are dependent on radians parameter - the extent parameter is * in radians if radians equals true, decimal degrees if not. */ public ArrayList<float[]> forwardArc(LatLonPoint c, boolean radians, double radius, double start, double extent) { return forwardArc(c, radians, radius, -1, start, extent, java.awt.geom.Arc2D.OPEN); } public ArrayList<float[]> forwardArc(LatLonPoint c, boolean radians, double radius, int nverts, double start, double extent) { return forwardArc(c, radians, radius, nverts, start, extent, java.awt.geom.Arc2D.OPEN); } /** * Forward project a Lat/Lon Arc. * <p> * Arcs have the same restrictions as <a href="#poly_restrictions"> polys * </a>. * * @param c LatLonPoint center of circle * @param radians radius in radians or decimal degrees? * @param radius radius of circle (0 < radius < 180) * @param nverts number of vertices of the circle poly. * @param start the starting angle of the arc, zero being North up. Units * are dependent on radians parameter - the start parameter is in * radians if radians equals true, decimal degrees if not. * @param extent the angular extent angle of the arc, zero being no length. * Units are dependent on radians parameter - the extent parameter is * in radians if radians equals true, decimal degrees if not. * @param arcType type of arc to create - see java.awt.geom.Arc2D for (OPEN, * CHORD, PIE). Arc2D.OPEN means that the just the points for the * curved edge will be provided. Arc2D.PIE means that addition lines * from the edge of the curve to the center point will be added. * Arc2D.CHORD means a single line from each end of the curve will be * drawn. */ public ArrayList<float[]> forwardArc(LatLonPoint c, boolean radians, double radius, int nverts, double start, double extent, int arcType) { // HACK-need better decision for number of vertices. if (nverts < 3) nverts = NUM_DEFAULT_CIRCLE_VERTS; double[] rawllpts; switch (arcType) { case Arc2D.PIE: rawllpts = new double[(nverts << 1) + 4];// *2 for pairs +4 // connect break; case Arc2D.CHORD: rawllpts = new double[(nverts << 1) + 2];// *2 for pairs +2 // connect break; default: rawllpts = new double[(nverts << 1)];// *2 for pairs, no // connect } GreatCircle.earthCircle(c.getRadLat(), c.getRadLon(), (radians) ? radius : ProjMath.degToRad(radius), (radians) ? start : ProjMath.degToRad(start), (radians) ? extent : ProjMath.degToRad(extent), nverts, rawllpts); int linetype = LineType.Straight; boolean isFilled = false; switch (arcType) { case Arc2D.PIE: rawllpts[rawllpts.length - 4] = c.getRadLat(); rawllpts[rawllpts.length - 3] = c.getRadLon(); // Fall through... case Arc2D.CHORD: rawllpts[rawllpts.length - 2] = rawllpts[0]; rawllpts[rawllpts.length - 1] = rawllpts[1]; // Need to do this for the sides, not the arc part. linetype = LineType.GreatCircle; isFilled = true; break; default: // Don't need to do anything, defaults are already set. } // forward project the arc-poly. return forwardPoly(rawllpts, linetype, -1, isFilled); } /** * Forward project a circle. * * @param c LatLonPoint center * @param radians boolean radius in radians? * @param radius radius in radians or decimal degrees */ public ArrayList<float[]> forwardCircle(LatLonPoint c, boolean radians, double radius) { return forwardCircle(c, radians, radius, -1, false); } /** * Forward project a Lat/Lon Circle. * <p> * Circles have the same restrictions as <a href="#poly_restrictions"> * polys. </a>. * * @param c LatLonPoint center of circle * @param radians radius in radians or decimal degrees? * @param radius radius of circle (0 < radius < 180) * @param nverts number of vertices of the circle poly. */ public ArrayList<float[]> forwardCircle(LatLonPoint c, boolean radians, double radius, int nverts) { return forwardCircle(c, radians, radius, nverts, false); } /** * Forward project a Lat/Lon Circle. * <p> * Circles have the same restrictions as <a href="#poly_restrictions"> * polys. </a>. * * @param c LatLonPoint center of circle * @param radians radius in radians or decimal degrees? * @param radius radius of circle (0 < radius < 180) * @param nverts number of vertices of the circle poly. * @param isFilled filled poly? */ public ArrayList<float[]> forwardCircle(LatLonPoint c, boolean radians, double radius, int nverts, boolean isFilled) { // HACK-need better decision for number of vertices. if (nverts < 3) nverts = NUM_DEFAULT_CIRCLE_VERTS; double[] rawllpts = new double[(nverts << 1) + 2];// *2 for // pairs +2 // connect GreatCircle.earthCircle(c.getRadLat(), c.getRadLon(), (radians) ? radius : ProjMath.degToRad(radius), nverts, rawllpts); // connect the vertices. rawllpts[rawllpts.length - 2] = rawllpts[0]; rawllpts[rawllpts.length - 1] = rawllpts[1]; // forward project the circle-poly return forwardPoly(rawllpts, LineType.Straight, -1, isFilled); } // HACK protected transient static int XTHRESHOLD = 16384;// half range protected transient int XSCALE_THRESHOLD = 1000000;// dynamically /** * Forward project a LatLon Poly. * <p> * Returns a ArrayList of (x[], y[]) coordinate pair(s) of the projected * poly. * <a name="poly_restrictions"></a> * <b>RESTRICTIONS:</b> * All the following restrictions apply to LatLon polygons (either * filled or non-filled). Many of these restrictions apply to other * poly-like ArrayList graphics (Lines, Rectangles, Circles, Ellipses, ...). * See also <a href="#line_restrictions">restrictions on LatLon lines. </a> * <p> * <a name="antarctica_anomaly"> </a> For the cylindrical projections, (e.g. * Mercator), your polygons should not include or touch the poles. This is * because a polygon or polyline that includes a pole becomes a * non-continuous straight line on the map. "So what about Antarctica", you * say, "after all it's a polygon that is draped over the South Pole". Well, * if you want to see it in a cylindrical projection, you will need to * "augment" the vertices to turn it into a valid x-y polygon. You could do * this by removing the segment which crosses the dateline, and instead add * two extra edges down along both sides of the dateline to very near the * south pole and then connect these ends back the other way around the * world (not across the dateline) with a few extra line segments (remember * the <a href="#line_restrictions">line length restrictions </a>). This way * you've removed the polar anomaly from the data set. On the screen, all * you see is a sliver artifact down along the dateline. This is the very * method that our DCW data server shows Antarctica. * <p> * There is a fundamental ambiguity with filled polygons on a sphere: which * side do you draw the fill-color? The Cylindrical family will draw the * polygon as if it were in x-y space. For the Azimuthal projections, (e.g. * Orthographic), you can have polygons that cover the pole, but it's * important to specify the vertices in a clockwise order so that we can do * the correct clipping along the hemisphere edge. We traverse the vertices * assuming that the fill will be to the right hand side if the polygon * straddles the edge of the projection. (This default can be changed). * <p> * <h3>To Be (Mostly) Safe:</h3> * <ul> * <li>Polygons should not touch or encompass the poles unless you will be * viewing them with azimuthal projections, such as Orthographic. <br> * <li>Polygons should not encompass more area than one hemisphere. <br> * <li>Polygon vertices should be specified in "clockwise", fill-on-right * order to ensure proper filling. <br> * <li>Polygon edges are also restricted by the <a * href="#line_restrictions">restrictions on LatLon lines </a>. * </ul> * <p> * <h3>Optimization Notes:</h3> * The projection library deals internally in radians, and so you're * required to pass in an array of radian points. See <a * href="com.bbn.openmap.proj.ProjMath.html#arrayDegToRad"> * ProjMath.arrayDegToRad(float[]) </a> for an efficient in-place * conversion. * <p> * For no-frills, no-assumptions, fast and efficient projecting, see <a * href="#forwardRaw">forwardRaw() </a>. * * @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS! * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @param isFilled poly is filled? or not * @return ArrayList of x[], y[], x[], y[], ... projected poly * @see #forwardRaw * @see LineType#Straight * @see LineType#Rhumb * @see LineType#GreatCircle */ public ArrayList<float[]> forwardPoly(float[] rawllpts, int ltype, int nsegs, boolean isFilled) { ArrayList<float[]> stuff = _forwardPoly(rawllpts, ltype, nsegs, isFilled); // @HACK: workaround XWindows bug. simple clip to a boundary. // this is ugly. if (Environment.doingXWindowsWorkaround && (scale <= XSCALE_THRESHOLD)) { int i, j, size = stuff.size(); float[] xpts, ypts; for (i = 0; i < size; i += 2) { xpts = (float[]) stuff.get(i); ypts = (float[]) stuff.get(i + 1); for (j = 0; j < xpts.length; j++) { if (xpts[j] <= -XTHRESHOLD) { xpts[j] = -XTHRESHOLD; } else if (xpts[j] >= XTHRESHOLD) { xpts[j] = XTHRESHOLD; } if (ypts[j] <= -XTHRESHOLD) { ypts[j] = -XTHRESHOLD; } else if (ypts[j] >= XTHRESHOLD) { ypts[j] = XTHRESHOLD; } } stuff.set(i, xpts); stuff.set(i + 1, ypts); } } return stuff; } /** * Forward project a lat/lon Poly. * <p> * Delegates to _forwardPoly(), and may do additional clipping for Java * XWindows problem. Remember to specify vertices in radians! * * @param rawllpts double[] of lat,lon,lat,lon,... in RADIANS! * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @param isFilled filled poly? * @return ArrayList of x[], y[], x[], y[], ... projected poly * @see #forwardRaw * @see LineType#Straight * @see LineType#Rhumb * @see LineType#GreatCircle */ public ArrayList<float[]> forwardPoly(double[] rawllpts, int ltype, int nsegs, boolean isFilled) { ArrayList<float[]> stuff = _forwardPoly(rawllpts, ltype, nsegs, isFilled); // @HACK: workaround XWindows bug. simple clip to a boundary. // this is ugly. if (Environment.doingXWindowsWorkaround && (scale <= XSCALE_THRESHOLD)) { int i, j, size = stuff.size(); float[] xpts, ypts; for (i = 0; i < size; i += 2) { xpts = stuff.get(i); ypts = stuff.get(i + 1); for (j = 0; j < xpts.length; j++) { if (xpts[j] <= -XTHRESHOLD) { xpts[j] = -XTHRESHOLD; } else if (xpts[j] >= XTHRESHOLD) { xpts[j] = XTHRESHOLD; } if (ypts[j] <= -XTHRESHOLD) { ypts[j] = -XTHRESHOLD; } else if (ypts[j] >= XTHRESHOLD) { ypts[j] = XTHRESHOLD; } } stuff.set(i, xpts); stuff.set(i + 1, ypts); } } return stuff; } /** * Forward project a lat/lon Poly defined as decimal degree lat/lons. * <p> * Delegates to _forwardPoly(), and may do additional clipping for Java * XWindows problem. Remember to specify vertices in decimal degrees. If you * have radians, use them and call forwardPoly, it's faster. This method * will convert the coords to radians before calling the fowardPoly method. * * @param llpts double[] of lat,lon,lat,lon,... in decimal degree lat/lon! * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @param isFilled filled poly? * @return ArrayList of x[], y[], x[], y[], ... projected poly * @see #forwardRaw * @see LineType#Straight * @see LineType#Rhumb * @see LineType#GreatCircle */ public ArrayList<float[]> forwardLLPoly(double[] llpts, int ltype, int nsegs, boolean isFilled) { double[] rawllpts = new double[llpts.length]; System.arraycopy(llpts, 0, rawllpts, 0, llpts.length); ProjMath.arrayDegToRad(rawllpts); return forwardPoly(rawllpts, ltype, nsegs, isFilled); } /** * Forward project a lat/lon Poly. Remember to specify vertices in radians! * * @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS! * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @param isFilled filled poly? * @return ArrayList of x[], y[], x[], y[], ... projected poly */ protected abstract ArrayList<float[]> _forwardPoly(float[] rawllpts, int ltype, int nsegs, boolean isFilled); /** * Forward project a lat/lon Poly. Remember to specify vertices in radians! * * @param rawllpts double[] of lat,lon,lat,lon,... in RADIANS! * @param ltype line type (straight, rhumbline, greatcircle) * @param nsegs number of segment points (only for greatcircle or rhumbline * line types, and if < 1, this value is generated internally) * @param isFilled filled poly? * @return ArrayList of x[], y[], x[], y[], ... projected poly */ protected abstract ArrayList<float[]> _forwardPoly(double[] rawllpts, int ltype, int nsegs, boolean isFilled); /** * Get the unprojected coordinates units of measure. * * @return Length.DECIMAL_DEGREE */ public Length getUcuom() { return Length.DECIMAL_DEGREE; } /** * Can't set the unprojected coordinates units of measure for a GeoProj, * it's always Length.DECIMAL_DEGREE. * * @param ucuom */ public void setUcuom(Length ucuom) { // no-op } /** * Convenience method to create a GCT for this projection. For projections * that start with lat/lon coordinates, this will return a LatLonGCT. For * projections that have world coordinates in meters, the GCT will provide a * way to get to those meter coordinates. For instance, a UTMProjection will * return a UTMGCT. * * @return GeoCoordTransformation for this projection */ @SuppressWarnings("unchecked") public <T extends GeoCoordTransformation> T getGCTForProjection() { return (T) new LatLonGCT(); } /** * @return the reference longitude of the projection. For most projections, * it'll just be the center point longitude. For LLC, it'll be the * reference meridian. */ public double getReferenceLon() { return getCenter().getX(); } }