source: josm/trunk/src/org/openstreetmap/josm/data/projection/CustomProjection.java@ 5227

Last change on this file since 5227 was 5227, checked in by bastiK, 12 years ago

finish custom projection

  • Property svn:eol-style set to native
File size: 15.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.data.projection;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5
6import java.util.ArrayList;
7import java.util.Arrays;
8import java.util.HashMap;
9import java.util.HashSet;
10import java.util.List;
11import java.util.Map;
12import java.util.Set;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import org.openstreetmap.josm.data.Bounds;
17import org.openstreetmap.josm.data.coor.LatLon;
18import org.openstreetmap.josm.data.projection.CustomProjection.Param;
19import org.openstreetmap.josm.data.projection.datum.CentricDatum;
20import org.openstreetmap.josm.data.projection.datum.Datum;
21import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
22import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
23import org.openstreetmap.josm.data.projection.datum.SevenParameterDatum;
24import org.openstreetmap.josm.data.projection.datum.ThreeParameterDatum;
25import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
26import org.openstreetmap.josm.data.projection.proj.Proj;
27import org.openstreetmap.josm.data.projection.proj.ProjParameters;
28import org.openstreetmap.josm.tools.Utils;
29
30/**
31 * Custom projection
32 *
33 * Inspired by PROJ.4 and Proj4J.
34 */
35public class CustomProjection extends AbstractProjection {
36
37 /**
38 * pref String that defines the projection
39 *
40 * null means fall back mode (Mercator)
41 */
42 protected String pref = null;
43
44 protected static class Param {
45 public String key;
46 public boolean hasValue;
47
48 public final static Param x_0 = new Param("x_0", true);
49 public final static Param y_0 = new Param("y_0", true);
50 public final static Param lon_0 = new Param("lon_0", true);
51 public final static Param k_0 = new Param("k_0", true);
52 public final static Param ellps = new Param("ellps", true);
53 public final static Param a = new Param("a", true);
54 public final static Param es = new Param("es", true);
55 public final static Param rf = new Param("rf", true);
56 public final static Param f = new Param("f", true);
57 public final static Param b = new Param("b", true);
58 public final static Param datum = new Param("datum", true);
59 public final static Param towgs84 = new Param("towgs84", true);
60 public final static Param nadgrids = new Param("nadgrids", true);
61 public final static Param proj = new Param("proj", true);
62 public final static Param lat_0 = new Param("lat_0", true);
63 public final static Param lat_1 = new Param("lat_1", true);
64 public final static Param lat_2 = new Param("lat_2", true);
65
66 public final static Set<Param> params = new HashSet<Param>(Arrays.asList(
67 x_0, y_0, lon_0, k_0, ellps, a, es, rf, f, b, datum, towgs84,
68 nadgrids, proj, lat_0, lat_1, lat_2
69 ));
70
71 public final static Map<String, Param> paramsByKey = new HashMap<String, Param>();
72 static {
73 for (Param p : params) {
74 paramsByKey.put(p.key, p);
75 }
76 }
77
78 public Param(String key, boolean hasValue) {
79 this.key = key;
80 this.hasValue = hasValue;
81 }
82 }
83
84 public void update(String pref) throws ProjectionConfigurationException {
85 if (pref == null) {
86 this.pref = null;
87 ellps = Ellipsoid.WGS84;
88 datum = WGS84Datum.INSTANCE;
89 proj = new org.openstreetmap.josm.data.projection.proj.Mercator();
90 } else {
91 Map<String, String> parameters = new HashMap<String, String>();
92 String[] parts = pref.trim().split("\\s+");
93 if (pref.trim().isEmpty()) {
94 parts = new String[0];
95 }
96 for (int i = 0; i < parts.length; i++) {
97 String part = parts[i];
98 if (part.isEmpty() || part.charAt(0) != '+')
99 throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' sign (found ''{0}'')", part));
100 Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part);
101 if (m.matches()) {
102 String key = m.group(1);
103 String value = null;
104 if (m.groupCount() >= 3) {
105 value = m.group(3);
106 }
107 if (!Param.paramsByKey.containsKey(key))
108 throw new ProjectionConfigurationException(tr("Unkown parameter: ''{0}''.", key));
109 if (Param.paramsByKey.get(key).hasValue && value == null)
110 throw new ProjectionConfigurationException(tr("Value expected for parameter ''{0}''.", key));
111 if (!Param.paramsByKey.get(key).hasValue && value != null)
112 throw new ProjectionConfigurationException(tr("No value expected for parameter ''{0}''.", key));
113 parameters.put(key, value);
114 } else
115 throw new ProjectionConfigurationException(tr("Unexpected parameter format (''{0}'')", part));
116 }
117 ellps = parseEllipsoid(parameters);
118 datum = parseDatum(parameters, ellps);
119 proj = parseProjection(parameters, ellps);
120 String s = parameters.get(Param.x_0.key);
121 if (s != null) {
122 this.x_0 = parseDouble(s, Param.x_0.key);
123 }
124 s = parameters.get(Param.y_0.key);
125 if (s != null) {
126 this.y_0 = parseDouble(s, Param.x_0.key);
127 }
128 s = parameters.get(Param.lon_0.key);
129 if (s != null) {
130 this.lon_0 = parseAngle(s, Param.x_0.key);
131 }
132 s = parameters.get(Param.k_0.key);
133 if (s != null) {
134 this.k_0 = parseDouble(s, Param.x_0.key);
135 }
136 this.pref = pref;
137 }
138 }
139
140 public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException {
141 String code = parameters.get(Param.ellps.key);
142 if (code != null) {
143 Ellipsoid ellipsoid = Projections.getEllipsoid(code);
144 if (ellipsoid == null) {
145 throw new ProjectionConfigurationException(tr("Ellipsoid ''{0}'' not supported.", code));
146 } else {
147 return ellipsoid;
148 }
149 }
150 String s = parameters.get(Param.a.key);
151 if (s != null) {
152 double a = parseDouble(s, Param.a.key);
153 if (parameters.get(Param.es.key) != null) {
154 double es = parseDouble(parameters, Param.es.key);
155 return Ellipsoid.create_a_es(a, es);
156 }
157 if (parameters.get(Param.rf.key) != null) {
158 double rf = parseDouble(parameters, Param.rf.key);
159 return Ellipsoid.create_a_rf(a, rf);
160 }
161 if (parameters.get(Param.f.key) != null) {
162 double f = parseDouble(parameters, Param.f.key);
163 return Ellipsoid.create_a_f(a, f);
164 }
165 if (parameters.get(Param.b.key) != null) {
166 double b = parseDouble(parameters, Param.b.key);
167 return Ellipsoid.create_a_b(a, b);
168 }
169 }
170 if (parameters.containsKey(Param.a.key) ||
171 parameters.containsKey(Param.es.key) ||
172 parameters.containsKey(Param.rf.key) ||
173 parameters.containsKey(Param.f.key) ||
174 parameters.containsKey(Param.b.key))
175 throw new ProjectionConfigurationException(tr("Combination of ellipsoid parameters is not supported."));
176 // nothing specified, use WGS84 as default
177 return Ellipsoid.WGS84;
178 }
179
180 public Datum parseDatum(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
181 String nadgridsId = parameters.get(Param.nadgrids.key);
182 if (nadgridsId != null) {
183 NTV2GridShiftFileWrapper nadgrids = Projections.getNadgrids(nadgridsId);
184 if (nadgrids == null)
185 throw new ProjectionConfigurationException(tr("Grid shift file ''{0}'' for option +nadgrids not supported.", nadgridsId));
186 return new NTV2Datum(nadgridsId, null, ellps, nadgrids);
187 }
188
189 String towgs84 = parameters.get(Param.towgs84.key);
190 if (towgs84 != null)
191 return parseToWGS84(towgs84, ellps);
192
193 String datumId = parameters.get(Param.datum.key);
194 if (datumId != null) {
195 Datum datum = Projections.getDatum(datumId);
196 if (datum == null) throw new ProjectionConfigurationException(tr("Unkown datum identifier: ''{0}''", datumId));
197 return datum;
198 } else
199 return new CentricDatum(null, null, ellps);
200 }
201
202 public Datum parseToWGS84(String paramList, Ellipsoid ellps) throws ProjectionConfigurationException {
203 String[] numStr = paramList.split(",");
204
205 if (numStr.length != 3 && numStr.length != 7)
206 throw new ProjectionConfigurationException(tr("Unexpected number of arguments for parameter ''towgs84'' (must be 3 or 7)"));
207 List<Double> towgs84Param = new ArrayList<Double>();
208 for (int i = 0; i < numStr.length; i++) {
209 try {
210 towgs84Param.add(Double.parseDouble(numStr[i]));
211 } catch (NumberFormatException e) {
212 throw new ProjectionConfigurationException(tr("Unable to parse value of parameter ''towgs84'' (''{0}'')", numStr[i]));
213 }
214 }
215 boolean is3Param = true;
216 for (int i = 3; i<towgs84Param.size(); i++) {
217 if (towgs84Param.get(i) != 0.0) {
218 is3Param = false;
219 break;
220 }
221 }
222 Datum datum = null;
223 if (is3Param) {
224 datum = new ThreeParameterDatum(null, null, ellps,
225 towgs84Param.get(0),
226 towgs84Param.get(1),
227 towgs84Param.get(2)
228 );
229 } else {
230 datum = new SevenParameterDatum(null, null, ellps,
231 towgs84Param.get(0),
232 towgs84Param.get(1),
233 towgs84Param.get(2),
234 towgs84Param.get(3),
235 towgs84Param.get(4),
236 towgs84Param.get(5),
237 towgs84Param.get(6)
238 );
239 }
240 return datum;
241 }
242
243 public Proj parseProjection(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException {
244 String id = (String) parameters.get(Param.proj.key);
245 if (id == null) throw new ProjectionConfigurationException(tr("Projection required (+proj=*)"));
246
247 Proj proj = Projections.getBaseProjection(id);
248 if (proj == null) throw new ProjectionConfigurationException(tr("Unkown projection identifier: ''{0}''", id));
249
250 ProjParameters projParams = new ProjParameters();
251
252 projParams.ellps = ellps;
253
254 String s;
255 s = parameters.get(Param.lat_0.key);
256 if (s != null) {
257 projParams.lat_0 = parseAngle(s, Param.lat_0.key);
258 }
259 s = parameters.get(Param.lat_1.key);
260 if (s != null) {
261 projParams.lat_1 = parseAngle(s, Param.lat_1.key);
262 }
263 s = parameters.get(Param.lat_2.key);
264 if (s != null) {
265 projParams.lat_2 = parseAngle(s, Param.lat_2.key);
266 }
267 proj.initialize(projParams);
268 return proj;
269
270 }
271
272 public double parseDouble(Map<String, String> parameters, String parameterName) throws ProjectionConfigurationException {
273 String doubleStr = parameters.get(parameterName);
274 if (doubleStr == null && parameters.containsKey(parameterName))
275 throw new ProjectionConfigurationException(
276 tr("Expected number argument for parameter ''{0}''", parameterName));
277 return parseDouble(doubleStr, parameterName);
278 }
279
280 public double parseDouble(String doubleStr, String parameterName) throws ProjectionConfigurationException {
281 try {
282 return Double.parseDouble(doubleStr);
283 } catch (NumberFormatException e) {
284 throw new ProjectionConfigurationException(
285 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as number.", parameterName, doubleStr));
286 }
287 }
288
289 public double parseAngle(String angleStr, String parameterName) throws ProjectionConfigurationException {
290 String s = angleStr;
291 double value = 0;
292 boolean neg = false;
293 Matcher m = Pattern.compile("^-").matcher(s);
294 if (m.find()) {
295 neg = true;
296 s = s.substring(m.end());
297 }
298 final String FLOAT = "(\\d+(\\.\\d*)?)";
299 boolean dms = false;
300 // degrees
301 m = Pattern.compile("^"+FLOAT+"d").matcher(s);
302 if (m.find()) {
303 s = s.substring(m.end());
304 value += Double.parseDouble(m.group(1));
305 dms = true;
306 }
307 // minutes
308 m = Pattern.compile("^"+FLOAT+"'").matcher(s);
309 if (m.find()) {
310 s = s.substring(m.end());
311 value += Double.parseDouble(m.group(1)) / 60.0;
312 dms = true;
313 }
314 // seconds
315 m = Pattern.compile("^"+FLOAT+"\"").matcher(s);
316 if (m.find()) {
317 s = s.substring(m.end());
318 value += Double.parseDouble(m.group(1)) / 3600.0;
319 dms = true;
320 }
321 // plain number (in degrees)
322 if (!dms) {
323 m = Pattern.compile("^"+FLOAT).matcher(s);
324 if (m.find()) {
325 s = s.substring(m.end());
326 value += Double.parseDouble(m.group(1));
327 }
328 }
329 m = Pattern.compile("^(N|E)", Pattern.CASE_INSENSITIVE).matcher(s);
330 if (m.find()) {
331 s = s.substring(m.end());
332 } else {
333 m = Pattern.compile("^(S|W)", Pattern.CASE_INSENSITIVE).matcher(s);
334 if (m.find()) {
335 s = s.substring(m.end());
336 neg = !neg;
337 }
338 }
339 if (neg) {
340 value = -value;
341 }
342 if (!s.isEmpty()) {
343 throw new ProjectionConfigurationException(
344 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));
345 }
346 return value;
347 }
348
349 public void dump() {
350 System.err.println("x_0="+x_0);
351 System.err.println("y_0="+y_0);
352 System.err.println("lon_0="+lon_0);
353 System.err.println("k_0="+k_0);
354 System.err.println("ellps="+ellps);
355 System.err.println("proj="+proj);
356 System.err.println("datum="+datum);
357 }
358
359 @Override
360 public Integer getEpsgCode() {
361 return null;
362 }
363
364 @Override
365 public String toCode() {
366 return "proj:" + (pref == null ? "ERROR" : pref);
367 }
368
369 @Override
370 public String getCacheDirectoryName() {
371 return "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4);
372 }
373
374 @Override
375 public Bounds getWorldBoundsLatLon() {
376 return new Bounds(
377 new LatLon(-85.05112877980659, -180.0),
378 new LatLon(85.05112877980659, 180.0)); // FIXME
379 }
380
381 @Override
382 public String toString() {
383 return tr("Custom Projection");
384 }
385}
Note: See TracBrowser for help on using the repository browser.