| 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 | }
|
|---|