001 package edu.nrao.sss.geom; 002 003 import java.awt.geom.AffineTransform; 004 import java.awt.geom.NoninvertibleTransformException; 005 import java.awt.geom.Point2D; 006 import java.math.BigDecimal; 007 008 import edu.nrao.sss.measure.ArcUnits; 009 import edu.nrao.sss.measure.Latitude; 010 import edu.nrao.sss.measure.Longitude; 011 012 /** 013 * A coordinate transformer that maps a point on a sphere to a point on a 014 * plane using an azimuthal equidistant projection. This projection is 015 * such that the relative distances from a point at the center of the 016 * plane to other points elsewhere on the plane are accurate in their 017 * ratios to one another. This makes it a useful projection for comparing 018 * relative distances from a fixed point. 019 * <p> 020 * The algorithms used in this class were taken from: 021 * <blockquote> 022 * <a href="http://mathworld.wolfram.com/about/author.html"> 023 * Weisstein, Eric W</a>, 024 * <a href="http://mathworld.wolfram.com/AzimuthalEquidistantProjection.html"> 025 * "Azimuthal Equidistant Projection"</a>, from 026 * <a href="http://mathworld.wolfram.com/"> 027 * <i>MathWorld</i></a>--A Wolfram Web Resource. 028 * </blockquote></p> 029 * <p> 030 * <b>Version Info:</b> 031 * <table style="margin-left:2em"> 032 * <tr><td>$Revision: 1239 $</td></tr> 033 * <tr><td>$Date: 2008-04-25 10:34:57 -0600 (Fri, 25 Apr 2008) $</td></tr> 034 * <tr><td>$Author: dharland $ (last person to modify)</td></tr> 035 * </table></p> 036 * 037 * @author David M. Harland 038 * @since 2007-06-07 039 */ 040 public class AzimuthalEquidistantProjector 041 { 042 private SphericalPosition centerPoint; 043 private AffineTransform xyTransformer; 044 045 private double latCtr, lonCtr; 046 private double cosLatCtr, sinLatCtr; 047 048 /** Creates a new instance. */ 049 public AzimuthalEquidistantProjector() 050 { 051 setCenter(new EarthPosition()); 052 xyTransformer = new AffineTransform(); 053 } 054 055 /** 056 * Sets the center point, in longitude and latitude, for this projector. 057 * @param centerPosition 058 * the center point, in longitude and latitude, for this projector. 059 */ 060 public final void setCenter(SphericalPosition centerPosition) 061 { 062 centerPoint = centerPosition; 063 064 latCtr = centerPoint.getLatitude().toUnits(ArcUnits.RADIAN).doubleValue(); 065 lonCtr = centerPoint.getLongitude().toUnits(ArcUnits.RADIAN).doubleValue(); 066 067 cosLatCtr = Math.cos(latCtr); 068 sinLatCtr = Math.sin(latCtr); 069 } 070 071 /** 072 * Returns the 2D-to-2D planar transformer used by this projector. 073 * This transformer is initially created as an identity transformer. 074 * The returned transformer is a reference to the one held by this 075 * projector, so changes made to it are reflected herein. 076 * <p> 077 * This projector turns a longitude or latitude of ninety degrees 078 * into an x or y value of one-half pi. By properly configuring 079 * the returned transformer, you can change this projector's center 080 * coordinate and x and y ranges to other values.</p> 081 * 082 * @return the affine transform used by this projector to transform the 083 * x,y coordinates it produces into x',y' coordinates. 084 */ 085 public AffineTransform getXyTransformer() 086 { 087 return xyTransformer; 088 } 089 090 /** 091 * Projects the longitude, latitude position on a sphere into an x,y 092 * position on a plane. The returned value is first run through 093 * an azimuthal equidistant projection, and then through an 094 * {@link #getXyTransformer() affine transform} that may be configured 095 * by clients. 096 * 097 * @param sphericalPosition 098 * a longitude, latitude coordinate pair on a sphere. 099 * (The distance attribute is ignored.) 100 * 101 * @return an x,y point on a plane created by an azimuthal equidistant 102 * projection from the given position on a sphere. 103 */ 104 public Point2D getXyFor(SphericalPosition sphericalPosition) 105 { 106 double latPos = sphericalPosition.getLatitude().toUnits(ArcUnits.RADIAN).doubleValue(); 107 double lonPos = sphericalPosition.getLongitude().toUnits(ArcUnits.RADIAN).doubleValue(); 108 109 double cosLatPos = Math.cos(latPos); 110 double sinLatPos = Math.sin(latPos); 111 112 double deltaLon = lonPos - lonCtr; 113 double cosDeltaLon = Math.cos(deltaLon); 114 115 double c = 116 Math.acos(sinLatCtr * sinLatPos + cosLatCtr * cosLatPos * cosDeltaLon); 117 118 double k = (c == 0.0) ? 0.0 : c / Math.sin(c); 119 120 double x = k * cosLatPos * Math.sin(deltaLon); 121 122 double y = k * (cosLatCtr*sinLatPos - sinLatCtr*cosLatPos*cosDeltaLon); 123 124 Point2D rawPoint = new Point2D.Double(x, y); 125 126 return 127 xyTransformer.isIdentity() ? rawPoint 128 : xyTransformer.transform(rawPoint, rawPoint); 129 } 130 131 /** 132 * Performs the inverse projection of an x,y point on a plane to a 133 * longitude, latitude position on a sphere. The x,y point is first run 134 * through the inverse of an 135 * {@link #getXyTransformer() affine transform} that may be configured 136 * by clients. It is then run through the inverse of an azimuthal 137 * equidistant projection. 138 * 139 * @param xyPosition an x,y position on a plane. 140 * 141 * @return a longitude, latitude position on a sphere created by an 142 * inverse azimuthal equidistant projection from the given position 143 * on a plane. 144 */ 145 public SphericalPosition getLatLonFor(Point2D xyPosition) 146 throws NoninvertibleTransformException 147 { 148 double x = xyPosition.getX(); 149 double y = xyPosition.getY(); 150 151 //Apply inverse transformation 152 if (!xyTransformer.isIdentity()) 153 { 154 Point2D raw = new Point2D.Double(x, y); 155 Point2D xf = xyTransformer.inverseTransform(raw, null); 156 157 x = xf.getX(); 158 y = xf.getY(); 159 } 160 161 double c = Math.sqrt(x*x + y*y); 162 163 double cosC = Math.cos(c); 164 double sinC = Math.sin(c); 165 166 double sinLat = cosC * sinLatCtr + y * sinC * cosLatCtr / c; 167 168 double lat = Math.asin(sinLat); 169 double lon = lonCtr; 170 171 if (sinLat >= +1) 172 { 173 lon += Math.atan(-x/y); 174 } 175 else if (sinLat <= -1) 176 { 177 lon += Math.atan(+x/y); 178 } 179 else 180 { 181 lon += Math.atan(x * sinC / 182 (c * cosLatCtr * cosC - y * sinLatCtr * sinC)); 183 } 184 185 Latitude latitude = new Latitude (new BigDecimal(lat), ArcUnits.RADIAN); 186 Longitude longitude = new Longitude(new BigDecimal(lon), ArcUnits.RADIAN); 187 EarthPosition ep = new EarthPosition(longitude, latitude, null); 188 189 return ep; 190 } 191 192 //============================================================================ 193 // 194 //============================================================================ 195 /* 196 public static void main(String[] args) throws Exception 197 { 198 AzimuthalEquidistantProjector projector = new AzimuthalEquidistantProjector(); 199 200 AffineTransform xform = projector.getXyTransformer(); 201 double scale = 100.0 / (Math.PI / 2.0); 202 //xform.translate(100, 100); 203 xform.scale(scale, -scale); 204 205 SphericalPosition center = 206 new edu.nrao.sss.astronomy.SimpleSkyPosition(); //Lat=0, Lon=0 207 208 projector.setCenter(center); 209 210 System.out.println(" 0d, 0d => " + projector.getXyFor(center)); 211 212 System.out.println(); 213 214 SphericalPosition pos = new edu.nrao.sss.astronomy.SimpleSkyPosition(); 215 pos.getLongitude().set(90.0, ArcUnits.DEGREE); 216 System.out.println(" 90d, 0d => " + projector.getXyFor(pos)); 217 218 pos.getLongitude().set(0.0, ArcUnits.DEGREE); 219 pos.getLatitude().set(90.0, ArcUnits.DEGREE); 220 System.out.println(" 0d, 90d => " + projector.getXyFor(pos)); 221 222 pos.getLongitude().set(-90.0, ArcUnits.DEGREE); 223 pos.getLatitude().set(0.0, ArcUnits.DEGREE); 224 System.out.println(" -90d, 0d => " + projector.getXyFor(pos)); 225 226 pos.getLongitude().set(0.0, ArcUnits.DEGREE); 227 pos.getLatitude().set(-90.0, ArcUnits.DEGREE); 228 System.out.println(" 0d, -90d => " + projector.getXyFor(pos)); 229 230 System.out.println(); 231 232 pos.getLongitude().set(180.0, ArcUnits.DEGREE); 233 pos.getLatitude().set(0.0, ArcUnits.DEGREE); 234 System.out.println(" 180d, 0d => " + projector.getXyFor(pos)); 235 236 pos.getLongitude().set(-180.0, ArcUnits.DEGREE); 237 pos.getLatitude().set(0.0, ArcUnits.DEGREE); 238 System.out.println("-180d, 0d => " + projector.getXyFor(pos)); 239 240 pos.getLongitude().set(360.0, ArcUnits.DEGREE); 241 pos.getLatitude().set(0.0, ArcUnits.DEGREE); 242 System.out.println(" 360d, 0d => " + projector.getXyFor(pos)); 243 244 System.out.println(); 245 246 pos.getLongitude().set(180.0, ArcUnits.DEGREE); 247 pos.getLatitude().set(90.0, ArcUnits.DEGREE); 248 System.out.println(" 180d, 90d => " + projector.getXyFor(pos)); 249 250 pos.getLongitude().set(360.0, ArcUnits.DEGREE); 251 pos.getLatitude().set(90.0, ArcUnits.DEGREE); 252 System.out.println(" 360d, 90d => " + projector.getXyFor(pos)); 253 254 System.out.println(); 255 256 pos.getLongitude().set(0.0, ArcUnits.DEGREE); 257 pos.getLatitude().set(90.0, ArcUnits.DEGREE); 258 Point2D xy = projector.getXyFor(pos); 259 System.out.print(" 0d, 90d => " + xy); 260 pos = projector.getLatLonFor(xy); 261 System.out.println("; INVERSE => " + pos.getLongitude().toUnits(ArcUnits.DEGREE) + ", " + pos.getLatitude().toUnits(ArcUnits.DEGREE)); 262 263 pos.getLongitude().set(90.0, ArcUnits.DEGREE); 264 pos.getLatitude().set(0.0, ArcUnits.DEGREE); 265 xy = projector.getXyFor(pos); 266 System.out.print(" 90d, 0d => " + xy); 267 pos = projector.getLatLonFor(xy); 268 System.out.println("; INVERSE => " + pos.getLongitude().toUnits(ArcUnits.DEGREE) + ", " + pos.getLatitude().toUnits(ArcUnits.DEGREE)); 269 270 pos.getLongitude().set(30.0, ArcUnits.DEGREE); 271 pos.getLatitude().set(60.0, ArcUnits.DEGREE); 272 xy = projector.getXyFor(pos); 273 System.out.print(" 30d, 60d => " + xy); 274 pos = projector.getLatLonFor(xy); 275 System.out.println("; INVERSE => " + pos.getLongitude().toUnits(ArcUnits.DEGREE) + ", " + pos.getLatitude().toUnits(ArcUnits.DEGREE)); 276 277 pos.getLongitude().set(45.0, ArcUnits.DEGREE); 278 pos.getLatitude().set(45.0, ArcUnits.DEGREE); 279 xy = projector.getXyFor(pos); 280 System.out.print(" 45d, 45d => " + xy); 281 pos = projector.getLatLonFor(xy); 282 System.out.println("; INVERSE => " + pos.getLongitude().toUnits(ArcUnits.DEGREE) + ", " + pos.getLatitude().toUnits(ArcUnits.DEGREE)); 283 284 pos.getLongitude().set(60.0, ArcUnits.DEGREE); 285 pos.getLatitude().set(30.0, ArcUnits.DEGREE); 286 xy = projector.getXyFor(pos); 287 System.out.print(" 60d, 30d => " + xy); 288 pos = projector.getLatLonFor(xy); 289 System.out.println("; INVERSE => " + pos.getLongitude().toUnits(ArcUnits.DEGREE) + ", " + pos.getLatitude().toUnits(ArcUnits.DEGREE)); 290 } 291 */ 292 }