gov.nasa.worldwind.util.GeographicImageInterpolator Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) 2012 United States Government as represented by the Administrator of the
* National Aeronautics and Space Administration.
* All Rights Reserved.
*/
package gov.nasa.worldwind.util;
import gov.nasa.worldwind.geom.*;
import java.awt.*;
/**
* GeographicImageInterpolator extends the functionality of {@link gov.nasa.worldwind.util.ImageInterpolator} to
* correctly map from geographic coordinates to image coordinates. Unlike its superclass, which works in Cartesian
* coordinates, GeographicImageInterpolator handles the singularities of geographic coordinates. For example,
* GeographicImageInterpolator can map images which cross the international dateline.
*
* @author dcollins
* @version $Id: GeographicImageInterpolator.java 1171 2013-02-11 21:45:02Z dcollins $
*/
public class GeographicImageInterpolator extends ImageInterpolator
{
/**
* GeographicCell extends {@link gov.nasa.worldwind.util.ImageInterpolator.Cell} to correctly handle image cells
* which have geographic coordinates. Unlike its superclass, which works in Cartesian coordinates, GeographicCell
* handles the singularities of geographic coordinates, such as image cells which cross the international dateline.
*/
protected static class GeographicCell extends Cell
{
/** Denotes if the pixels in this geographic image cell crosses the international dateline. */
protected boolean crossesDateline;
/**
* Constructs a new Geographic Cell, but otherwise does nothing.
*
* @param m0 the cell's left image coordinate.
* @param m1 the cell's right image coordinate.
* @param n0 the cell's bottom image coordinate.
* @param n1 the cell's top image coordinate.
*/
public GeographicCell(int m0, int m1, int n0, int n1)
{
super(m0, m1, n0, n1);
}
/**
* Overridden to create a {@link gov.nasa.worldwind.util.GeographicImageInterpolator.GeographicCell}.
*
* @param m0 the cell's left image coordinate.
* @param m1 the cell's right image coordinate.
* @param n0 the cell's bottom image coordinate.
* @param n1 the cell's top image coordinate.
*
* @return a new GeographicCell with the specified image coordinates.
*/
@Override
protected Cell makeChildCell(int m0, int m1, int n0, int n1)
{
return new GeographicCell(m0, m1, n0, n1);
}
/**
* Returns true if this image cell crosses the international dateline, and false otherwise.
*
* @return true if this cell crosses the international dateline; false otherwise.
*/
public boolean isCrossesDateline()
{
return this.crossesDateline;
}
/**
* Overridden to correctly compute intersection for cells which cross the international dateline. If the
* specified cell does not cross the dateline, this invokes the superclass' functionality.
*
* @param x the x-component to test for intersection with this cell.
* @param y the y-component to test for intersection with this cell.
*
* @return true if the (x, y) point intersects this cell; false otherwise.
*/
@Override
public boolean intersects(float x, float y)
{
// Invoke the superclass functionality if this cell doesn't cross the international dateline.
if (!this.isCrossesDateline())
{
return super.intersects(x, y);
}
// The cell crosses the international dateline. The cell's minx and maxx define its extremes to either side
// of the dateline. minx is the extreme value in the eastern hemisphere, and maxx is the extreme value in
// the western hemisphere.
return ((x >= this.minx && x <= 180f) || (x >= -180f && x <= this.maxx))
&& y >= this.miny && y <= this.maxy;
}
/**
* Overridden to correctly compute the extremes for leaf cells which cross the international dateline. If this
* cell does not cross the dateline, this invokes the superclass' functionality.
*
* @param dim the image's dimensions.
* @param xs the x-coordinates of the image's pixels. Must contain at least gridSize.width *
* gridSize.height
elements.
* @param ys the y-coordinates of the image's pixels. Must contain at least gridSize.width *
* gridSize.height
elements.
*/
@Override
protected void computeExtremesFromLocations(Dimension dim, float[] xs, float[] ys)
{
// Invoke the superclass functionality if this cell doesn't cross the international dateline.
if (!this.longitudesCrossDateline(dim, xs))
{
super.computeExtremesFromLocations(dim, xs, ys);
return;
}
// The cell crosses the international dateline. The cell's minx and maxx define its extremes to either side
// of the dateline. minx is the extreme value in the eastern hemisphere, and maxx is the extreme value in
// the western hemisphere. Therefore minx and maxx are initialized with values nearest to the dateline.
this.minx = 180f;
this.maxx = -180f;
this.miny = Float.MAX_VALUE;
this.maxy = -Float.MAX_VALUE;
this.crossesDateline = true;
// Assume that dateline crossing cells span the shorter of two possible paths around the globe. Therefore
// each location contributes to the extreme in its hemisphere. minx is the furthest value from the dateline
// in the eastern hemisphere. maxx is the furthest value from the dateline in the western hemisphere.
for (int j = this.n0; j <= this.n1; j++)
{
for (int i = this.m0; i <= this.m1; i++)
{
int k = j * dim.width + i;
float x = xs[k];
float y = ys[k];
if (this.minx > x && x > 0f)
this.minx = x;
if (this.maxx < x && x < 0f)
this.maxx = x;
if (this.miny > y)
this.miny = y;
if (this.maxy < y)
this.maxy = y;
}
}
}
/**
* Overridden to correctly compute the extremes for parent cells who's children cross the international
* dateline. If the this cell does not cross the dateline, this invokes the superclass' functionality.
*/
@Override
protected void computeExtremesFromChildren()
{
// Invoke the superclass functionality if this cell doesn't cross the international dateline.
if (!this.childrenCrossDateline())
{
super.computeExtremesFromChildren();
return;
}
// The cell crosses the international dateline. The cell's minx and maxx define its extremes to either side
// of the dateline. minx is the extreme value in the eastern hemisphere, and maxx is the extreme value in
// the western hemisphere. Therefore minx and maxx are initialized with values nearest to the dateline.
this.minx = 180f;
this.maxx = -180f;
this.miny = Float.MAX_VALUE;
this.maxy = -Float.MAX_VALUE;
this.crossesDateline = true;
// Assume that dateline crossing cells span the shorter of two possible paths around the globe. Therefore
// each location contributes to the extreme in its hemisphere. minx is the furthest value from the dateline
// in the eastern hemisphere. maxx is the furthest value from the dateline in the western hemisphere.
for (Cell t : this.children)
{
// The child cell crosses the dateline. This cell's minx and maxx have the same meaning as the child
// cell, so a simple comparison determines this cell's extreme x values.
if (((GeographicCell) t).isCrossesDateline())
{
if (this.minx > t.minx)
this.minx = t.minx;
if (this.maxx < t.maxx)
this.maxx = t.maxx;
}
// The child cell doesn't cross the dateline. This cell's minx and maxx have different meaning than the
// child cell. If the child cell is entirely contained within either the eastern or western hemisphere,
// we adjust this cell's minx or maxx to include it. If the child cell spans the prime meridian, this
// cell's minx and maxx must extent to the prime meridian to include it.
else
{
if (this.minx > t.minx && t.minx > 0f) // Cell is entirely within the eastern hemisphere.
this.minx = t.minx;
if (this.maxx < t.maxx && t.maxx < 0f) // Cell is entirely within the western hemisphere.
this.maxx = t.maxx;
if (t.minx <= 0f && t.maxx >= 0f) // Cell is in both the western and eastern hemispheres.
this.minx = this.maxx = 0f;
}
if (this.miny > t.miny)
this.miny = t.miny;
if (this.maxy < t.maxy)
this.maxy = t.maxy;
}
}
/**
* Returns true if a line segment from the first pixel in this cell to any other pixel in this cell crosses the
* international dateline, and false otherwise.
*
* @param dim the image's dimensions.
* @param longitudes the longitude coordinates of the image's pixels in degrees. Must contain at least
* dim.width * dim.height
elements.
*
* @return true if this image cell's crosses the international dateline; false otherwise.
*/
protected boolean longitudesCrossDateline(Dimension dim, float[] longitudes)
{
Float x1 = null;
for (int j = this.n0; j <= this.n1; j++)
{
for (int i = this.m0; i <= this.m1; i++)
{
int k = j * dim.width + i;
float x2 = longitudes[k];
if (x1 != null)
{
// A segment cross the line if end pos have different longitude signs
// and are more than 180 degress longitude apart
if (Math.signum(x1) != Math.signum(x2))
{
float delta = Math.abs(x1 - x2);
if (delta > 180f && delta < 360f)
return true;
}
}
x1 = x2;
}
}
return false;
}
/**
* Returns true if any of this image cell's children cross the international dateline, and false otherwise.
* Returns false if this image cell has no children.
*
* @return true if any children cross the international dateline; false otherwise.
*/
protected boolean childrenCrossDateline()
{
if (this.children == null || this.children.length == 0)
return false;
for (Cell t : this.children)
{
if (((GeographicCell) t).isCrossesDateline())
return true;
}
return false;
}
}
/**
* Creates a new GeographicImageInterpolator, initializing this interpolator's internal image cell tree with root
* cell dimensions of (gridSize.width * gridSize.height)
and with the specified depth
.
*
* @param gridSize the image's dimensions.
* @param xs the x-coordinates of the image's pixels. Must contain at least gridSize.width *
* gridSize.height
elements.
* @param ys the y-coordinates of the image's pixels. Must contain at least gridSize.width *
* gridSize.height
elements.
* @param depth the initial depth of this interpolator's image cell tree.
* @param cellSize the size of a leaf cell in this interpolator's image cell tree, in pixels.
*
* @throws IllegalStateException if any of the the gridSize, x-coordinates, or y-coordinates are null, if either the
* x-coordinates or y-coordinates contain less than gridSize.width *
* gridSize.height
elements, if the depth is less than zero, or if the cell
* size is less than one.
*/
public GeographicImageInterpolator(Dimension gridSize, float[] xs, float[] ys, int depth, int cellSize)
{
super(gridSize, xs, ys, depth, cellSize);
}
/**
* Overridden to create a {@link gov.nasa.worldwind.util.GeographicImageInterpolator.GeographicCell}.
*
* @param m0 the root cell's left image coordinate.
* @param m1 the root cell's right image coordinate.
* @param n0 the root cell's bottom image coordinate.
* @param n1 the root cell's top image coordinate.
*
* @return a new GeographicCell with the specified image coordinates.
*/
@Override
protected Cell makeRootCell(int m0, int m1, int n0, int n1)
{
return new GeographicCell(m0, m1, n0, n1);
}
/**
* Returns the sector containing the image's geographic coordinates. This returns a sector which spans the longitude
* range [-180, 180] if the image crosses the international dateline.
*
* @return the image's bounding sector.
*/
public Sector getSector()
{
return ((GeographicCell) this.root).isCrossesDateline() ?
Sector.fromDegrees(this.root.miny, this.root.maxy, -180, 180) :
Sector.fromDegrees(this.root.miny, this.root.maxy, this.root.minx, this.root.maxx);
}
/**
* Overridden to correctly compute bilinear interpolation coordinates for image cells which cross the international
* dateline. If the specified cell does not cross the dateline, this invokes the superclass' functionality. This
* returns null if the specified (x, y) point does not intersect the cell.
*
* @param x the x-component of the point to compute bilinear coordinate for.
* @param y the y-component of the point to compute bilinear coordinate for.
* @param cell the cell to compute bilinear coordinates for.
*
* @return the bilinear coordinates of the specified (x, y) point in the specified cell, or null if the point does
* not intersect the cell.
*/
@Override
protected double[] computeBilinearCoordinates(float x, float y, Cell cell)
{
// Invoke the superclass functionality if the cell doesn't cross the international dateline.
if (!((GeographicCell) cell).isCrossesDateline())
{
return super.computeBilinearCoordinates(x, y, cell);
}
int[] indices = this.getFieldIndices(cell);
Vec4[] points = new Vec4[4];
// The cell crosses the international dateline. Adjust the cell's coordinates so the're in the same hemisphere
// as the x-coordinate. This will result in longitude values outside of the range [-180, 180], but preserves
// the size and shape relative to the (x, y) point.
for (int i = 0; i < 4; i++)
{
double lon = this.xs[indices[i]];
double lat = this.ys[indices[i]];
if (x < 0f && lon >= 0f)
lon -= 360f;
else if (x >= 0f && lon < 0f)
lon += 360f;
points[i] = new Vec4(lon, lat);
}
// Use the adjusted cell coordinates to compute the (x, y) point's bilinear coordinates in the cell. The
// adjusted coordinates contain nonstandard longitudes, but produce the correct result here because the
// coordinates are interpreted as Cartesian.
return BarycentricQuadrilateral.invertBilinear(
new Vec4(x, y),
points[0],
points[1],
points[2],
points[3]);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy