Ticket #13408: patch-projection-custom-cleanup.patch
File patch-projection-custom-cleanup.patch, 18.9 KB (added by , 8 years ago) |
---|
-
src/org/openstreetmap/josm/data/projection/CustomProjection.java
diff --git a/src/org/openstreetmap/josm/data/projection/CustomProjection.java b/src/org/openstreetmap/josm/data/projection/CustomProjection.java index 6574b0f..fdd4d28 100644
a b package org.openstreetmap.josm.data.projection; 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.util.ArrayList; 7 import java.util.Arrays; 7 8 import java.util.EnumMap; 8 9 import java.util.HashMap; 9 10 import java.util.List; … … import org.openstreetmap.josm.data.projection.proj.Mercator; 31 32 import org.openstreetmap.josm.data.projection.proj.Proj; 32 33 import org.openstreetmap.josm.data.projection.proj.ProjParameters; 33 34 import org.openstreetmap.josm.tools.Utils; 35 import org.openstreetmap.josm.tools.bugreport.BugReport; 34 36 35 37 /** 36 38 * Custom projection. … … public class CustomProjection extends AbstractProjection { 63 65 private double metersPerUnitWMTS; 64 66 private String axis = "enu"; // default axis orientation is East, North, Up 65 67 68 private final static List<String> LON_LAT_VALUES = Arrays.asList("longlat", "latlon", "latlong"); 69 66 70 /** 67 71 * Proj4-like projection parameters. See <a href="https://trac.osgeo.org/proj/wiki/GenParms">reference</a>. 68 72 * @since 7370 (public) 69 73 */ 70 74 public enum Param { 71 72 75 /** False easting */ 73 76 x_0("x_0", true), 74 77 /** False northing */ … … public class CustomProjection extends AbstractProjection { 154 157 for (Param p : Param.values()) { 155 158 paramsByKey.put(p.key, p); 156 159 } 160 // alias 161 paramsByKey.put("k", Param.k_0); 157 162 } 158 163 159 164 Param(String key, boolean hasValue) { … … public class CustomProjection extends AbstractProjection { 162 167 } 163 168 } 164 169 165 private enum Polarity { NORTH, SOUTH } 170 private enum Polarity { 171 NORTH(LatLon.NORTH_POLE), 172 SOUTH(LatLon.SOUTH_POLE); 166 173 167 private EnumMap<Polarity, EastNorth> polesEN; 168 private EnumMap<Polarity, LatLon> polesLL; 169 { 170 polesLL = new EnumMap<>(Polarity.class); 171 polesLL.put(Polarity.NORTH, LatLon.NORTH_POLE); 172 polesLL.put(Polarity.SOUTH, LatLon.SOUTH_POLE); 174 private final LatLon latlon; 175 176 Polarity(LatLon latlon) { 177 this.latlon = latlon; 178 } 179 180 private LatLon getLatLon() { 181 return latlon; 182 } 173 183 } 174 184 185 private EnumMap<Polarity, EastNorth> polesEN; 186 175 187 /** 176 188 * Constructs a new empty {@code CustomProjection}. 177 189 */ … … public class CustomProjection extends AbstractProjection { 208 220 try { 209 221 update(null); 210 222 } catch (ProjectionConfigurationException ex1) { 211 throw new RuntimeException(ex1);223 throw BugReport.intercept(ex1).put("name", name).put("code", code).put("pref", pref); 212 224 } 213 225 } 214 226 } … … public class CustomProjection extends AbstractProjection { 239 251 // "utm" is a shortcut for a set of parameters 240 252 if ("utm".equals(parameters.get(Param.proj.key))) { 241 253 String zoneStr = parameters.get(Param.zone.key); 242 Integer zone;243 254 if (zoneStr == null) 244 255 throw new ProjectionConfigurationException(tr("UTM projection (''+proj=utm'') requires ''+zone=...'' parameter.")); 256 Integer zone; 245 257 try { 246 258 zone = Integer.valueOf(zoneStr); 247 259 } catch (NumberFormatException e) { … … public class CustomProjection extends AbstractProjection { 331 343 */ 332 344 public static Map<String, String> parseParameterList(String pref, boolean ignoreUnknownParameter) throws ProjectionConfigurationException { 333 345 Map<String, String> parameters = new HashMap<>(); 334 String[] parts = Utils.WHITE_SPACES_PATTERN.split(pref.trim());335 346 if (pref.trim().isEmpty()) { 336 parts = new String[0];347 return parameters; 337 348 } 349 350 Pattern keyPattern = Pattern.compile("\\+(?<key>[a-zA-Z0-9_]+)(=(?<value>.*))?"); 351 String[] parts = Utils.WHITE_SPACES_PATTERN.split(pref.trim()); 338 352 for (String part : parts) { 339 if (part.isEmpty() || part.charAt(0) != '+') 340 throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' character (found ''{0}'')", part)); 341 Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part); 353 Matcher m = keyPattern.matcher(part); 342 354 if (m.matches()) { 343 String key = m.group(1); 344 // alias 345 if ("k".equals(key)) { 346 key = Param.k_0.key; 347 } 348 String value = null; 349 if (m.groupCount() >= 3) { 350 value = m.group(3); 351 // some aliases 352 if (key.equals(Param.proj.key)) { 353 if ("longlat".equals(value) || "latlon".equals(value) || "latlong".equals(value)) { 354 value = "lonlat"; 355 } 356 } 355 String key = m.group("key"); 356 String value = m.group("value"); 357 // some aliases 358 if (key.equals(Param.proj.key) && LON_LAT_VALUES.contains(value)) { 359 value = "lonlat"; 357 360 } 358 if (!Param.paramsByKey.containsKey(key)) { 361 Param param = Param.paramsByKey.get(key); 362 if (param == null) { 359 363 if (!ignoreUnknownParameter) 360 364 throw new ProjectionConfigurationException(tr("Unknown parameter: ''{0}''.", key)); 361 365 } else { 362 if ( Param.paramsByKey.get(key).hasValue && value == null)366 if (param.hasValue && value == null) 363 367 throw new ProjectionConfigurationException(tr("Value expected for parameter ''{0}''.", key)); 364 if (! Param.paramsByKey.get(key).hasValue && value != null)368 if (!param.hasValue && value != null) 365 369 throw new ProjectionConfigurationException(tr("No value expected for parameter ''{0}''.", key)); 370 key = param.key; // To be really sure, we might have an alias. 366 371 } 367 372 parameters.put(key, value); 368 } else 373 } else if (!part.startsWith("+")) { 374 throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' character (found ''{0}'')", part)); 375 } else { 369 376 throw new ProjectionConfigurationException(tr("Unexpected parameter format (''{0}'')", part)); 377 } 370 378 } 371 379 return parameters; 372 380 } … … public class CustomProjection extends AbstractProjection { 400 408 return parameters; 401 409 } 402 410 411 /** 412 * Gets the ellipsoid 413 * @param parameters The parameters to get the value from 414 * @return The Ellipsoid as specified with the parameters 415 * @throws ProjectionConfigurationException 416 */ 403 417 public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException { 404 418 String code = parameters.get(Param.ellps.key); 405 419 if (code != null) { … … public class CustomProjection extends AbstractProjection { 439 453 return null; 440 454 } 441 455 456 /** 457 * Gets the datum 458 * @param parameters The parameters to get the value from 459 * @param ellps The ellisoid that was previously computed 460 * @return The Datum as specified with the parameters 461 * @throws ProjectionConfigurationException 462 */ 442 463 public Datum parseDatum(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException { 443 464 String datumId = parameters.get(Param.datum.key); 444 465 if (datumId != null) { … … public class CustomProjection extends AbstractProjection { 518 539 towgs84Param.get(6)); 519 540 } 520 541 542 /** 543 * Gets a projection using the given ellipsoid 544 * @param parameters Additional parameters 545 * @param ellps The {@link Ellipsoid} 546 * @return The projection 547 * @throws ProjectionConfigurationException 548 */ 521 549 public Proj parseProjection(Map<String, String> parameters, Ellipsoid ellps) throws ProjectionConfigurationException { 522 550 String id = parameters.get(Param.proj.key); 523 551 if (id == null) throw new ProjectionConfigurationException(tr("Projection required (+proj=*)")); … … public class CustomProjection extends AbstractProjection { 577 605 return proj; 578 606 } 579 607 608 /** 609 * Converts a string to a bounds object 610 * @param boundsStr The string as comma separated list of angles. 611 * @return The bounds. 612 * @throws ProjectionConfigurationException 613 * @see {@link CustomProjection#parseAngle(String, String)} 614 */ 580 615 public static Bounds parseBounds(String boundsStr) throws ProjectionConfigurationException { 581 616 String[] numStr = boundsStr.split(","); 582 617 if (numStr.length != 4) … … public class CustomProjection extends AbstractProjection { 606 641 } 607 642 } 608 643 644 /** 645 * Convert an angle string to a double value 646 * @param angleStr The string. e.g. -1.1 or 50d 10' 3" 647 * @param parameterName Only for error message. 648 * @return The angle value, in degrees. 649 * @throws ProjectionConfigurationException 650 */ 609 651 public static double parseAngle(String angleStr, String parameterName) throws ProjectionConfigurationException { 610 String s = angleStr; 652 final String floatPattern = "(\\d+(\\.\\d*)?)"; 653 // pattern does all error handling. 654 Matcher in = Pattern.compile("^(?<neg1>-)?" 655 + "(?=\\d)(?:(?<single>" + floatPattern + ")|" 656 + "((?<degree>" + floatPattern + ")d)?" 657 + "((?<minutes>" + floatPattern + ")\')?" 658 + "((?<seconds>" + floatPattern + ")\")?)" 659 + "(?:[NE]|(?<neg2>[SW]))?$").matcher(angleStr); 660 661 if (!in.find()) { 662 throw new ProjectionConfigurationException( 663 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr)); 664 } 665 611 666 double value = 0; 612 boolean neg = false; 613 Matcher m = Pattern.compile("^-").matcher(s); 614 if (m.find()) { 615 neg = true; 616 s = s.substring(m.end()); 667 if (in.group("single") != null) { 668 value += Double.parseDouble(in.group("single")); 617 669 } 618 final String floatPattern = "(\\d+(\\.\\d*)?)"; 619 boolean dms = false; 620 double deg = 0.0, min = 0.0, sec = 0.0; 621 // degrees 622 m = Pattern.compile("^"+floatPattern+"d").matcher(s); 623 if (m.find()) { 624 s = s.substring(m.end()); 625 deg = Double.parseDouble(m.group(1)); 626 dms = true; 627 } 628 // minutes 629 m = Pattern.compile("^"+floatPattern+"'").matcher(s); 630 if (m.find()) { 631 s = s.substring(m.end()); 632 min = Double.parseDouble(m.group(1)); 633 dms = true; 634 } 635 // seconds 636 m = Pattern.compile("^"+floatPattern+"\"").matcher(s); 637 if (m.find()) { 638 s = s.substring(m.end()); 639 sec = Double.parseDouble(m.group(1)); 640 dms = true; 641 } 642 // plain number (in degrees) 643 if (dms) { 644 value = deg + (min/60.0) + (sec/3600.0); 645 } else { 646 m = Pattern.compile("^"+floatPattern).matcher(s); 647 if (m.find()) { 648 s = s.substring(m.end()); 649 value += Double.parseDouble(m.group(1)); 650 } 670 if (in.group("degree") != null) { 671 value += Double.parseDouble(in.group("degree")); 651 672 } 652 m = Pattern.compile("^(N|E)", Pattern.CASE_INSENSITIVE).matcher(s); 653 if (m.find()) { 654 s = s.substring(m.end()); 655 } else { 656 m = Pattern.compile("^(S|W)", Pattern.CASE_INSENSITIVE).matcher(s); 657 if (m.find()) { 658 s = s.substring(m.end()); 659 neg = !neg; 660 } 673 if (in.group("minutes") != null) { 674 value += Double.parseDouble(in.group("minutes")) / 60; 661 675 } 662 if ( neg) {663 value = -value;676 if (in.group("seconds") != null) { 677 value += Double.parseDouble(in.group("seconds")) / 3600; 664 678 } 665 if (!s.isEmpty()) { 666 throw new ProjectionConfigurationException(667 tr("Unable to parse value ''{1}'' of parameter ''{0}'' as coordinate value.", parameterName, angleStr));679 680 if (in.group("neg1") != null ^ in.group("neg2") != null) { 681 value = -value; 668 682 } 669 683 return value; 670 684 } … … public class CustomProjection extends AbstractProjection { 683 697 684 698 @Override 685 699 public String toCode() { 686 return code != null ? code : "proj:" + (pref == null ? "ERROR" : pref); 700 if (code != null) { 701 return code; 702 } else if (pref != null) { 703 return "proj:" + pref; 704 } else { 705 return "proj:ERROR"; 706 } 687 707 } 688 708 689 709 @Override 690 710 public String getCacheDirectoryName() { 691 return cacheDir != null ? cacheDir : "proj-"+Utils.md5Hex(pref == null ? "" : pref).substring(0, 4); 711 if (cacheDir != null) { 712 return cacheDir; 713 } else { 714 return "proj-" + Utils.md5Hex(pref == null ? "" : pref).substring(0, 4); 715 } 692 716 } 693 717 694 718 @Override 695 719 public Bounds getWorldBoundsLatLon() { 696 if (bounds != null) return bounds; 697 Bounds ab = proj.getAlgorithmBounds(); 698 if (ab != null) { 699 double minlon = Math.max(ab.getMinLon() + lon0 + pm, -180); 700 double maxlon = Math.min(ab.getMaxLon() + lon0 + pm, 180); 701 return new Bounds(ab.getMinLat(), minlon, ab.getMaxLat(), maxlon, false); 702 } else { 703 return new Bounds( 704 new LatLon(-90.0, -180.0), 705 new LatLon(90.0, 180.0)); 720 if (bounds == null) { 721 Bounds ab = proj.getAlgorithmBounds(); 722 if (ab != null) { 723 double minlon = Math.max(ab.getMinLon() + lon0 + pm, -180); 724 double maxlon = Math.min(ab.getMaxLon() + lon0 + pm, 180); 725 bounds = new Bounds(ab.getMinLat(), minlon, ab.getMaxLat(), maxlon, false); 726 } else { 727 bounds = new Bounds( 728 new LatLon(-90.0, -180.0), 729 new LatLon(90.0, 180.0)); 730 } 706 731 } 732 return bounds; 707 733 } 708 734 709 735 @Override … … public class CustomProjection extends AbstractProjection { 804 830 polesEN = new EnumMap<>(Polarity.class); 805 831 for (Polarity p : Polarity.values()) { 806 832 polesEN.put(p, null); 807 LatLon ll = p olesLL.get(p);833 LatLon ll = p.getLatLon(); 808 834 try { 809 835 EastNorth enPole = latlon2eastNorth(ll); 810 836 if (enPole.isValid()) { … … public class CustomProjection extends AbstractProjection { 852 878 for (Polarity p : Polarity.values()) { 853 879 EastNorth pole = getPole(p); 854 880 if (pole != null && r.contains(pole)) { 855 result.extend(p olesLL.get(p));881 result.extend(p.getLatLon()); 856 882 } 857 883 } 858 884 return result; -
new file test/unit/org/openstreetmap/josm/data/projection/CustomProjectionTest.java
diff --git a/test/unit/org/openstreetmap/josm/data/projection/CustomProjectionTest.java b/test/unit/org/openstreetmap/josm/data/projection/CustomProjectionTest.java new file mode 100644 index 0000000..3b2ae69
- + 1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.data.projection; 3 4 import static org.junit.Assert.assertEquals; 5 import static org.junit.Assert.assertTrue; 6 import static org.junit.Assert.fail; 7 8 import java.util.stream.Stream; 9 10 import org.junit.Rule; 11 import org.junit.Test; 12 import org.openstreetmap.josm.testutils.JOSMTestRules; 13 14 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 15 16 /** 17 * Tests for {@link CustomProjection} 18 * @author Michael Zangl 19 * @since xxx 20 */ 21 public class CustomProjectionTest { 22 /** 23 * Need pref to load pref. 24 */ 25 @Rule 26 @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD") 27 public JOSMTestRules test = new JOSMTestRules().preferences(); 28 29 /** 30 * Test {@link CustomProjection#parseAngle(String, String)} 31 * @throws ProjectionConfigurationException 32 */ 33 @Test 34 public void testParseAngle() throws ProjectionConfigurationException { 35 assertEquals(0, CustomProjection.parseAngle("0", "xxx"), 1e-10); 36 assertEquals(1, CustomProjection.parseAngle("1", "xxx"), 1e-10); 37 assertEquals(1.1, CustomProjection.parseAngle("1.1", "xxx"), 1e-10); 38 39 assertEquals(1, CustomProjection.parseAngle("1d", "xxx"), 1e-10); 40 assertEquals(1.1, CustomProjection.parseAngle("1.1d", "xxx"), 1e-10); 41 42 assertEquals(1 / 60.0, CustomProjection.parseAngle("1'", "xxx"), 1e-10); 43 assertEquals(1.1 / 60.0, CustomProjection.parseAngle("1.1'", "xxx"), 1e-10); 44 45 assertEquals(1 / 3600.0, CustomProjection.parseAngle("1\"", "xxx"), 1e-10); 46 assertEquals(1.1 / 3600.0, CustomProjection.parseAngle("1.1\"", "xxx"), 1e-10); 47 48 // negate 49 assertEquals(-1.1, CustomProjection.parseAngle("-1.1", "xxx"), 1e-10); 50 assertEquals(1.1, CustomProjection.parseAngle("1.1N", "xxx"), 1e-10); 51 assertEquals(1.1, CustomProjection.parseAngle("1.1E", "xxx"), 1e-10); 52 assertEquals(-1.1, CustomProjection.parseAngle("1.1S", "xxx"), 1e-10); 53 assertEquals(-1.1, CustomProjection.parseAngle("1.1W", "xxx"), 1e-10); 54 assertEquals(-1.1, CustomProjection.parseAngle("-1.1N", "xxx"), 1e-10); 55 assertEquals(-1.1, CustomProjection.parseAngle("-1.1E", "xxx"), 1e-10); 56 assertEquals(1.1, CustomProjection.parseAngle("-1.1S", "xxx"), 1e-10); 57 assertEquals(1.1, CustomProjection.parseAngle("-1.1W", "xxx"), 1e-10); 58 59 // combine 60 assertEquals(1.1 + 3 / 60.0 + 5.2 / 3600.0, CustomProjection.parseAngle("1.1d3'5.2\"", "xxx"), 1e-10); 61 62 // fail 63 Stream.of("", "-", "-N", "N", "1.1 ", "x", "1.1d1.1d", "1.1e","1.1.1",".1", "1.1d3\"5.2'").forEach( 64 s -> { 65 try { 66 CustomProjection.parseAngle(s, "xxxx"); 67 fail("Expected exception for " + s); 68 } catch (ProjectionConfigurationException e) { 69 // good! 70 assertTrue(e.getMessage().contains("xxx")); 71 } 72 }); 73 } 74 }