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

Last change on this file since 13250 was 12820, checked in by bastiK, 7 years ago

see #15229 - wrap around +180 degrees, for consistency

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