All Downloads are FREE. Search and download functionalities are using the official Maven repository.

gov.nasa.worldwind.globes.projections.ProjectionTransverseMercator Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2014 United States Government as represented by the Administrator of the
 * National Aeronautics and Space Administration.
 * All Rights Reserved.
 */

package gov.nasa.worldwind.globes.projections;

import gov.nasa.worldwind.geom.*;
import gov.nasa.worldwind.geom.coords.TMCoord;
import gov.nasa.worldwind.globes.*;
import gov.nasa.worldwind.util.*;

/**
 * Provides a Transverse Mercator ellipsoidal projection using the WGS84 ellipsoid. The projection's central meridian
 * may be specified and defaults to the Prime Meridian (0 longitude). By default, the projection computes values for 30
 * degrees either side of the central meridian. This may be changed via the {@link
 * #setWidth(gov.nasa.worldwind.geom.Angle)} method, but the projection may fail for large widths.
 * 

* The projection limits are modified to reflect the central meridian and the width, however the projection limits are * clamped to a minimum of -180 degrees and a maximum of +180 degrees. It's therefore not possible to display a band * whose central meridian is plus or minus 180. * * @author tag * @version $Id: ProjectionTransverseMercator.java 2393 2014-10-20 20:21:55Z tgaskins $ */ public class ProjectionTransverseMercator extends AbstractGeographicProjection { protected static Angle DEFAULT_WIDTH = Angle.fromDegrees(30); protected static Angle DEFAULT_CENTRAL_MERIDIAN = Angle.ZERO; protected static Angle DEFAULT_CENTRAL_LATITUDE = Angle.ZERO; protected Angle width = DEFAULT_WIDTH; protected Angle centralMeridian = DEFAULT_CENTRAL_MERIDIAN; protected Angle centralLatitude = DEFAULT_CENTRAL_LATITUDE; /** Creates a projection whose central meridian is the Prime Meridian and central latitude is 0. */ public ProjectionTransverseMercator() { super(makeProjectionLimits(DEFAULT_CENTRAL_MERIDIAN, DEFAULT_WIDTH)); } /** * Creates a projection with a specified central meridian and a central latitude of 0. * * @param centralMeridian The projection's central meridian. */ public ProjectionTransverseMercator(Angle centralMeridian) { super(makeProjectionLimits(centralMeridian, DEFAULT_WIDTH)); if (centralMeridian == null) { String message = Logging.getMessage("nullValue.CentralMeridianIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.centralMeridian = centralMeridian; } /** * Creates a projection with a specified central meridian and central latitude. * * @param centralMeridian The projection's central meridian. * @param centralLatitude The projection's central latitude. */ public ProjectionTransverseMercator(Angle centralMeridian, Angle centralLatitude) { super(makeProjectionLimits(centralMeridian, DEFAULT_WIDTH)); if (centralMeridian == null) { String message = Logging.getMessage("nullValue.CentralMeridianIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } if (centralLatitude == null) { String message = Logging.getMessage("nullValue.CentralLatitudeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.centralMeridian = centralMeridian; this.centralLatitude = centralLatitude; } @Override public String getName() { return "Transverse Mercator"; } /** * Indicates this projection's central meridian. * * @return This projection's central meridian. */ public Angle getCentralMeridian() { return centralMeridian; } /** * Specifies this projections central meridian. * * @param centralMeridian This projection's central meridian. The default is 0. */ public void setCentralMeridian(Angle centralMeridian) { if (centralMeridian == null) { String message = Logging.getMessage("nullValue.CentralMeridianIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.centralMeridian = centralMeridian; this.setProjectionLimits(makeProjectionLimits(this.getCentralMeridian(), this.getWidth())); } /** * Indicates this projection's central latitude. * * @return This projection's central latitude. */ public Angle getCentralLatitude() { return centralLatitude; } /** * Set this projection's central latitude. * * @param centralLatitude This projection's central latitude. The default is 0. */ public void setCentralLatitude(Angle centralLatitude) { if (centralLatitude == null) { String message = Logging.getMessage("nullValue.CentralLatitudeIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.centralLatitude = centralLatitude; } /** * Indicates the region in which positions are mapped. The default is 30 degrees either side of this projection's * central meridian. * * @return This projection's width. */ public Angle getWidth() { return width; } /** * Specifies the region in which positions are mapped. The default is 30 degrees either side of this projection's * central meridian. * * @param width This projection's width. */ public void setWidth(Angle width) { if (width == null) { String message = Logging.getMessage("nullValue.AngleIsNull"); Logging.logger().severe(message); throw new IllegalArgumentException(message); } this.width = width; this.setProjectionLimits(makeProjectionLimits(this.getCentralMeridian(), this.getWidth())); } protected static Sector makeProjectionLimits(Angle centralMeridian, Angle width) { double minLon = centralMeridian.degrees - width.degrees; if (minLon < -180) minLon = -180; double maxLon = centralMeridian.degrees + width.degrees; if (maxLon > 180) maxLon = 180; return Sector.fromDegrees(-90, 90, minLon, maxLon); } protected double getScale() { return 1.0; } @Override public Vec4 geographicToCartesian(Globe globe, Angle latitude, Angle longitude, double metersElevation, Vec4 offset) { if (latitude.degrees > 86) latitude = Angle.fromDegrees(86); else if (latitude.degrees < -82) latitude = Angle.fromDegrees(-82); if (longitude.degrees > this.centralMeridian.degrees + this.width.degrees) longitude = Angle.fromDegrees(this.centralMeridian.degrees + this.width.degrees); else if (longitude.degrees < this.centralMeridian.degrees - this.width.degrees) longitude = Angle.fromDegrees(this.centralMeridian.degrees - this.width.degrees); TMCoord tm = TMCoord.fromLatLon(latitude, longitude, globe, null, null, this.centralLatitude, this.centralMeridian, 0, 0, this.getScale()); return new Vec4(tm.getEasting(), tm.getNorthing(), metersElevation); } @Override public void geographicToCartesian(Globe globe, Sector sector, int numLat, int numLon, double[] metersElevation, Vec4 offset, Vec4[] out) { double minLat = sector.getMinLatitude().radians; double maxLat = sector.getMaxLatitude().radians; double minLon = sector.getMinLongitude().radians; double maxLon = sector.getMaxLongitude().radians; double deltaLat = (maxLat - minLat) / (numLat > 1 ? numLat - 1 : 1); double deltaLon = (maxLon - minLon) / (numLon > 1 ? numLon - 1 : 1); double minLatLimit = -82 * Math.PI / 180; double maxLatLimit = 86 * Math.PI / 180; double minLonLimit = this.centralMeridian.radians - this.width.radians; double maxLonLimit = this.centralMeridian.radians + this.width.radians; int pos = 0; // Iterate over the latitude and longitude coordinates in the specified sector, computing the Cartesian point // corresponding to each latitude and longitude. double lat = minLat; for (int j = 0; j < numLat; j++, lat += deltaLat) { if (j == numLat - 1) // explicitly set the last lat to the max latitude to ensure alignment lat = maxLat; lat = WWMath.clamp(lat, minLatLimit, maxLatLimit); // limit lat to projection limits double lon = minLon; for (int i = 0; i < numLon; i++, lon += deltaLon) { if (i == numLon - 1) // explicitly set the last lon to the max longitude to ensure alignment lon = maxLon; lon = WWMath.clamp(lon, minLonLimit, maxLonLimit); // limit lon to projection limits TMCoord tm = TMCoord.fromLatLon(Angle.fromRadians(lat), Angle.fromRadians(lon), globe, null, null, this.centralLatitude, this.centralMeridian, 0, 0, this.getScale()); double x = tm.getEasting(); double y = tm.getNorthing(); double z = metersElevation[pos]; out[pos++] = new Vec4(x, y, z); } } } @Override public Position cartesianToGeographic(Globe globe, Vec4 cart, Vec4 offset) { TMCoord tm = TMCoord.fromTM(cart.x, cart.y, globe, this.centralLatitude, this.centralMeridian, 0, 0, this.getScale()); return new Position(tm.getLatitude(), tm.getLongitude(), cart.z); } // These are spherical forms from Map Projections -- A Working Manual, but I can't get them to fully work. -- tag 6/25/14 // @Override // public Vec4 geographicToCartesian(Globe globe, Angle latitude, Angle longitude, double metersElevation, Vec4 offset) // { // double B = Math.cos(latitude.radians) * Math.sin(longitude.radians - this.getCentralMeridian().radians); // if (B == 1) // B = 0.9999; // TODO // // double x = globe.getEquatorialRadius() * this.getScale() * 0.5 * Math.log((1 + B) / (1 - B)); // double y; // if (Math.abs(latitude.degrees) == 90 || Math.abs(longitude.degrees - this.centralMeridian.degrees) == 90) // y = globe.getEquatorialRadius() * this.getScale() * (Math.signum(latitude.degrees) * Math.PI/2); // else // { // double s = Math.tan(latitude.radians) / Math.cos(longitude.radians - this.getCentralMeridian().radians); // y = globe.getEquatorialRadius() * this.getScale() * Math.atan(s); // } // // return new Vec4(x + offset.x, y, metersElevation); // } // // @Override // public Position cartesianToGeographic(Globe globe, Vec4 cart, Vec4 offset) // { // double rk0 = globe.getEquatorialRadius() * this.getScale(); // double D = cart.y / rk0; // // double lat = Math.asin( // Math.sin(D) / Math.cosh((cart.x - offset.x) / rk0)); // double lon = Math.atan(Math.sinh((cart.x() - offset.x) / rk0) / Math.cos(D)); // // return Position.fromRadians(lat, lon + this.getCentralMeridian().radians, cart.z); // } @Override public Vec4 northPointingTangent(Globe globe, Angle latitude, Angle longitude) { // Choose a small angle that we'll use as an increment in order to estimate the north pointing tangent by // computing the vector resulting from a small increment in latitude. Using 1e-7 in radians gives a tangent // resolution of approximately 1/2 meter. We specify the value in radians since geodeticToCartesian performs // arithmetic using angles in radians. Angle deltaLat = Angle.fromRadians(1.0e-7); if (latitude.degrees + deltaLat.degrees >= 86) // compute the incremental vector below the location { Vec4 p1 = this.geographicToCartesian(globe, latitude, longitude, 0, null); Vec4 p2 = this.geographicToCartesian(globe, latitude.subtract(deltaLat), longitude, 0, null); return p1.subtract3(p2).normalize3(); } else if (latitude.degrees - deltaLat.degrees <= -82) // compute the incremental vector above the location { Vec4 p1 = this.geographicToCartesian(globe, latitude.add(deltaLat), longitude, 0, null); Vec4 p2 = this.geographicToCartesian(globe, latitude, longitude, 0, null); return p1.subtract3(p2).normalize3(); } else // compute the average of the incremental vector above and below the location { Vec4 p1 = this.geographicToCartesian(globe, latitude.add(deltaLat), longitude, 0, null); Vec4 p2 = this.geographicToCartesian(globe, latitude.subtract(deltaLat), longitude, 0, null); return p1.subtract3(p2).normalize3(); } } @Override public boolean isContinuous() { return false; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ProjectionTransverseMercator that = (ProjectionTransverseMercator) o; if (!centralMeridian.equals(that.centralMeridian)) return false; if (!centralLatitude.equals(that.centralLatitude)) return false; if (!width.equals(that.width)) return false; return true; } @Override public int hashCode() { int result = width.hashCode(); result = 31 * result + centralMeridian.hashCode(); result = 31 * result + centralLatitude.hashCode(); return result; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy