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

Last change on this file since 35 was 35, checked in by imi, 18 years ago
  • fixed bug in UTM
  • upload of nodes and segments
  • fixed bugs in display the selection
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 if (b == null)
198 return zd;
199 GeoPoint center = b.centerLatLon();
200 double lat = center.lat;
201 double lon = center.lon;
202 // make sure the longitude is between -180.00 .. 179.9
203 double long_temp = (lon + 180) - (Math.floor((lon + 180) / 360) * 360) - 180;
204
205 zd.zone = (int)((long_temp + 180) / 6) + 1;
206 if ((lat >= 56.0) && (lat < 64.0) && (long_temp >= 3.0) && (long_temp < 12.0))
207 zd.zone = 32;
208 // special zones for Svalbard
209 if ((lat >= 72.0) && (lat < 84.0))
210 {
211 if ((long_temp >= 0.0) && (long_temp < 9.0))
212 zd.zone = 31;
213 else if ((long_temp >= 9.0) && (long_temp < 21.0))
214 zd.zone = 33;
215 else if ((long_temp >= 21.0) && (long_temp < 33.0))
216 zd.zone = 35;
217 else if ((long_temp >= 33.0) && (long_temp < 42.0))
218 zd.zone = 37;
219 }
220 zd.hemisphere = lat > 0 ? Hemisphere.north : Hemisphere.south;
221 return zd;
222 }
223
224 /**
225 * If the zone is not already set, calculate it from this dataset.
226 * If the dataset span over more than one zone, take the middle one
227 * (the zone of the middle lat/lon).
228 * Also, calculate the hemisphere (northern/southern).
229 */
230 @Override
231 public void init(Bounds b) {
232 if (zone == 0) {
233 ZoneData zd = autoDetect(b);
234 zone = zd.zone;
235 hemisphere = zd.hemisphere;
236 }
237 }
238
239 @Override
240 public JComponent getConfigurationPanel() {
241 JPanel panel = new JPanel(new GridBagLayout());
242 GBC gbc = GBC.std().insets(0,0,5,0);
243
244 // ellipsoid
245 if (ellipsoidCombo == null)
246 ellipsoidCombo = new JComboBox(allEllipsoids);
247 panel.add(new JLabel("Ellipsoid"), gbc);
248 panel.add(ellipsoidCombo, GBC.eol());
249 ellipsoidCombo.setSelectedItem(ellipsoid);
250
251 // zone
252 if (zoneSpinner == null)
253 zoneSpinner = new JSpinner(new SpinnerNumberModel(1,1,60,1));
254 panel.add(new JLabel("Zone"), gbc);
255 panel.add(zoneSpinner, GBC.eol().insets(0,5,0,5));
256 if (zone != 0)
257 zoneSpinner.setValue(zone);
258
259 // hemisphere
260 if (hemisphereCombo == null)
261 hemisphereCombo = new JComboBox(Hemisphere.values());
262 panel.add(new JLabel("Hemisphere"), gbc);
263 panel.add(hemisphereCombo, GBC.eop());
264 hemisphereCombo.setSelectedItem(hemisphere);
265
266 // Autodetect
267 JButton autoDetect = new JButton("Detect");
268 autoDetect.addActionListener(new ActionListener(){
269 public void actionPerformed(ActionEvent e) {
270 if (Main.main.getMapFrame() != null) {
271 ZoneData zd = autoDetect(Main.main.ds.getBoundsLatLon());
272 if (zd.zone == 0)
273 JOptionPane.showMessageDialog(Main.main, "Autodetection failed. Maybe the data set contain too few information.");
274 else {
275 zoneSpinner.setValue(zd.zone);
276 hemisphereCombo.setSelectedItem(zd.hemisphere);
277 }
278 } else {
279 JOptionPane.showMessageDialog(Main.main, "No data loaded. Please open a data set first.");
280 }
281 }
282 });
283 JLabel descLabel = new JLabel("Autodetect parameter based on loaded data");
284 descLabel.setFont(descLabel.getFont().deriveFont(Font.ITALIC));
285 panel.add(descLabel, GBC.eol().fill(GBC.HORIZONTAL));
286 panel.add(autoDetect, GBC.eol().anchor(GBC.CENTER));
287
288 return panel;
289 }
290
291 @Override
292 public void commitConfigurationPanel() {
293 if (ellipsoidCombo != null && zoneSpinner != null && hemisphereCombo != null) {
294 ellipsoid = (Ellipsoid)ellipsoidCombo.getSelectedItem();
295 zone = (Integer)zoneSpinner.getValue();
296 hemisphere = (Hemisphere)hemisphereCombo.getSelectedItem();
297 fireStateChanged();
298 }
299 }
300}
Note: See TracBrowser for help on using the repository browser.