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

Last change on this file since 23 was 23, checked in by imi, 18 years ago
  • added commands to support undo later
  • added Edit-Layer concept
  • painting of deleted objects
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 /**
42 * A reference ellipsoid used in Projections
43 */
44 public static class Ellipsoid {
45 String name;
46 double a, ecc_squared;
47 Ellipsoid(String name, double a, double ecc_squared) {
48 this.name = name;
49 this.a = a;
50 this.ecc_squared = ecc_squared;
51 }
52 @Override
53 public String toString() {
54 return name;
55 }
56 }
57
58 /**
59 * All available reference ellipsoids.
60 */
61 public final static Ellipsoid[] allEllipsoids = new Ellipsoid[] {
62 new Ellipsoid("Airy", 6377563, 0.00667054),
63 new Ellipsoid("Australian National", 6378160, 0.006694542),
64 new Ellipsoid("Bessel 1841", 6377397, 0.006674372),
65 new Ellipsoid("Clarke 1866", 6378206, 0.006768658),
66 new Ellipsoid("Clarke 1880", 6378249, 0.006803511),
67 new Ellipsoid("Everest", 6377276, 0.006637847),
68 new Ellipsoid("Fischer Mercury 1960", 6378166, 0.006693422),
69 new Ellipsoid("Fischer 1968", 6378150, 0.006693422),
70 new Ellipsoid("GRS 1967", 6378160, 0.006694605),
71 new Ellipsoid("GRS 1980", 6378137, 0.00669438),
72 new Ellipsoid("Helmert 1906", 6378200, 0.006693422),
73 new Ellipsoid("Hough", 6378270, 0.00672267),
74 new Ellipsoid("Krassovsky", 6378245, 0.006693422),
75 new Ellipsoid("WGS-60", 6378165, 0.006693422),
76 new Ellipsoid("WGS-66", 6378145, 0.006694542),
77 new Ellipsoid("WGS-72", 6378135, 0.006694318),
78 new Ellipsoid("WGS-84", 6378137, 0.00669438)
79 };
80
81 private enum Hemisphere {north, south}
82
83 /**
84 * What hemisphere the whole map is in.
85 */
86 private Hemisphere hemisphere = Hemisphere.north;
87 /**
88 * What zone the whole map is in.
89 */
90 private int zone = 0; // 0 means not initialized
91 /**
92 * Reference ellipsoid used in projection
93 */
94 protected Ellipsoid ellipsoid = allEllipsoids[allEllipsoids.length-1];
95
96 /**
97 * Combobox with all ellipsoids for the configuration panel
98 */
99 private JComboBox ellipsoidCombo;
100 /**
101 * Spinner with all possible zones for the configuration panel
102 */
103 JSpinner zoneSpinner;
104 /**
105 * Hemisphere combo for the configuration panel
106 */
107 JComboBox hemisphereCombo;
108
109
110 @Override
111 public void latlon2xy(GeoPoint p) {
112 // converts lat/long to UTM coords. Equations from USGS Bulletin 1532
113 // North latitudes are positive, South latitudes are negative
114 // East longitudes are positive, West longitudes are negative
115 // lat and long are in decimal degrees
116 // Written by Chuck Gantz- chuck.gantz@globalstar.com
117 // ported to Ruby by Ben Gimpert- ben@somethingmodern.com
118 double k0 = 0.9996012717;
119
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 k0 = 0.9996;
153 double e1 = (1-Math.sqrt(1-ellipsoid.ecc_squared))/(1+Math.sqrt(1-ellipsoid.ecc_squared));
154 double x = p.x - 500000.0;
155 double y = p.y;
156 if (hemisphere == Hemisphere.south)
157 y -= 10000000.0;
158
159 double long_origin = (zone - 1)*6 - 180 + 3; // +3 puts origin in middle of zone
160 double ecc_prime_squared = ellipsoid.ecc_squared / (1 - ellipsoid.ecc_squared);
161 double m = y / k0;
162 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));
163
164 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);
165
166 double n1 = ellipsoid.a/Math.sqrt(1-ellipsoid.ecc_squared*Math.sin(phi1_rad)*Math.sin(phi1_rad));
167 double t1 = Math.tan(phi1_rad)*Math.tan(phi1_rad);
168 double c1 = ecc_prime_squared*Math.cos(phi1_rad)*Math.cos(phi1_rad);
169 double r1 = ellipsoid.a*(1-ellipsoid.ecc_squared)/Math.pow((1-ellipsoid.ecc_squared*Math.sin(phi1_rad)*Math.sin(phi1_rad)), 1.5);
170 double d = x / (n1*k0);
171
172 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);
173 p.lat = p.lat * RAD_TO_DEG;
174
175 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);
176 p.lon = long_origin + (p.lon * RAD_TO_DEG);
177 }
178
179 @Override
180 public String toString() {
181 return "UTM";
182 }
183
184 /**
185 * Helper class for the zone detection
186 * @author imi
187 */
188 private static class ZoneData {
189 int zone = 0;
190 Hemisphere hemisphere = Hemisphere.north;
191 }
192 /**
193 * Try to autodetect the zone and hemisphere from the dataset.
194 * @return The zone data extrakted from the dataset.
195 */
196 ZoneData autoDetect(Bounds b) {
197 ZoneData zd = new ZoneData();
198 if (b == null)
199 return zd;
200 GeoPoint center = b.centerLatLon();
201 double lat = center.lat;
202 double lon = center.lon;
203 // make sure the longitude is between -180.00 .. 179.9
204 double long_temp = (lon + 180) - (Math.floor((lon + 180) / 360) * 360) - 180;
205
206 zd.zone = (int)((long_temp + 180) / 6) + 1;
207 if ((lat >= 56.0) && (lat < 64.0) && (long_temp >= 3.0) && (long_temp < 12.0))
208 zd.zone = 32;
209 // special zones for Svalbard
210 if ((lat >= 72.0) && (lat < 84.0))
211 {
212 if ((long_temp >= 0.0) && (long_temp < 9.0))
213 zd.zone = 31;
214 else if ((long_temp >= 9.0) && (long_temp < 21.0))
215 zd.zone = 33;
216 else if ((long_temp >= 21.0) && (long_temp < 33.0))
217 zd.zone = 35;
218 else if ((long_temp >= 33.0) && (long_temp < 42.0))
219 zd.zone = 37;
220 }
221 zd.hemisphere = lat > 0 ? Hemisphere.north : Hemisphere.south;
222 return zd;
223 }
224
225 /**
226 * If the zone is not already set, calculate it from this dataset.
227 * If the dataset span over more than one zone, take the middle one
228 * (the zone of the middle lat/lon).
229 * Also, calculate the hemisphere (northern/southern).
230 */
231 @Override
232 public void init(Bounds b) {
233 if (zone == 0) {
234 ZoneData zd = autoDetect(b);
235 zone = zd.zone;
236 hemisphere = zd.hemisphere;
237 }
238 }
239
240 @Override
241 public JComponent getConfigurationPanel() {
242 JPanel panel = new JPanel(new GridBagLayout());
243 GBC gbc = GBC.std().insets(0,0,5,0);
244
245 // ellipsoid
246 if (ellipsoidCombo == null)
247 ellipsoidCombo = new JComboBox(allEllipsoids);
248 panel.add(new JLabel("Ellipsoid"), gbc);
249 panel.add(ellipsoidCombo, GBC.eol());
250 ellipsoidCombo.setSelectedItem(ellipsoid);
251
252 // zone
253 if (zoneSpinner == null)
254 zoneSpinner = new JSpinner(new SpinnerNumberModel(1,1,60,1));
255 panel.add(new JLabel("Zone"), gbc);
256 panel.add(zoneSpinner, GBC.eol().insets(0,5,0,5));
257 if (zone != 0)
258 zoneSpinner.setValue(zone);
259
260 // hemisphere
261 if (hemisphereCombo == null)
262 hemisphereCombo = new JComboBox(Hemisphere.values());
263 panel.add(new JLabel("Hemisphere"), gbc);
264 panel.add(hemisphereCombo, GBC.eop());
265 hemisphereCombo.setSelectedItem(hemisphere);
266
267 // Autodetect
268 JButton autoDetect = new JButton("Detect");
269 autoDetect.addActionListener(new ActionListener(){
270 public void actionPerformed(ActionEvent e) {
271 if (Main.main.getMapFrame() != null) {
272 ZoneData zd = autoDetect(Main.main.ds.getBoundsLatLon());
273 if (zd.zone == 0)
274 JOptionPane.showMessageDialog(Main.main, "Autodetection failed. Maybe the data set contain too few information.");
275 else {
276 zoneSpinner.setValue(zd.zone);
277 hemisphereCombo.setSelectedItem(zd.hemisphere);
278 }
279 } else {
280 JOptionPane.showMessageDialog(Main.main, "No data loaded. Please open a data set first.");
281 }
282 }
283 });
284 JLabel descLabel = new JLabel("Autodetect parameter based on loaded data");
285 descLabel.setFont(descLabel.getFont().deriveFont(Font.ITALIC));
286 panel.add(descLabel, GBC.eol().fill(GBC.HORIZONTAL));
287 panel.add(autoDetect, GBC.eol().anchor(GBC.CENTER));
288
289 return panel;
290 }
291
292 @Override
293 public void commitConfigurationPanel() {
294 if (ellipsoidCombo != null && zoneSpinner != null && hemisphereCombo != null) {
295 ellipsoid = (Ellipsoid)ellipsoidCombo.getSelectedItem();
296 zone = (Integer)zoneSpinner.getValue();
297 hemisphere = (Hemisphere)hemisphereCombo.getSelectedItem();
298 fireStateChanged();
299 }
300 }
301}
Note: See TracBrowser for help on using the repository browser.