source: josm/trunk/src/org/openstreetmap/josm/data/projection/AbstractProjection.java@ 12161

Last change on this file since 12161 was 12161, checked in by michael2402, 7 years ago

See #13415: Add the ILatLon interface, unify handling of Nodes and CachedLatLon

  • Property svn:eol-style set to native
File size: 8.9 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.projection;
3
4import java.util.Collections;
5import java.util.HashMap;
6import java.util.Map;
7import java.util.function.DoubleUnaryOperator;
8
9import org.openstreetmap.josm.data.Bounds;
10import org.openstreetmap.josm.data.ProjectionBounds;
11import org.openstreetmap.josm.data.coor.EastNorth;
12import org.openstreetmap.josm.data.coor.ILatLon;
13import org.openstreetmap.josm.data.coor.LatLon;
14import org.openstreetmap.josm.data.projection.datum.Datum;
15import org.openstreetmap.josm.data.projection.proj.Proj;
16import org.openstreetmap.josm.tools.Utils;
17
18/**
19 * Implementation of the Projection interface that represents a coordinate reference system and delegates
20 * the real projection and datum conversion to other classes.
21 *
22 * It handles false easting and northing, central meridian and general scale factor before calling the
23 * delegate projection.
24 *
25 * Forwards lat/lon values to the real projection in units of radians.
26 *
27 * The fields are named after Proj.4 parameters.
28 *
29 * Subclasses of AbstractProjection must set ellps and proj to a non-null value.
30 * In addition, either datum or nadgrid has to be initialized to some value.
31 */
32public abstract class AbstractProjection implements Projection {
33
34 protected Ellipsoid ellps;
35 protected Datum datum;
36 protected Proj proj;
37 protected double x0; /* false easting (in meters) */
38 protected double y0; /* false northing (in meters) */
39 protected double lon0; /* central meridian */
40 protected double pm; /* prime meridian */
41 protected double k0 = 1.0; /* general scale factor */
42 protected double toMeter = 1.0; /* switch from meters to east/north coordinate units */
43
44 private volatile ProjectionBounds projectionBoundsBox;
45
46 /**
47 * Get the base ellipsoid that this projection uses.
48 * @return The {@link Ellipsoid}
49 */
50 public final Ellipsoid getEllipsoid() {
51 return ellps;
52 }
53
54 /**
55 * Gets the datum this projection is based on.
56 * @return The datum
57 */
58 public final Datum getDatum() {
59 return datum;
60 }
61
62 /**
63 * Replies the projection (in the narrow sense)
64 * @return The projection object
65 */
66 public final Proj getProj() {
67 return proj;
68 }
69
70 /**
71 * Gets an east offset that gets applied when converting the coordinate
72 * @return The offset to apply in meter
73 */
74 public final double getFalseEasting() {
75 return x0;
76 }
77
78 /**
79 * Gets an north offset that gets applied when converting the coordinate
80 * @return The offset to apply in meter
81 */
82 public final double getFalseNorthing() {
83 return y0;
84 }
85
86 /**
87 * Gets the meridian that this projection is centered on.
88 * @return The longitude of the meridian.
89 */
90 public final double getCentralMeridian() {
91 return lon0;
92 }
93
94 public final double getScaleFactor() {
95 return k0;
96 }
97
98 /**
99 * Get the factor that converts meters to intended units of east/north coordinates.
100 *
101 * For projected coordinate systems, the semi-major axis of the ellipsoid is
102 * always given in meters, which means the preliminary projection result will
103 * be in meters as well. This factor is used to convert to the intended units
104 * of east/north coordinates (e.g. feet in the US).
105 *
106 * For geographic coordinate systems, the preliminary "projection" result will
107 * be in degrees, so there is no reason to convert anything and this factor
108 * will by 1 by default.
109 *
110 * @return factor that converts meters to intended units of east/north coordinates
111 */
112 public final double getToMeter() {
113 return toMeter;
114 }
115
116 @Override
117 public EastNorth latlon2eastNorth(ILatLon toConvert) {
118 // TODO: Use ILatLon in datum, so we don't need to wrap it here.
119 LatLon ll = datum.fromWGS84(new LatLon(toConvert));
120 double[] en = proj.project(Utils.toRadians(ll.lat()), Utils.toRadians(LatLon.normalizeLon(ll.lon() - lon0 - pm)));
121 return new EastNorth((ellps.a * k0 * en[0] + x0) / toMeter, (ellps.a * k0 * en[1] + y0) / toMeter);
122 }
123
124 @Override
125 public LatLon eastNorth2latlon(EastNorth en) {
126 // We know it is a latlon. Nice would be to change this method return type to ILatLon
127 return eastNorth2latlon(en, LatLon::normalizeLon);
128 }
129
130 @Override
131 public LatLon eastNorth2latlonClamped(EastNorth en) {
132 ILatLon ll = eastNorth2latlon(en, lon -> Utils.clamp(lon, -180, 180));
133 Bounds bounds = getWorldBoundsLatLon();
134 return new LatLon(Utils.clamp(ll.lat(), bounds.getMinLat(), bounds.getMaxLat()),
135 Utils.clamp(ll.lon(), bounds.getMinLon(), bounds.getMaxLon()));
136 }
137
138 private LatLon eastNorth2latlon(EastNorth en, DoubleUnaryOperator normalizeLon) {
139 double[] latlonRad = proj.invproject((en.east() * toMeter - x0) / ellps.a / k0, (en.north() * toMeter - y0) / ellps.a / k0);
140 double lon = Utils.toDegrees(latlonRad[1]) + lon0 + pm;
141 LatLon ll = new LatLon(Utils.toDegrees(latlonRad[0]), normalizeLon.applyAsDouble(lon));
142 return datum.toWGS84(ll);
143 }
144
145 @Override
146 public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
147 if (proj.lonIsLinearToEast()) {
148 //FIXME: Respect datum?
149 // wrap the wrold around
150 Bounds bounds = getWorldBoundsLatLon();
151 double minEast = latlon2eastNorth(bounds.getMin()).east();
152 double maxEast = latlon2eastNorth(bounds.getMax()).east();
153 double dEast = maxEast - minEast;
154 if ((area.minEast < minEast || area.maxEast > maxEast) && dEast > 0) {
155 // We could handle the dEast < 0 case but we don't need it atm.
156 int minChunk = (int) Math.floor((area.minEast - minEast) / dEast);
157 int maxChunk = (int) Math.floor((area.maxEast - minEast) / dEast);
158 HashMap<ProjectionBounds, Projecting> ret = new HashMap<>();
159 for (int chunk = minChunk; chunk <= maxChunk; chunk++) {
160 ret.put(new ProjectionBounds(Math.max(area.minEast, minEast + chunk * dEast), area.minNorth,
161 Math.min(area.maxEast, maxEast + chunk * dEast), area.maxNorth),
162 new ShiftedProjecting(this, new EastNorth(-chunk * dEast, 0)));
163 }
164 return ret;
165 }
166 }
167
168 return Collections.singletonMap(area, this);
169 }
170
171 @Override
172 public double getDefaultZoomInPPD() {
173 // this will set the map scaler to about 1000 m
174 return 10 / getMetersPerUnit();
175 }
176
177 /**
178 * @return The EPSG Code of this CRS, null if it doesn't have one.
179 */
180 public abstract Integer getEpsgCode();
181
182 /**
183 * Default implementation of toCode().
184 * Should be overridden, if there is no EPSG code for this CRS.
185 */
186 @Override
187 public String toCode() {
188 return "EPSG:" + getEpsgCode();
189 }
190
191 protected static final double convertMinuteSecond(double minute, double second) {
192 return (minute/60.0) + (second/3600.0);
193 }
194
195 protected static final double convertDegreeMinuteSecond(double degree, double minute, double second) {
196 return degree + (minute/60.0) + (second/3600.0);
197 }
198
199 @Override
200 public final ProjectionBounds getWorldBoundsBoxEastNorth() {
201 ProjectionBounds result = projectionBoundsBox;
202 if (result == null) {
203 synchronized (this) {
204 result = projectionBoundsBox;
205 if (result == null) {
206 Bounds b = getWorldBoundsLatLon();
207 // add 4 corners
208 result = new ProjectionBounds(latlon2eastNorth(b.getMin()));
209 result.extend(latlon2eastNorth(b.getMax()));
210 result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), b.getMaxLon())));
211 result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), b.getMinLon())));
212 // and trace along the outline
213 double dLon = (b.getMaxLon() - b.getMinLon()) / 1000;
214 double dLat = (b.getMaxLat() - b.getMinLat()) / 1000;
215 for (double lon = b.getMinLon(); lon < b.getMaxLon(); lon += dLon) {
216 result.extend(latlon2eastNorth(new LatLon(b.getMinLat(), lon)));
217 result.extend(latlon2eastNorth(new LatLon(b.getMaxLat(), lon)));
218 }
219 for (double lat = b.getMinLat(); lat < b.getMaxLat(); lat += dLat) {
220 result.extend(latlon2eastNorth(new LatLon(lat, b.getMinLon())));
221 result.extend(latlon2eastNorth(new LatLon(lat, b.getMaxLon())));
222 }
223 projectionBoundsBox = result;
224 }
225 }
226 }
227 return projectionBoundsBox;
228 }
229
230 @Override
231 public Projection getBaseProjection() {
232 return this;
233 }
234}
Note: See TracBrowser for help on using the repository browser.