// **********************************************************************
//
// <copyright>
//
// BBN Technologies
// 10 Moulton Street
// Cambridge, MA 02138
// (617) 873-8000
//
// Copyright (C) BBNT Solutions LLC. All rights reserved.
//
// </copyright>
// **********************************************************************
//
// $Source: /cvs/distapps/openmap/src/openmap/com/bbn/openmap/proj/Cylindrical.java,v $
// $RCSfile: Cylindrical.java,v $
// $Revision: 1.10 $
// $Date: 2009/01/21 01:24:41 $
// $Author: dietrick $
//
// **********************************************************************
package com.bbn.openmap.proj;
import java.awt.Point;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import com.bbn.openmap.proj.coords.LatLonPoint;
import com.bbn.openmap.util.Debug;
/**
* Base of all cylindrical projections.
* <p>
*
* @see Projection
* @see Proj
* @see Mercator
* @see CADRG
*
*/
public abstract class Cylindrical
extends GeoProj {
// used for calculating wrapping of ArrayList graphics
protected transient Point world; // world width in pixels.
protected transient int half_world; // world.x / 2
/**
* Construct a cylindrical projection.
* <p>
*
* @param center LatLonPoint center of projection
* @param scale float scale of projection
* @param width width of screen
* @param height height of screen
*/
public Cylindrical(LatLonPoint center, float scale, int width, int height) {
super(center, scale, width, height);
}
/**
* Return string-ified description of this projection.
* <p>
*
* @return String
* @see Projection#getProjectionID
*/
public String toString() {
return " world(" + world.x + "," + world.y + ")" + super.toString();
}
protected void init() {
super.init();
// minscale is the minimum scale allowable (before integer
// wrapping
// can occur)
minscale = Math.ceil(planetPixelCircumference / (int) Integer.MAX_VALUE);
if (minscale < 1) {
minscale = 1;
}
}
/**
* Called when some fundamental parameters change.
* <p>
* Each projection will decide how to respond to this change. For instance,
* they may need to recalculate "constant" parameters used in the forward()
* and inverse() calls.
* <p>
*/
protected void computeParameters() {
if (scale < minscale)
scale = minscale;
// maxscale = scale at which world circumference fits in
// window
maxscale = Math.floor(planetPixelCircumference / width);
if (maxscale < minscale) {
maxscale = minscale;
}
if (scale > maxscale) {
scale = maxscale;
}
scaled_radius = planetPixelRadius / scale;
if (world == null)
world = new Point(0, 0);
// width of the world in pixels at current scale
world.x = (int) (planetPixelCircumference / scale);
half_world = world.x / 2;
// calculate cutoff scale for XWindows workaround
XSCALE_THRESHOLD = (int) (planetPixelCircumference / 64000);// fudge
// it a
// little
// bit
if (Debug.debugging("proj")) {
Debug.output("Cylindrical.computeParameters(): " + "world.x = " + world.x + " half_world = " + half_world
+ " XSCALE_THRESHOLD = " + XSCALE_THRESHOLD);
}
}
/**
* Get the upper left (northwest) point of the projection.
* <p>
* Returns the upper left point (or closest equivalent) of the projection
* based on the center point and height and width of screen.
* <p>
*
* @return LatLonPoint
*
*/
public LatLonPoint getUpperLeft() {
return inverse(0, 0, new LatLonPoint.Double());
}
/**
* Get the lower right (southeast) point of the projection.
* <p>
* Returns the lower right point (or closest equivalent) of the projection
* based on the center point and height and width of screen.
* <p>
*
* @return LatLonPoint
*
*/
public LatLonPoint getLowerRight() {
return inverse(width - 1, height - 1, new LatLonPoint.Double());
}
/**
* Forward project a raw array of radian points. This assumes nothing about
* the array of coordinates. In no way does it assume the points are
* connected or that the composite figure is to be filled.
* <p>
* It does populate a visible array indicating whether the points are
* visible on the projected view of the world.
* <p>
*
* @param rawllpts array of lat,lon,... in radians
* @param rawoff offset into rawllpts
* @param xcoords x coordinates
* @param ycoords y coordinates
* @param visible coordinates visible?
* @param copyoff offset into x,y,visible arrays
* @param copylen number of coordinates (coordinate arrays should be at
* least this long, rawllpts should be at least twice as long).
* @return boolean true if all points visible, false if some points not
* visible.
*/
public boolean forwardRaw(float[] rawllpts, int rawoff, float[] xcoords, float[] ycoords, boolean[] visible, int copyoff,
int copylen) {
Point2D temp = new Point2D.Float();
int end = copylen + copyoff;
for (int i = copyoff, j = rawoff; i < end; i++, j += 2) {
forward(rawllpts[j], rawllpts[j + 1], temp, true);
xcoords[i] = (float) temp.getX();
ycoords[i] = (float) temp.getY();
visible[i] = true;// should always be visible in
// cylindrical family
}
// everything is visible
return true;
}
/**
* Forward project a raw array of radian points. This assumes nothing about
* the array of coordinates. In no way does it assume the points are
* connected or that the composite figure is to be filled.
* <p>
* It does populate a visible array indicating whether the points are
* visible on the projected view of the world.
* <p>
*
* @param rawllpts array of lat,lon,... in radians
* @param rawoff offset into rawllpts
* @param xcoords x coordinates
* @param ycoords y coordinates
* @param visible coordinates visible?
* @param copyoff offset into x,y,visible arrays
* @param copylen number of coordinates (coordinate arrays should be at
* least this long, rawllpts should be at least twice as long).
* @return boolean true if all points visible, false if some points not
* visible.
*/
public boolean forwardRaw(double[] rawllpts, int rawoff, float[] xcoords, float[] ycoords, boolean[] visible, int copyoff,
int copylen) {
Point2D temp = new Point2D.Float();
int end = copylen + copyoff;
for (int i = copyoff, j = rawoff; i < end; i++, j += 2) {
forward(rawllpts[j], rawllpts[j + 1], temp, true);
xcoords[i] = (float) temp.getX();
ycoords[i] = (float) temp.getY();
visible[i] = true;// should always be visible in
// cylindrical family
}
// everything is visible
return true;
}
/**
* Forward project a raw float[] Poly.
* <p>
* <strong>Implementation: </strong> <br>
* For the cylindrical "boxy" family of projections, we project all the
* points, and check the horizontal (longitudinal) spacing between vertices
* as we go. If the spacing is greater than half the world width
* (circumference) in pixels, we assume that the segment has wrapped off one
* edge of the screen and back onto the other side. (NOTE that our
* restrictions on line segments mentioned in the Projection interface do
* not allow for lines >= 180 degrees of arc or for the difference in
* longitude between two points to be >= 180 degrees of arc).
* <p>
* For the case where a segment wraps offscreen, we keep track of the
* wrapping adjustment factor, and shift the points as we go. After
* projecting and shifting all the points, we have a single continuous x-y
* polygon. We then need to make shifted copies of this polygon for the
* maxima and minima wrap values calculated during the projection process.
* This allows us to see the discontinuous (wrapped) sections on the screen
* when they are drawn.
* <p>
*
* @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS!
* @param ltype line type (straight, rhumbline, greatcircle)
* @param nsegs number of segments between vertices (or if < 0, generate
* this value internally)
* @param isFilled filled poly? this is currently ignored for cylindrical
* projections.
* @return ArrayList of x[], y[], x[], y[], ... the projected poly
*/
protected ArrayList<float[]> _forwardPoly(float[] rawllpts, int ltype, int nsegs, boolean isFilled) {
int n, k, flag = 0, min = 0, max = 0;
float xp, xadj = 0;
// determine length of pairs list
int len = rawllpts.length >> 1; // len/2, chop off extra
if (len < 2)
return new ArrayList<float[]>(0);
// handle complicated line in specific routines
if (isComplicatedLineType(ltype)) {
return doPolyDispatch(rawllpts, ltype, nsegs, isFilled);
}
// determine when to stop
Point2D temp = new Point2D.Float(0, 0);
float[] xs = new float[len];
float[] ys = new float[len];
// forward project the first point
forward(rawllpts[0], rawllpts[1], temp, true);
xp = (float) temp.getX();
xs[0] = (float) temp.getX();
ys[0] = (float) temp.getY();
// forward project the other points
for (n = 1, k = 2; n < len; n++, k += 2) {
forward(rawllpts[k], rawllpts[k + 1], temp, true);
xs[n] = (float) temp.getX();
ys[n] = (float) temp.getY();
// segment crosses longitude along screen edge
if (Math.abs(xp - xs[n]) >= half_world) {
flag += (xp < xs[n]) ? -1 : 1;// inc/dec the wrap
// count
min = (flag < min) ? flag : min;// left wrap count
max = (flag > max) ? flag : max;// right wrap count
xadj = flag * world.x;// adjustment to x coordinates
// Debug.output("flag=" + flag + " xadj=" + xadj);
}
xp = (float) temp.getX();// save previous unshifted x coordinate
if (flag != 0) {
xs[n] += xadj;// adjust x coordinates
}
}
min *= -1;// positive magnitude
// now create the return list
ArrayList<float[]> ret_val = null;
ret_val = new ArrayList<float[]>(2 + 2 * (max + min));
ret_val.add(xs);
ret_val.add(ys);
float[] altx = null;
/*
* if (Debug.debugging("proj")) { dumpPoly(rawllpts, xs, ys); }
*/
// add the extra left-wrap polys
for (int i = 1; i <= min; i++) {
altx = new float[xs.length];
xadj = i * world.x;// shift opposite
for (int j = 0; j < altx.length; j++) {
altx[j] = xs[j] + xadj;
}
ret_val.add(altx);
ret_val.add(ys);
/*
* if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); }
*/
}
// add the extra right-wrap polys
for (int i = 1; i <= max; i++) {
altx = new float[xs.length];
xadj = -i * world.x;// shift opposite
for (int j = 0; j < altx.length; j++) {
altx[j] = xs[j] + xadj;
}
ret_val.add(altx);
ret_val.add(ys);
/*
* if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); }
*/
}
return ret_val;
}// _forwardPoly()
/**
* Forward project a raw float[] Poly.
* <p>
* <strong>Implementation: </strong> <br>
* For the cylindrical "boxy" family of projections, we project all the
* points, and check the horizontal (longitudinal) spacing between vertices
* as we go. If the spacing is greater than half the world width
* (circumference) in pixels, we assume that the segment has wrapped off one
* edge of the screen and back onto the other side. (NOTE that our
* restrictions on line segments mentioned in the Projection interface do
* not allow for lines >= 180 degrees of arc or for the difference in
* longitude between two points to be >= 180 degrees of arc).
* <p>
* For the case where a segment wraps offscreen, we keep track of the
* wrapping adjustment factor, and shift the points as we go. After
* projecting and shifting all the points, we have a single continuous x-y
* polygon. We then need to make shifted copies of this polygon for the
* maxima and minima wrap values calculated during the projection process.
* This allows us to see the discontinuous (wrapped) sections on the screen
* when they are drawn.
* <p>
*
* @param rawllpts float[] of lat,lon,lat,lon,... in RADIANS!
* @param ltype line type (straight, rhumbline, greatcircle)
* @param nsegs number of segments between vertices (or if < 0, generate
* this value internally)
* @param isFilled filled poly? this is currently ignored for cylindrical
* projections.
* @return ArrayList of x[], y[], x[], y[], ... the projected poly
*/
protected ArrayList<float[]> _forwardPoly(double[] rawllpts, int ltype, int nsegs, boolean isFilled) {
int n, k, flag = 0, min = 0, max = 0;
float xp, xadj = 0;
// determine length of pairs list
int len = rawllpts.length >> 1; // len/2, chop off extra
if (len < 2)
return new ArrayList<float[]>(0);
// handle complicated line in specific routines
if (isComplicatedLineType(ltype)) {
return doPolyDispatch(rawllpts, ltype, nsegs, isFilled);
}
// determine when to stop
Point2D temp = new Point2D.Float(0, 0);
float[] xs = new float[len];
float[] ys = new float[len];
// forward project the first point
forward(rawllpts[0], rawllpts[1], temp, true);
xp = (float) temp.getX();
xs[0] = (float) temp.getX();
ys[0] = (float) temp.getY();
// forward project the other points
for (n = 1, k = 2; n < len; n++, k += 2) {
forward(rawllpts[k], rawllpts[k + 1], temp, true);
xs[n] = (float) temp.getX();
ys[n] = (float) temp.getY();
// segment crosses longitude along screen edge
if (Math.abs(xp - xs[n]) >= half_world) {
flag += (xp < xs[n]) ? -1 : 1;// inc/dec the wrap
// count
min = (flag < min) ? flag : min;// left wrap count
max = (flag > max) ? flag : max;// right wrap count
xadj = flag * world.x;// adjustment to x coordinates
// Debug.output("flag=" + flag + " xadj=" + xadj);
}
xp = (float) temp.getX();// save previous unshifted x coordinate
if (flag != 0) {
xs[n] += xadj;// adjust x coordinates
}
}
min *= -1;// positive magnitude
// now create the return list
ArrayList<float[]> ret_val = null;
ret_val = new ArrayList<float[]>(2 + 2 * (max + min));
ret_val.add(xs);
ret_val.add(ys);
float[] altx = null;
/*
* if (Debug.debugging("proj")) { dumpPoly(rawllpts, xs, ys); }
*/
// add the extra left-wrap polys
for (int i = 1; i <= min; i++) {
altx = new float[xs.length];
xadj = i * world.x;// shift opposite
for (int j = 0; j < altx.length; j++) {
altx[j] = xs[j] + xadj;
}
ret_val.add(altx);
ret_val.add(ys);
/*
* if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); }
*/
}
// add the extra right-wrap polys
for (int i = 1; i <= max; i++) {
altx = new float[xs.length];
xadj = -i * world.x;// shift opposite
for (int j = 0; j < altx.length; j++) {
altx[j] = xs[j] + xadj;
}
ret_val.add(altx);
ret_val.add(ys);
/*
* if (Debug.debugging("proj")) { dumpPoly(rawllpts, altx, ys); }
*/
}
return ret_val;
}// _forwardPoly()
// print out polygon
public static final void dumpPoly(float[] rawllpts, float[] xs, float[] ys) {
Debug.output("poly:");
for (int i = 0, j = 0; j < xs.length; i += 2, j++) {
System.out.print("[" + ProjMath.radToDeg(rawllpts[i]) + "," + ProjMath.radToDeg(rawllpts[i + 1]) + "]=");
Debug.output("(" + xs[j] + "," + ys[j] + ")");
}
Debug.output("");
}
// print out polygon
public static final void dumpPoly(double[] rawllpts, float[] xs, float[] ys) {
Debug.output("poly:");
for (int i = 0, j = 0; j < xs.length; i += 2, j++) {
System.out.print("[" + ProjMath.radToDeg(rawllpts[i]) + "," + ProjMath.radToDeg(rawllpts[i + 1]) + "]=");
Debug.output("(" + xs[j] + "," + ys[j] + ")");
}
Debug.output("");
}
/**
* Get the name string of the projection.
*/
public String getName() {
return "Cylindrical";
}
}