gt:                 return Ellipsoid.create_a_rf(a, rf);
context: // License: GPL. For details, see LICENSE file.
  package org.openstreetmap.josm.data.projection;
  
  import static org.openstreetmap.josm.tools.I18n.tr;
  
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  import org.openstreetmap.josm.Main;
  import org.openstreetmap.josm.data.Bounds;
  import org.openstreetmap.josm.data.coor.LatLon;
  import org.openstreetmap.josm.data.projection.datum.CentricDatum;
  import org.openstreetmap.josm.data.projection.datum.Datum;
  import org.openstreetmap.josm.data.projection.datum.NTV2Datum;
  import org.openstreetmap.josm.data.projection.datum.NTV2GridShiftFileWrapper;
  import org.openstreetmap.josm.data.projection.datum.NullDatum;
  import org.openstreetmap.josm.data.projection.datum.SevenParameterDatum;
  import org.openstreetmap.josm.data.projection.datum.ThreeParameterDatum;
  import org.openstreetmap.josm.data.projection.datum.WGS84Datum;
  import org.openstreetmap.josm.data.projection.proj.Mercator;
  import org.openstreetmap.josm.data.projection.proj.Proj;
  import org.openstreetmap.josm.data.projection.proj.ProjParameters;
  import org.openstreetmap.josm.tools.Utils;
  
  /**
   * Custom projection
   *
   * Inspired by PROJ.4 and Proj4J.
   */
  public class CustomProjection extends AbstractProjection {
  
      /**
       * pref String that defines the projection
       *
       * null means fall back mode (Mercator)
       */
      protected String pref;
      protected String name;
      protected String code;
      protected String cacheDir;
      protected Bounds bounds;
  
      protected static enum Param {
  
          x_0("x_0", true),
          y_0("y_0", true),
          lon_0("lon_0", true),
          k_0("k_0", true),
          ellps("ellps", true),
          a("a", true),
          es("es", true),
          rf("rf", true),
          f("f", true),
          b("b", true),
          datum("datum", true),
          towgs84("towgs84", true),
          nadgrids("nadgrids", true),
          proj("proj", true),
          lat_0("lat_0", true),
          lat_1("lat_1", true),
          lat_2("lat_2", true),
          wktext("wktext", false),  // ignored
          units("units", true),     // ignored
          no_defs("no_defs", false),
          init("init", true),
          // JOSM extension, not present in PROJ.4
          bounds("bounds", true);
  
          public String key;
          public boolean hasValue;
  
          public final static Map<String, Param> paramsByKey = new HashMap<String, Param>();
          static {
              for (Param p : Param.values()) {
                  paramsByKey.put(p.key, p);
              }
          }
  
          Param(String key, boolean hasValue) {
              this.key = key;
              this.hasValue = hasValue;
          }
      }
  
      public CustomProjection() {
      }
  
      public CustomProjection(String pref) {
          this(null, null, pref, null);
      }
  
      /**
       * Constructor.
       *
       * @param name describe projection in one or two words
       * @param code unique code for this projection - may be null
       * @param pref the string that defines the custom projection
       * @param cacheDir cache directory name
       */
      public CustomProjection(String name, String code, String pref, String cacheDir) {
          this.name = name;
          this.code = code;
          this.pref = pref;
          this.cacheDir = cacheDir;
          try {
              update(pref);
          } catch (ProjectionConfigurationException ex) {
              try {
                  update(null);
              } catch (ProjectionConfigurationException ex1) {
                  throw new RuntimeException();
              }
          }
      }
  
      public void update(String pref) throws ProjectionConfigurationException {
          this.pref = pref;
          if (pref == null) {
              ellps = Ellipsoid.WGS84;
              datum = WGS84Datum.INSTANCE;
              proj = new Mercator();
              bounds = new Bounds(
                      -85.05112877980659, -180.0,
                      85.05112877980659, 180.0, true);
          } else {
              Map<String, String> parameters = parseParameterList(pref);
              ellps = parseEllipsoid(parameters);
              datum = parseDatum(parameters, ellps);
              proj = parseProjection(parameters, ellps);
              String s = parameters.get(Param.x_0.key);
              if (s != null) {
                  this.x_0 = parseDouble(s, Param.x_0.key);
              }
              s = parameters.get(Param.y_0.key);
              if (s != null) {
                  this.y_0 = parseDouble(s, Param.y_0.key);
              }
              s = parameters.get(Param.lon_0.key);
              if (s != null) {
                  this.lon_0 = parseAngle(s, Param.lon_0.key);
              }
              s = parameters.get(Param.k_0.key);
              if (s != null) {
                  this.k_0 = parseDouble(s, Param.k_0.key);
              }
              s = parameters.get(Param.bounds.key);
              if (s != null) {
                  this.bounds = parseBounds(s);
              }
          }
      }
  
      private Map<String, String> parseParameterList(String pref) throws ProjectionConfigurationException {
          Map<String, String> parameters = new HashMap<String, String>();
          String[] parts = pref.trim().split("\\s+");
          if (pref.trim().isEmpty()) {
              parts = new String[0];
          }
          for (String part : parts) {
              if (part.isEmpty() || part.charAt(0) != '+')
                  throw new ProjectionConfigurationException(tr("Parameter must begin with a ''+'' character (found ''{0}'')", part));
              Matcher m = Pattern.compile("\\+([a-zA-Z0-9_]+)(=(.*))?").matcher(part);
              if (m.matches()) {
                  String key = m.group(1);
                  // alias
                  if (key.equals("k")) {
                      key = Param.k_0.key;
                  }
                  String value = null;
                  if (m.groupCount() >= 3) {
                      value = m.group(3);
                      // same aliases
                      if (key.equals(Param.proj.key)) {
                          if (value.equals("longlat") || value.equals("latlon") || value.equals("latlong")) {
                              value = "lonlat";
                          }
                      }
                  }
                  if (!Param.paramsByKey.containsKey(key))
                      throw new ProjectionConfigurationException(tr("Unkown parameter: ''{0}''.", key));
                  if (Param.paramsByKey.get(key).hasValue && value == null)
                      throw new ProjectionConfigurationException(tr("Value expected for parameter ''{0}''.", key));
                  if (!Param.paramsByKey.get(key).hasValue && value != null)
                      throw new ProjectionConfigurationException(tr("No value expected for parameter ''{0}''.", key));
                  parameters.put(key, value);
              } else
                  throw new ProjectionConfigurationException(tr("Unexpected parameter format (''{0}'')", part));
          }
          // recursive resolution of +init includes
          String initKey = parameters.get(Param.init.key);
          if (initKey != null) {
              String init = Projections.getInit(initKey);
              if (init == null)
                  throw new ProjectionConfigurationException(tr("Value ''{0}'' for option +init not supported.", initKey));
              Map<String, String> initp = null;
              try {
                  initp = parseParameterList(init);
              } catch (ProjectionConfigurationException ex) {
                  throw new ProjectionConfigurationException(tr(initKey+": "+ex.getMessage()));
              }
              for (Map.Entry<String, String> e : parameters.entrySet()) {
                  initp.put(e.getKey(), e.getValue());
              }
              return initp;
          }
          return parameters;
      }
  
      public Ellipsoid parseEllipsoid(Map<String, String> parameters) throws ProjectionConfigurationException {
          String code = parameters.get(Param.ellps.key);
          if (code != null) {
              Ellipsoid ellipsoid = Projections.getEllipsoid(code);
              if (ellipsoid == null) {
                  throw new ProjectionConfigurationException(tr("Ellipsoid ''{0}'' not supported.", code));
              } else {
                  return ellipsoid;
              }
          }
          String s = parameters.get(Param.a.key);
          if (s != null) {
              double a = parseDouble(s, Param.a.key);
              if (parameters.get(Param.es.key) != null) {
                  double es = parseDouble(parameters, Param.es.key);
                  return Ellipsoid.create_a_es(a, es);
              }
              if (parameters.get(Param.rf.key) != null) {
                  double rf = parseDouble(parameters, Param.rf.key);
  