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