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