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

Last change on this file was 16553, checked in by Don-vip, 4 years ago

see #19334 - javadoc fixes + protected constructors for abstract classes

  • Property svn:eol-style set to native
File size: 9.4 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(
123 (ellps.a * k0 * en[0] + x0) / toMeter,
124 (ellps.a * k0 * en[1] + y0) / toMeter);
125 }
126
127 @Override
128 public LatLon eastNorth2latlon(EastNorth en) {
129 // We know it is a latlon. Nice would be to change this method return type to ILatLon
130 return eastNorth2latlon(en, LatLon::normalizeLon);
131 }
132
133 @Override
134 public LatLon eastNorth2latlonClamped(EastNorth en) {
135 ILatLon ll = eastNorth2latlon(en, lon -> Utils.clamp(lon, -180, 180));
136 Bounds bounds = getWorldBoundsLatLon();
137 return new LatLon(Utils.clamp(ll.lat(), bounds.getMinLat(), bounds.getMaxLat()),
138 Utils.clamp(ll.lon(), bounds.getMinLon(), bounds.getMaxLon()));
139 }
140
141 private LatLon eastNorth2latlon(EastNorth en, DoubleUnaryOperator normalizeLon) {
142 double[] latlonRad = proj.invproject(
143 (en.east() * toMeter - x0) / ellps.a / k0,
144 (en.north() * toMeter - y0) / ellps.a / k0);
145 double lon = Utils.toDegrees(latlonRad[1]) + lon0 + pm;
146 LatLon ll = new LatLon(Utils.toDegrees(latlonRad[0]), normalizeLon.applyAsDouble(lon));
147 return datum.toWGS84(ll);
148 }
149
150 @Override
151 public Map<ProjectionBounds, Projecting> getProjectingsForArea(ProjectionBounds area) {
152 if (proj.lonIsLinearToEast()) {
153 //FIXME: Respect datum?
154 // wrap the world around
155 Bounds bounds = getWorldBoundsLatLon();
156 double minEast = latlon2eastNorth(bounds.getMin()).east();
157 double maxEast = latlon2eastNorth(bounds.getMax()).east();
158 double dEast = maxEast - minEast;
159 if ((area.minEast < minEast || area.maxEast > maxEast) && dEast > 0) {
160 // We could handle the dEast < 0 case but we don't need it atm.
161 int minChunk = (int) Math.floor((area.minEast - minEast) / dEast);
162 int maxChunk = (int) Math.floor((area.maxEast - minEast) / dEast);
163 HashMap<ProjectionBounds, Projecting> ret = new HashMap<>();
164 for (int chunk = minChunk; chunk <= maxChunk; chunk++) {
165 ret.put(new ProjectionBounds(Math.max(area.minEast, minEast + chunk * dEast), area.minNorth,
166 Math.min(area.maxEast, maxEast + chunk * dEast), area.maxNorth),
167 new ShiftedProjecting(this, new EastNorth(-chunk * dEast, 0)));
168 }
169 return ret;
170 }
171 }
172
173 return Collections.singletonMap(area, this);
174 }
175
176 @Override
177 public double getDefaultZoomInPPD() {
178 // this will set the map scaler to about 1000 m
179 return 10 / getMetersPerUnit();
180 }
181
182 /**
183 * Returns The EPSG Code of this CRS.
184 * @return The EPSG Code of this CRS, null if it doesn't have one.
185 */
186 public abstract Integer getEpsgCode();
187
188 /**
189 * Default implementation of toCode().
190 * Should be overridden, if there is no EPSG code for this CRS.
191 */
192 @Override
193 public String toCode() {
194 return "EPSG:" + getEpsgCode();
195 }
196
197 protected static final double convertMinuteSecond(double minute, double second) {
198 return (minute/60.0) + (second/3600.0);
199 }
200
201 protected static final double convertDegreeMinuteSecond(double degree, double minute, double second) {
202 return degree + (minute/60.0) + (second/3600.0);
203 }
204
205 @Override
206 public final ProjectionBounds getWorldBoundsBoxEastNorth() {
207 ProjectionBounds result = projectionBoundsBox;
208 if (result == null) {
209 synchronized (this) {
210 result = projectionBoundsBox;
211 if (result == null) {
212 ProjectionBounds bds = new ProjectionBounds();
213 visitOutline(getWorldBoundsLatLon(), 1000, bds::extend);
214 projectionBoundsBox = bds;
215 }
216 }
217 }
218 return projectionBoundsBox;
219 }
220
221 @Override
222 public Projection getBaseProjection() {
223 return this;
224 }
225
226 @Override
227 public void visitOutline(Bounds b, Consumer<EastNorth> visitor) {
228 visitOutline(b, 100, visitor);
229 }
230
231 private void visitOutline(Bounds b, int nPoints, Consumer<EastNorth> visitor) {
232 double maxlon = b.getMaxLon();
233 if (b.crosses180thMeridian()) {
234 maxlon += 360.0;
235 }
236 double spanLon = maxlon - b.getMinLon();
237 double spanLat = b.getMaxLat() - b.getMinLat();
238
239 //TODO: Use projection to see if there is any need for doing this along each axis.
240 for (int step = 0; step < nPoints; step++) {
241 visitor.accept(latlon2eastNorth(
242 new LatLon(b.getMinLat(), b.getMinLon() + spanLon * step / nPoints)));
243 }
244 for (int step = 0; step < nPoints; step++) {
245 visitor.accept(latlon2eastNorth(
246 new LatLon(b.getMinLat() + spanLat * step / nPoints, maxlon)));
247 }
248 for (int step = 0; step < nPoints; step++) {
249 visitor.accept(latlon2eastNorth(
250 new LatLon(b.getMaxLat(), maxlon - spanLon * step / nPoints)));
251 }
252 for (int step = 0; step < nPoints; step++) {
253 visitor.accept(latlon2eastNorth(
254 new LatLon(b.getMaxLat() - spanLat * step / nPoints, b.getMinLon())));
255 }
256 }
257}
Note: See TracBrowser for help on using the repository browser.