/*
* @(#)MapGrid.java
*
*
* This software was developed by the Thermal Modeling and Analysis
* Project(TMAP) of the National Oceanographic and Atmospheric
* Administration's (NOAA) Pacific Marine Environmental Lab(PMEL),
* hereafter referred to as NOAA/PMEL/TMAP.
*
* Access and use of this software shall impose the following
* obligations and understandings on the user. The user is granted the
* right, without any fee or cost, to use, copy, modify, alter, enhance
* and distribute this software, and any derivative works thereof, and
* its supporting documentation for any purpose whatsoever, provided
* that this entire notice appears in all copies of the software,
* derivative works and supporting documentation. Further, the user
* agrees to credit NOAA/PMEL/TMAP in any publications that result from
* the use of this software or in any product that includes this
* software. The names TMAP, NOAA and/or PMEL, however, may not be used
* in any advertising or publicity to endorse or promote any products
* or commercial entity unless specific written permission is obtained
* from NOAA/PMEL/TMAP. The user also understands that NOAA/PMEL/TMAP
* is not obligated to provide the user with any support, consulting,
* training or assistance of any kind with regard to the use, operation
* and performance of this software nor to provide the user with any
* updates, revisions, new versions or "bug fixes".
*
* THIS SOFTWARE IS PROVIDED BY NOAA/PMEL/TMAP "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL NOAA/PMEL/TMAP BE LIABLE FOR ANY SPECIAL,
* INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
* RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
* CONTRACT, NEGLIGENCE OR OTHER TORTUOUS ACTION, ARISING OUT OF OR IN
* CONNECTION WITH THE ACCESS, USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
package dods.clients.importwizard.TMAP.map;
import java.awt.Color;
import java.awt.Rectangle;
//import dods.clients.importwizard.TMAP.map.MapConstants;
/**
* A grid for use with the MapCanvas.
* <p>
* A MapGrid contains all the information for the conversion of pixels
* to user values and vice versa. Associating a grid with a map also
* allows MapTools to work in snap-to-grid mode.
*
* version 2.3, 23 Jun 1997
* @version 3.0, 16 Nov 1999
* @author Jonathan Callahan (NOAA/OAR/PMEL/TMAP)
*/
public class MapGrid extends Object implements MapConstants {
/**
* The data domain along X [in user coordinates].
*/
public double [] domain_X = new double[2];
/**
* The data domain along Y [in user coordinates].
*/
public double [] domain_Y = new double[2];
/**
* "user" value of the leftmost pixel in the base image.
*/
public double x_start=0.0;
/**
* "user" value of the topmost pixel in the base image.
*/
public double y_start=0.0;
/**
* Full "user" domain of x in the base image.
*/
public double x_factor=1.0;
/**
* Full "user" domain of y in the base image.
*/
public double y_factor=1.0;
/**
* The grid spacing (in "user" coordinates) in the x dimension.
*/
public double delta_X=1.0;
/**
* The grid spacing (in "user" coordinates) in the y dimension.
*/
public double delta_Y=1.0;
/**
* The X axis type.
* @see MapConstants
*/
public int x_type=LONGITUDE_AXIS;
/**
* The Y axis type.
* @see MapConstants
*/
public int y_type=LATITUDE_AXIS;
/**
* Flag determining whether the X axis is modulo or not (eg. longitude).
*/
public boolean modulo_X = true;
/**
* The rectangle (in pixels) of the base image.
* This is a reference to the imageRect in the MapCanvas and
* will be updated by zoom/pan/scrolling.
* <p>
* Do not alter this property inside of MapGrid.java!
*/
public Rectangle imageRect;
/**
* The MapCanvas width is needed in userToPixel_X().
*/
private int canvasWidth;
/**
* Constructs a new MapGrid.
*/
public MapGrid() {
}
/**
* Constructs and initializes a MapGrid with the specified parameters.
* @param x_lo the "user" value of the low end of the data domain along x.
* @param x_hi the "user" value of the high end of the data domain along x.
* @param y_lo the "user" value of the low end of the data domain along y.
* @param y_hi the "user" value of the high end of the data domain along y.
*
* The <b>data domain</b> is that region represented by the underlying base image.
*/
public MapGrid(double x_lo, double x_hi, double y_lo, double y_hi) {
this.setDomain_X(x_lo, x_hi);
this.setDomain_Y(y_lo, y_hi);
}
/**
* Sets canvasWidth.
* @param width the width associated with the MapCanvas.
*/
public void setCanvasWidth(int width) {
canvasWidth = width;
}
/**
* Sets the domain of X of the grid (in "user" coordinates).
* This should coincide with the domain specified
* by the underlying basemap in MapCanvas.
* @param lo the "user" value of the low end of the data domain along X.
* @param hi the "user" value of the high end of the data domain along X.
*
* A default delta_X will be calculated. This is overridden by
* the tool whenever MapTool.setDelta_X() is used to assign a
* specific delta_X to a tool.
*/
public void setDomain_X(double lo, double hi) {
double new_delta = Math.abs(hi-lo);
domain_X[LO] = lo;
domain_X[HI] = hi;
x_start = lo;
x_factor = hi - lo;
if ( x_type == LONGITUDE_AXIS && (hi - lo) != 360.0 )
modulo_X = false;
else
modulo_X = true;
if ( new_delta > 180 ) new_delta = 2.0;
else if ( new_delta > 90 ) new_delta = 1.0;
else if ( new_delta > 45 ) new_delta = 0.5;
else if ( new_delta > 18 ) new_delta = 0.2;
else if ( new_delta > 9 ) new_delta = 0.1;
else if ( new_delta > 4.5 ) new_delta = 0.05;
else if ( new_delta > 1.8 ) new_delta = 0.02;
else if ( new_delta > 0.9 ) new_delta = 0.01;
else if ( new_delta > 0.45 ) new_delta = 0.005;
else if ( new_delta > 0.18 ) new_delta = 0.002;
else new_delta = 0.001;
setDelta_X(new_delta);
}
/**
* Sets the domain of Y of the grid (in "user" coordinates).
* This should coincide with the domain specified
* by the underlying basemap in MapCanvas.
* @param lo the "user" value of the low end of the data domain along Y.
* @param hi the "user" value of the high end of the data domain along Y.
*
* A default delta_Y will be calculated. This is overridden by
* the tool whenever MapTool.setDelta_Y() is used to assign a
* specific delta_Y to a tool.
*/
public void setDomain_Y(double lo, double hi) {
double new_delta = hi - lo ;
domain_Y[LO] = lo;
domain_Y[HI] = hi;
y_start = lo;
y_factor = hi - lo;
if ( new_delta > 180 ) new_delta = 2.0;
else if ( new_delta > 90 ) new_delta = 1.0;
else if ( new_delta > 45 ) new_delta = 0.5;
else if ( new_delta > 18 ) new_delta = 0.2;
else if ( new_delta > 9 ) new_delta = 0.1;
else if ( new_delta > 4.5 ) new_delta = 0.05;
else if ( new_delta > 1.8 ) new_delta = 0.02;
else if ( new_delta > 0.9 ) new_delta = 0.01;
else if ( new_delta > 0.45 ) new_delta = 0.005;
else if ( new_delta > 0.18 ) new_delta = 0.002;
else new_delta = 0.001;
setDelta_Y(new_delta);
}
/**
* Sets delta_X which is used in grid snapping.
* @param delta the "user" spacing of grid cells along the X axis.
*/
public void setDelta_X(double delta) {
delta_X = delta;
}
/**
* Gets delta_X which is used in grid snapping.
*/
public double getDelta_X() {
return delta_X;
}
/**
* Sets delta_Y which is used in grid snapping.
* @param delta the "user" spacing of grid cells along the Y axis.
*/
public void setDelta_Y(double delta) {
delta_Y = delta;
}
/**
* Gets delta_Y which is used in grid snapping.
*/
public double getDelta_Y() {
return delta_Y;
}
//=========================================================
//
// X conversions.
//
//=========================================================
/**
* Converts a pixel value into a "user" value.
* @param pixel_x the X value in pixels.
* @return a "user" X value associated with the input.
*/
public double pixelToUser_X(int pixel_x) {
double user_x=0.0;
/*
* We need to take special care when the image is scrolling and
* pixel_x is beyond the edge of the initial image.
*
* In that case we need to subtract "imageRect.width" from the number of pixels
* we calculate before we apply "x_factor".
*/
if ( pixel_x > (imageRect.x+(imageRect.width-1)) )
user_x = (double)(pixel_x-imageRect.x-(imageRect.width-1)) *
(x_factor/(double)(imageRect.width-1)) + x_start;
else
user_x = (double)(pixel_x-imageRect.x) *
(x_factor/(double)(imageRect.width-1)) + x_start;
return(user_x);
}
/**
* Converts a "user" value into a pixel value.
* @param user_x the X value in "user" units.
* @return a pixel X value associated with the input.
*/
public int userToPixel_X(double user_x) {
int pixel_x=0;
pixel_x = (int)( (user_x - x_start) *
((double)(imageRect.width-1)/x_factor) + (double)imageRect.x );
// We need to make sure pixel_x is in the MapCanvas area if possible.
// This means it may need to be drawn on the first or second image
// depending on the value of imageRect.x when scrolling is in effect.
//
if ( pixel_x < 0 && pixel_x+imageRect.width < canvasWidth )
pixel_x += imageRect.width;
if ( pixel_x > canvasWidth && pixel_x-imageRect.width >= 0 )
pixel_x -= imageRect.width;
return(pixel_x);
}
/**
* Converts a "user" range along X to a width in pixels.
* @param range_x a "user" range.
* @return pixels the equivalent value in pixels.
*/
public int rangeToPixels_X(double range_x) {
int pixels = 0;
pixels = (int)( range_x * ((double)(imageRect.width-1)/x_factor) );
return pixels;
}
/**
* Returns the X pixel value nearest the closest X grid point.
* @param pixel_x the current X position in pixels.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @return a new X position in pixels.
*/
public int snap_X(int pixel_x, int style) {
return snap_X(pixel_x, style, 0);
}
/**
* Returns the X pixel value nearest the closest X grid point.
* @param pixel_x the current mouse X position.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @param shift number of grid cells to shift the result
* @return a new X position in pixels.
*/
public int snap_X(int pixel_x, int style, int shift) {
double return_val=0.0, user_val=0.0;
/*
* Note that we cannot use the pixelToUser_X() or userToPixel_X()
* methods because we NEED to know whether the "special care" mentioned
* below is taken in order to undo the calculation properly.
* This problem doesn't happen with snap_Y().
*
* Using the pixelToUser_X() and userToPixel_X() methods results in
* the tool jumping from the right edge to the left of the image depending
* on how far past the MapCanvas boundary the mouse is positioned.
* ==============================================================
* We need to take special care when the image is scrolling and
* pixel_x is beyond the edge of the initial image.
*
* In that case we need to subtract "imageRect.width" from the number of pixels
* we calculate before we apply "x_factor".
*/
// Check which image the pixel is on and calculate
// user_val accordingly.
//
if ( pixel_x > (imageRect.x+imageRect.width) )
user_val = (double)(pixel_x-imageRect.x -imageRect.width) *
(x_factor/(double)imageRect.width) + x_start;
else
user_val = (double)(pixel_x-imageRect.x) *
(x_factor/(double)imageRect.width) + x_start;
// Snap to the nearest grid point.
//
return_val = snapUser_X(user_val, style, shift);
// Undo the top calculation
//
if ( pixel_x > (imageRect.x + imageRect.width) )
return_val = (return_val - x_start) *
((double)imageRect.width/x_factor) +
(double)(imageRect.x + imageRect.width);
else
return_val = (return_val - x_start) *
((double)imageRect.width/x_factor) +
(double)imageRect.x;
return ( (int)return_val );
}
/**
* Returns the X "user" value nearest the closest X grid point.
* @param user_x the "user" X position.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @return a new value for user_x.
*/
public double snapUser_X(double user_x, int style) {
return snapUser_X(user_x, style, 0);
}
/**
* Returns the X "user" value nearest the closest X grid point.
* @param user_x the "user" X position.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @param shift number of grid cells to shift the result
* @return a new value for user_x.
*/
public double snapUser_X(double user_x, int style, int shift) {
double test_val=0.0, return_val=0.0, mid_adjust=0.0;
if ( style == SNAP_MID ) mid_adjust = delta_X/2.0;
// Test for the nearest grid point
//
test_val = user_x / delta_X;
if ( test_val >= 0 ) {
if ( (test_val - (int)test_val) < 0.5 )
return_val = ((int)(test_val)+shift) * delta_X + mid_adjust;
else
return_val = ((int)(test_val)+shift+1) * delta_X - mid_adjust;
} else {
if ( ((int)test_val - test_val) < 0.5 )
return_val = ((int)(test_val)+shift) * delta_X - mid_adjust;
else
return_val = ((int)(test_val)+shift-1) * delta_X + mid_adjust;
}
return ( return_val );
}
//=========================================================
//
// Y conversions.
//
//=========================================================
/**
* Converts a pixel value into a "user" value.
* (Note that pixel values increase from top to bottom, whereas
* user values increase from bottom to top.)
* @param pixel_y the pixel's y value.
* @return a "user" Y value associated with the input.
*/
public double pixelToUser_Y(int pixel_y) {
double user_y=0.0;
user_y = ((imageRect.height-1)-(pixel_y-imageRect.y)) *
(y_factor/(imageRect.height-1)) + y_start;
return(user_y);
}
/**
* Converts a "user" value into a pixel value.
* (Note that pixel values increase from top to bottom, whereas
* user values increase from bottom to top.)
* @param user_y the "user" y value.
* @return a pixel Y value associated with the input.
*/
public int userToPixel_Y(double user_y) {
int pixel_y=0;
pixel_y = (int)( imageRect.y - ( (user_y - y_start) *
((imageRect.height-1)/y_factor) - (imageRect.height-1)) );
return(pixel_y);
}
/**
* Converts a "user" range along Y to a width in pixels.
* @param range_y a "user" range.
* @return pixels the equivalent value in pixels.
*/
public int rangeToPixels_Y(double range_y) {
int pixels = 0;
pixels = (int)( range_y * ((double)(imageRect.height-1)/y_factor) );
return pixels;
}
/**
* Returns the Y pixel value nearest the closest Y grid point.
* @param pixel_y the current Y position in pixels.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @return a new Y position in p ixels.
*/
public int snap_Y(int pixel_y, int style) {
return snap_Y(pixel_y, style, 0);
}
/**
* Returns the Y pixel value nearest the closest Y grid point.
* @param pixel_y the current mouse Y position.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @param shift number of grid cells to shift the result
* @return a new Y position in pixels.
*/
public int snap_Y(int pixel_y, int style, int shift) {
double return_val=0.0, user_val=0.0;
user_val = this.pixelToUser_Y(pixel_y);
return_val = snapUser_Y(user_val, style, shift);
return ( this.userToPixel_Y(return_val) );
}
/**
* Returns the Y "user" value nearest the closest Y grid point.
* @param user_y the "user" Y position.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @return a new Y position in p ixels.
*/
public double snapUser_Y(double user_y, int style) {
return snapUser_Y(user_y, style, 0);
}
/**
* @param user_y the "user" Y position.
* @param style which nearby gridpoint to snap to [SNAP_ON, SNAP_MID]
* @param shift number of grid cells to shift the result
* @return a new value for user_y.
*/
public double snapUser_Y(double user_y, int style, int shift) {
double test_val=0.0, return_val=0.0, mid_adjust=0.0;
if ( style == SNAP_MID ) mid_adjust = delta_Y/2.0;
// Test for the nearest grid point
//
test_val = user_y / delta_Y;
if ( test_val >= 0 ) {
if ( (test_val - (int)test_val) < 0.5 )
return_val = ((int)(test_val)+shift) * delta_Y + mid_adjust;
else
return_val = ((int)(test_val)+shift+1) * delta_Y - mid_adjust;
} else {
if ( ((int)test_val - test_val) < 0.5 )
return_val = ((int)(test_val)+shift) * delta_Y - mid_adjust;
else
return_val = ((int)(test_val)+shift-1) * delta_Y + mid_adjust;
}
return ( return_val );
}
}