source: josm/src/org/openstreetmap/josm/data/projection/UTM.java@ 40

Last change on this file since 40 was 40, checked in by imi, 18 years ago
  • added world boundaries
  • added bug report exception handler
  • raw gps/real data when open depends now on extension
File size: 10.4 KB
Line 
1package org.openstreetmap.josm.data.projection;
2
3import java.awt.Font;
4import java.awt.GridBagLayout;
5import java.awt.event.ActionEvent;
6import java.awt.event.ActionListener;
7
8import javax.swing.JButton;
9import javax.swing.JComboBox;
10import javax.swing.JComponent;
11import javax.swing.JLabel;
12import javax.swing.JOptionPane;
13import javax.swing.JPanel;
14import javax.swing.JSpinner;
15import javax.swing.SpinnerNumberModel;
16
17import org.openstreetmap.josm.Main;
18import org.openstreetmap.josm.data.Bounds;
19import org.openstreetmap.josm.data.GeoPoint;
20import 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 */
36public 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}
Note: See TracBrowser for help on using the repository browser.