1 | package org.openstreetmap.josm.data.projection;
|
---|
2 |
|
---|
3 | import java.awt.Font;
|
---|
4 | import java.awt.GridBagLayout;
|
---|
5 | import java.awt.event.ActionEvent;
|
---|
6 | import java.awt.event.ActionListener;
|
---|
7 |
|
---|
8 | import javax.swing.JButton;
|
---|
9 | import javax.swing.JComboBox;
|
---|
10 | import javax.swing.JComponent;
|
---|
11 | import javax.swing.JLabel;
|
---|
12 | import javax.swing.JOptionPane;
|
---|
13 | import javax.swing.JPanel;
|
---|
14 | import javax.swing.JSpinner;
|
---|
15 | import javax.swing.SpinnerNumberModel;
|
---|
16 |
|
---|
17 | import org.openstreetmap.josm.Main;
|
---|
18 | import org.openstreetmap.josm.data.Bounds;
|
---|
19 | import org.openstreetmap.josm.data.GeoPoint;
|
---|
20 | import org.openstreetmap.josm.gui.GBC;
|
---|
21 |
|
---|
22 | /**
|
---|
23 | * A java port of a ruby port of a C port of a Projection from the
|
---|
24 | * Defense Mapping agency. ;-)
|
---|
25 | *
|
---|
26 | * The whole dataset is interpreted as beeing in one zone. This
|
---|
27 | * zone is initially taken from the center lat/lon value but can
|
---|
28 | * be configured to any other zone. Same is for the hemisphere.
|
---|
29 | *
|
---|
30 | * C code by Chuck Gantz
|
---|
31 | * Ruby port by Ben Gimpert
|
---|
32 | * modified Java port by imi (myself)
|
---|
33 | *
|
---|
34 | * @author imi
|
---|
35 | */
|
---|
36 | public class UTM extends Projection {
|
---|
37 |
|
---|
38 | public final static double DEG_TO_RAD = Math.PI / 180;
|
---|
39 | public final static double RAD_TO_DEG = 180 / Math.PI;
|
---|
40 |
|
---|
41 | public final static double k0 = 0.9996012717;
|
---|
42 |
|
---|
43 | /**
|
---|
44 | * A reference ellipsoid used in Projections
|
---|
45 | */
|
---|
46 | public static class Ellipsoid {
|
---|
47 | String name;
|
---|
48 | double a, ecc_squared;
|
---|
49 | Ellipsoid(String name, double a, double ecc_squared) {
|
---|
50 | this.name = name;
|
---|
51 | this.a = a;
|
---|
52 | this.ecc_squared = ecc_squared;
|
---|
53 | }
|
---|
54 | @Override
|
---|
55 | public String toString() {
|
---|
56 | return name;
|
---|
57 | }
|
---|
58 | }
|
---|
59 |
|
---|
60 | /**
|
---|
61 | * All available reference ellipsoids.
|
---|
62 | */
|
---|
63 | public final static Ellipsoid[] allEllipsoids = new Ellipsoid[] {
|
---|
64 | new Ellipsoid("Airy", 6377563, 0.00667054),
|
---|
65 | new Ellipsoid("Australian National", 6378160, 0.006694542),
|
---|
66 | new Ellipsoid("Bessel 1841", 6377397, 0.006674372),
|
---|
67 | new Ellipsoid("Clarke 1866", 6378206, 0.006768658),
|
---|
68 | new Ellipsoid("Clarke 1880", 6378249, 0.006803511),
|
---|
69 | new Ellipsoid("Everest", 6377276, 0.006637847),
|
---|
70 | new Ellipsoid("Fischer Mercury 1960", 6378166, 0.006693422),
|
---|
71 | new Ellipsoid("Fischer 1968", 6378150, 0.006693422),
|
---|
72 | new Ellipsoid("GRS 1967", 6378160, 0.006694605),
|
---|
73 | new Ellipsoid("GRS 1980", 6378137, 0.00669438),
|
---|
74 | new Ellipsoid("Helmert 1906", 6378200, 0.006693422),
|
---|
75 | new Ellipsoid("Hough", 6378270, 0.00672267),
|
---|
76 | new Ellipsoid("Krassovsky", 6378245, 0.006693422),
|
---|
77 | new Ellipsoid("WGS-60", 6378165, 0.006693422),
|
---|
78 | new Ellipsoid("WGS-66", 6378145, 0.006694542),
|
---|
79 | new Ellipsoid("WGS-72", 6378135, 0.006694318),
|
---|
80 | new Ellipsoid("WGS-84", 6378137, 0.00669438)
|
---|
81 | };
|
---|
82 |
|
---|
83 | private enum Hemisphere {north, south}
|
---|
84 |
|
---|
85 | /**
|
---|
86 | * What hemisphere the whole map is in.
|
---|
87 | */
|
---|
88 | private Hemisphere hemisphere = Hemisphere.north;
|
---|
89 | /**
|
---|
90 | * What zone the whole map is in.
|
---|
91 | */
|
---|
92 | private int zone = 0; // 0 means not initialized
|
---|
93 | /**
|
---|
94 | * Reference ellipsoid used in projection
|
---|
95 | */
|
---|
96 | protected Ellipsoid ellipsoid = allEllipsoids[allEllipsoids.length-1];
|
---|
97 |
|
---|
98 | /**
|
---|
99 | * Combobox with all ellipsoids for the configuration panel
|
---|
100 | */
|
---|
101 | private JComboBox ellipsoidCombo;
|
---|
102 | /**
|
---|
103 | * Spinner with all possible zones for the configuration panel
|
---|
104 | */
|
---|
105 | JSpinner zoneSpinner;
|
---|
106 | /**
|
---|
107 | * Hemisphere combo for the configuration panel
|
---|
108 | */
|
---|
109 | JComboBox hemisphereCombo;
|
---|
110 |
|
---|
111 |
|
---|
112 | @Override
|
---|
113 | public void latlon2xy(GeoPoint p) {
|
---|
114 | // converts lat/long to UTM coords. Equations from USGS Bulletin 1532
|
---|
115 | // North latitudes are positive, South latitudes are negative
|
---|
116 | // East longitudes are positive, West longitudes are negative
|
---|
117 | // lat and long are in decimal degrees
|
---|
118 | // Written by Chuck Gantz- chuck.gantz@globalstar.com
|
---|
119 | // ported to Ruby by Ben Gimpert- ben@somethingmodern.com
|
---|
120 | double lat_rad = p.lat * DEG_TO_RAD;
|
---|
121 | double long_temp = (p.lon + 180) - (Math.floor((p.lon + 180) / 360) * 360) - 180;
|
---|
122 | double long_rad = long_temp * DEG_TO_RAD;
|
---|
123 |
|
---|
124 | double long_origin = (zone - 1)*6 - 180 + 3; // +3 puts origin in middle of zone
|
---|
125 | double long_origin_rad = long_origin * DEG_TO_RAD;
|
---|
126 |
|
---|
127 | double ecc_prime_squared = ellipsoid.ecc_squared / (1 - ellipsoid.ecc_squared);
|
---|
128 |
|
---|
129 | double n = ellipsoid.a / Math.sqrt(1 - ellipsoid.ecc_squared * Math.sin(lat_rad) * Math.sin(lat_rad));
|
---|
130 | double t = Math.tan(lat_rad) * Math.tan(lat_rad);
|
---|
131 | double c = ecc_prime_squared * Math.cos(lat_rad) * Math.cos(lat_rad);
|
---|
132 | double a = Math.cos(lat_rad) * (long_rad - long_origin_rad);
|
---|
133 |
|
---|
134 | double e2 = ellipsoid.ecc_squared*ellipsoid.ecc_squared;
|
---|
135 | double e3 = e2*ellipsoid.ecc_squared;
|
---|
136 | double m = ellipsoid.a * (((1 - ellipsoid.ecc_squared/4 - 3*e2/64 - 5*e3/256)*lat_rad) - ((3*ellipsoid.ecc_squared/8 + 3*e2/32 + 45*e3/1024)*Math.sin(2*lat_rad)) + ((15*e2/256 + 45*e3/1024)*Math.sin(4*lat_rad)) - ((35*e3/3072)*Math.sin(6*lat_rad)));
|
---|
137 |
|
---|
138 | p.x = k0*n*(a+(1-t+c)*a*a*a/6 + (5-18*t+t*t+72*c-58*ecc_prime_squared)*a*a*a*a*a/120) + 500000.0;
|
---|
139 | p.y = k0*(m+n*Math.tan(lat_rad)*(a*a/2+(5-t+9*c+4*c*c)*a*a*a*a/24 + (61-58*t+t*t+600*c-330*ecc_prime_squared)*a*a*a*a*a*a/720));
|
---|
140 | if (p.lat < 0)
|
---|
141 | p.y += 10000000.0; // offset for southern hemisphere
|
---|
142 | }
|
---|
143 |
|
---|
144 | @Override
|
---|
145 | public void xy2latlon(GeoPoint p) {
|
---|
146 | // converts UTM coords to lat/long. Equations from USGS Bulletin 1532
|
---|
147 | // East longitudes are positive, West longitudes are negative.
|
---|
148 | // North latitudes are positive, South latitudes are negative
|
---|
149 | // lat and long are in decimal degrees.
|
---|
150 | // Written by Chuck Gantz- chuck.gantz@globalstar.com
|
---|
151 | // ported to Ruby by Ben Gimpert- ben@somethingmodern.com
|
---|
152 | double e1 = (1-Math.sqrt(1-ellipsoid.ecc_squared))/(1+Math.sqrt(1-ellipsoid.ecc_squared));
|
---|
153 | double x = p.x - 500000.0;
|
---|
154 | double y = p.y;
|
---|
155 | if (hemisphere == Hemisphere.south)
|
---|
156 | y -= 10000000.0;
|
---|
157 |
|
---|
158 | double long_origin = (zone - 1)*6 - 180 + 3; // +3 puts origin in middle of zone
|
---|
159 | double ecc_prime_squared = ellipsoid.ecc_squared / (1 - ellipsoid.ecc_squared);
|
---|
160 | double m = y / k0;
|
---|
161 | double mu = m / (ellipsoid.a*(1-ellipsoid.ecc_squared/4-3*ellipsoid.ecc_squared*ellipsoid.ecc_squared/64-5*ellipsoid.ecc_squared*ellipsoid.ecc_squared*ellipsoid.ecc_squared/256));
|
---|
162 |
|
---|
163 | double phi1_rad = mu + (3*e1/2-27*e1*e1*e1/32)*Math.sin(2*mu) + (21*e1*e1/16-55*e1*e1*e1*e1/32)*Math.sin(4*mu) +(151*e1*e1*e1/96)*Math.sin(6*mu);
|
---|
164 |
|
---|
165 | double n1 = ellipsoid.a/Math.sqrt(1-ellipsoid.ecc_squared*Math.sin(phi1_rad)*Math.sin(phi1_rad));
|
---|
166 | double t1 = Math.tan(phi1_rad)*Math.tan(phi1_rad);
|
---|
167 | double c1 = ecc_prime_squared*Math.cos(phi1_rad)*Math.cos(phi1_rad);
|
---|
168 | double r1 = ellipsoid.a*(1-ellipsoid.ecc_squared)/Math.pow((1-ellipsoid.ecc_squared*Math.sin(phi1_rad)*Math.sin(phi1_rad)), 1.5);
|
---|
169 | double d = x / (n1*k0);
|
---|
170 |
|
---|
171 | p.lat = phi1_rad - (n1*Math.tan(phi1_rad)/r1)*(d*d/2-(5+3*t1+10*c1-4*c1*c1-9*ecc_prime_squared)*d*d*d*d/24 + (61+90*t1+298*c1+45*t1*t1-252*ecc_prime_squared-3*c1*c1)*d*d*d*d*d*d/720);
|
---|
172 | p.lat = p.lat * RAD_TO_DEG;
|
---|
173 |
|
---|
174 | p.lon = (d-(1+2*t1+c1)*d*d*d/6+(5-2*c1+28*t1-3*c1*c1+8*ecc_prime_squared+24*t1*t1)*d*d*d*d*d/120)/Math.cos(phi1_rad);
|
---|
175 | p.lon = long_origin + (p.lon * RAD_TO_DEG);
|
---|
176 | }
|
---|
177 |
|
---|
178 | @Override
|
---|
179 | public String toString() {
|
---|
180 | return "UTM";
|
---|
181 | }
|
---|
182 |
|
---|
183 | /**
|
---|
184 | * Helper class for the zone detection
|
---|
185 | * @author imi
|
---|
186 | */
|
---|
187 | private static class ZoneData {
|
---|
188 | int zone = 0;
|
---|
189 | Hemisphere hemisphere = Hemisphere.north;
|
---|
190 | }
|
---|
191 | /**
|
---|
192 | * Try to autodetect the zone and hemisphere from the dataset.
|
---|
193 | * @return The zone data extrakted from the dataset.
|
---|
194 | */
|
---|
195 | ZoneData autoDetect(Bounds b) {
|
---|
196 | ZoneData zd = new ZoneData();
|
---|
197 | GeoPoint center = b.centerLatLon();
|
---|
198 | double lat = center.lat;
|
---|
199 | double lon = center.lon;
|
---|
200 | // make sure the longitude is between -180.00 .. 179.9
|
---|
201 | double long_temp = (lon + 180) - (Math.floor((lon + 180) / 360) * 360) - 180;
|
---|
202 |
|
---|
203 | zd.zone = (int)((long_temp + 180) / 6) + 1;
|
---|
204 | if ((lat >= 56.0) && (lat < 64.0) && (long_temp >= 3.0) && (long_temp < 12.0))
|
---|
205 | zd.zone = 32;
|
---|
206 | // special zones for Svalbard
|
---|
207 | if ((lat >= 72.0) && (lat < 84.0))
|
---|
208 | {
|
---|
209 | if ((long_temp >= 0.0) && (long_temp < 9.0))
|
---|
210 | zd.zone = 31;
|
---|
211 | else if ((long_temp >= 9.0) && (long_temp < 21.0))
|
---|
212 | zd.zone = 33;
|
---|
213 | else if ((long_temp >= 21.0) && (long_temp < 33.0))
|
---|
214 | zd.zone = 35;
|
---|
215 | else if ((long_temp >= 33.0) && (long_temp < 42.0))
|
---|
216 | zd.zone = 37;
|
---|
217 | }
|
---|
218 | zd.hemisphere = lat > 0 ? Hemisphere.north : Hemisphere.south;
|
---|
219 | return zd;
|
---|
220 | }
|
---|
221 |
|
---|
222 | /**
|
---|
223 | * If the zone is not already set, calculate it from this dataset.
|
---|
224 | * If the dataset span over more than one zone, take the middle one
|
---|
225 | * (the zone of the middle lat/lon).
|
---|
226 | * Also, calculate the hemisphere (northern/southern).
|
---|
227 | */
|
---|
228 | @Override
|
---|
229 | public void init(Bounds b) {
|
---|
230 | if (zone == 0) {
|
---|
231 | ZoneData zd = autoDetect(b);
|
---|
232 | zone = zd.zone;
|
---|
233 | hemisphere = zd.hemisphere;
|
---|
234 | }
|
---|
235 | }
|
---|
236 |
|
---|
237 | @Override
|
---|
238 | public JComponent getConfigurationPanel() {
|
---|
239 | JPanel panel = new JPanel(new GridBagLayout());
|
---|
240 | GBC gbc = GBC.std().insets(0,0,5,0);
|
---|
241 |
|
---|
242 | // ellipsoid
|
---|
243 | if (ellipsoidCombo == null)
|
---|
244 | ellipsoidCombo = new JComboBox(allEllipsoids);
|
---|
245 | panel.add(new JLabel("Ellipsoid"), gbc);
|
---|
246 | panel.add(ellipsoidCombo, GBC.eol());
|
---|
247 | ellipsoidCombo.setSelectedItem(ellipsoid);
|
---|
248 |
|
---|
249 | // zone
|
---|
250 | if (zoneSpinner == null)
|
---|
251 | zoneSpinner = new JSpinner(new SpinnerNumberModel(1,1,60,1));
|
---|
252 | panel.add(new JLabel("Zone"), gbc);
|
---|
253 | panel.add(zoneSpinner, GBC.eol().insets(0,5,0,5));
|
---|
254 | if (zone != 0)
|
---|
255 | zoneSpinner.setValue(zone);
|
---|
256 |
|
---|
257 | // hemisphere
|
---|
258 | if (hemisphereCombo == null)
|
---|
259 | hemisphereCombo = new JComboBox(Hemisphere.values());
|
---|
260 | panel.add(new JLabel("Hemisphere"), gbc);
|
---|
261 | panel.add(hemisphereCombo, GBC.eop());
|
---|
262 | hemisphereCombo.setSelectedItem(hemisphere);
|
---|
263 |
|
---|
264 | // Autodetect
|
---|
265 | JButton autoDetect = new JButton("Detect");
|
---|
266 | autoDetect.addActionListener(new ActionListener(){
|
---|
267 | public void actionPerformed(ActionEvent e) {
|
---|
268 | if (Main.main.getMapFrame() != null) {
|
---|
269 | ZoneData zd = autoDetect(Main.main.ds.getBoundsLatLon());
|
---|
270 | if (zd.zone == 0)
|
---|
271 | JOptionPane.showMessageDialog(Main.main, "Autodetection failed. Maybe the data set contain too few information.");
|
---|
272 | else {
|
---|
273 | zoneSpinner.setValue(zd.zone);
|
---|
274 | hemisphereCombo.setSelectedItem(zd.hemisphere);
|
---|
275 | }
|
---|
276 | } else {
|
---|
277 | JOptionPane.showMessageDialog(Main.main, "No data loaded. Please open a data set first.");
|
---|
278 | }
|
---|
279 | }
|
---|
280 | });
|
---|
281 | JLabel descLabel = new JLabel("Autodetect parameter based on loaded data");
|
---|
282 | descLabel.setFont(descLabel.getFont().deriveFont(Font.ITALIC));
|
---|
283 | panel.add(descLabel, GBC.eol().fill(GBC.HORIZONTAL));
|
---|
284 | panel.add(autoDetect, GBC.eol().anchor(GBC.CENTER));
|
---|
285 |
|
---|
286 | return panel;
|
---|
287 | }
|
---|
288 |
|
---|
289 | @Override
|
---|
290 | public void commitConfigurationPanel() {
|
---|
291 | if (ellipsoidCombo != null && zoneSpinner != null && hemisphereCombo != null) {
|
---|
292 | ellipsoid = (Ellipsoid)ellipsoidCombo.getSelectedItem();
|
---|
293 | zone = (Integer)zoneSpinner.getValue();
|
---|
294 | hemisphere = (Hemisphere)hemisphereCombo.getSelectedItem();
|
---|
295 | fireStateChanged();
|
---|
296 | }
|
---|
297 | }
|
---|
298 | }
|
---|