001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.projection; 003 004import org.openstreetmap.josm.data.Bounds; 005import org.openstreetmap.josm.data.ProjectionBounds; 006import org.openstreetmap.josm.data.coor.EastNorth; 007import org.openstreetmap.josm.data.coor.LatLon; 008import org.openstreetmap.josm.data.projection.datum.Datum; 009import org.openstreetmap.josm.data.projection.proj.Proj; 010 011/** 012 * Implementation of the Projection interface that represents a coordinate reference system and delegates 013 * the real projection and datum conversion to other classes. 014 * 015 * It handles false easting and northing, central meridian and general scale factor before calling the 016 * delegate projection. 017 * 018 * Forwards lat/lon values to the real projection in units of radians. 019 * 020 * The fields are named after Proj.4 parameters. 021 * 022 * Subclasses of AbstractProjection must set ellps and proj to a non-null value. 023 * In addition, either datum or nadgrid has to be initialized to some value. 024 */ 025public abstract class AbstractProjection implements Projection { 026 027 protected Ellipsoid ellps; 028 protected Datum datum; 029 protected Proj proj; 030 protected double x0; /* false easting (in meters) */ 031 protected double y0; /* false northing (in meters) */ 032 protected double lon0; /* central meridian */ 033 protected double pm; /* prime meridian */ 034 protected double k0 = 1.0; /* general scale factor */ 035 protected double toMeter = 1.0; /* switch from meters to east/north coordinate units */ 036 037 private volatile ProjectionBounds projectionBoundsBox; 038 039 public final Ellipsoid getEllipsoid() { 040 return ellps; 041 } 042 043 public final Datum getDatum() { 044 return datum; 045 } 046 047 /** 048 * Replies the projection (in the narrow sense) 049 * @return The projection object 050 */ 051 public final Proj getProj() { 052 return proj; 053 } 054 055 public final double getFalseEasting() { 056 return x0; 057 } 058 059 public final double getFalseNorthing() { 060 return y0; 061 } 062 063 public final double getCentralMeridian() { 064 return lon0; 065 } 066 067 public final double getScaleFactor() { 068 return k0; 069 } 070 071 /** 072 * Get the factor that converts meters to intended units of east/north coordinates. 073 * 074 * For projected coordinate systems, the semi-major axis of the ellipsoid is 075 * always given in meters, which means the preliminary projection result will 076 * be in meters as well. This factor is used to convert to the intended units 077 * of east/north coordinates (e.g. feet in the US). 078 * 079 * For geographic coordinate systems, the preliminary "projection" result will 080 * be in degrees, so there is no reason to convert anything and this factor 081 * will by 1 by default. 082 * 083 * @return factor that converts meters to intended units of east/north coordinates 084 */ 085 public final double getToMeter() { 086 return toMeter; 087 } 088 089 @Override 090 public EastNorth latlon2eastNorth(LatLon ll) { 091 ll = datum.fromWGS84(ll); 092 double[] en = proj.project(Math.toRadians(ll.lat()), Math.toRadians(LatLon.normalizeLon(ll.lon() - lon0 - pm))); 093 return new EastNorth((ellps.a * k0 * en[0] + x0) / toMeter, (ellps.a * k0 * en[1] + y0) / toMeter); 094 } 095 096 @Override 097 public LatLon eastNorth2latlon(EastNorth en) { 098 double[] latlon_rad = proj.invproject((en.east() * toMeter - x0) / ellps.a / k0, (en.north() * toMeter - y0) / ellps.a / k0); 099 LatLon ll = new LatLon(Math.toDegrees(latlon_rad[0]), LatLon.normalizeLon(Math.toDegrees(latlon_rad[1]) + lon0 + pm)); 100 return datum.toWGS84(ll); 101 } 102 103 @Override 104 public double getDefaultZoomInPPD() { 105 // this will set the map scaler to about 1000 m 106 return 10; 107 } 108 109 /** 110 * @return The EPSG Code of this CRS, null if it doesn't have one. 111 */ 112 public abstract Integer getEpsgCode(); 113 114 /** 115 * Default implementation of toCode(). 116 * Should be overridden, if there is no EPSG code for this CRS. 117 */ 118 @Override 119 public String toCode() { 120 return "EPSG:" + getEpsgCode(); 121 } 122 123 protected static final double convertMinuteSecond(double minute, double second) { 124 return (minute/60.0) + (second/3600.0); 125 } 126 127 protected static final double convertDegreeMinuteSecond(double degree, double minute, double second) { 128 return degree + (minute/60.0) + (second/3600.0); 129 } 130 131 @Override 132 public final ProjectionBounds getWorldBoundsBoxEastNorth() { 133 ProjectionBounds result = projectionBoundsBox; 134 if (result == null) { 135 synchronized (this) { 136 result = projectionBoundsBox; 137 if (result == null) { 138 Bounds b = getWorldBoundsLatLon(); 139 // add 4 corners 140 result = new ProjectionBounds(latlon2eastNorth(b.getMin())); 141 result.extend(latlon2eastNorth(b.getMax())); 142 result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), b.getMaxLon()))); 143 result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), b.getMinLon()))); 144 // and trace along the outline 145 double dLon = (b.getMaxLon() - b.getMinLon()) / 1000; 146 double dLat = (b.getMaxLat() - b.getMinLat()) / 1000; 147 for (double lon = b.getMinLon(); lon < b.getMaxLon(); lon += dLon) { 148 result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), lon))); 149 result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), lon))); 150 } 151 for (double lat = b.getMinLat(); lat < b.getMaxLat(); lat += dLat) { 152 result.extend(latlon2eastNorth(new LatLon(lat, b.getMinLon()))); 153 result.extend(latlon2eastNorth(new LatLon(lat, b.getMaxLon()))); 154 } 155 projectionBoundsBox = result; 156 } 157 } 158 } 159 return projectionBoundsBox; 160 } 161}