| 1 | // License: GPL. For details, see LICENSE file.
|
|---|
| 2 | package org.openstreetmap.josm.data.projection;
|
|---|
| 3 |
|
|---|
| 4 | import static org.openstreetmap.josm.tools.I18n.tr;
|
|---|
| 5 |
|
|---|
| 6 | import java.io.BufferedReader;
|
|---|
| 7 | import java.io.IOException;
|
|---|
| 8 | import java.io.InputStreamReader;
|
|---|
| 9 | import java.nio.charset.Charset;
|
|---|
| 10 | import java.nio.charset.StandardCharsets;
|
|---|
| 11 | import java.nio.file.Files;
|
|---|
| 12 | import java.nio.file.Paths;
|
|---|
| 13 | import java.util.ArrayList;
|
|---|
| 14 | import java.util.Arrays;
|
|---|
| 15 | import java.util.List;
|
|---|
| 16 | import java.util.function.ToDoubleFunction;
|
|---|
| 17 |
|
|---|
| 18 | import org.openstreetmap.josm.cli.CLIModule;
|
|---|
| 19 | import org.openstreetmap.josm.data.coor.EastNorth;
|
|---|
| 20 | import org.openstreetmap.josm.data.coor.LatLon;
|
|---|
| 21 | import org.openstreetmap.josm.data.coor.conversion.LatLonParser;
|
|---|
| 22 | import org.openstreetmap.josm.spi.lifecycle.Lifecycle;
|
|---|
| 23 | import org.openstreetmap.josm.tools.OptionParser;
|
|---|
| 24 |
|
|---|
| 25 | /**
|
|---|
| 26 | * Command line interface for projecting coordinates.
|
|---|
| 27 | * @since 12792
|
|---|
| 28 | */
|
|---|
| 29 | public class ProjectionCLI implements CLIModule {
|
|---|
| 30 |
|
|---|
| 31 | /** The unique instance **/
|
|---|
| 32 | public static final ProjectionCLI INSTANCE = new ProjectionCLI();
|
|---|
| 33 |
|
|---|
| 34 | private boolean argInverse;
|
|---|
| 35 | private boolean argSwitchInput;
|
|---|
| 36 | private boolean argSwitchOutput;
|
|---|
| 37 |
|
|---|
| 38 | @Override
|
|---|
| 39 | public String getActionKeyword() {
|
|---|
| 40 | return "project";
|
|---|
| 41 | }
|
|---|
| 42 |
|
|---|
| 43 | @Override
|
|---|
| 44 | public void processArguments(String[] argArray) {
|
|---|
| 45 | List<String> positionalArguments = new OptionParser("JOSM projection")
|
|---|
| 46 | .addFlagParameter("help", ProjectionCLI::showHelp)
|
|---|
| 47 | .addShortAlias("help", "h")
|
|---|
| 48 | .addFlagParameter("inverse", () -> argInverse = true)
|
|---|
| 49 | .addShortAlias("inverse", "I")
|
|---|
| 50 | .addFlagParameter("switch-input", () -> argSwitchInput = true)
|
|---|
| 51 | .addShortAlias("switch-input", "r")
|
|---|
| 52 | .addFlagParameter("switch-output", () -> argSwitchOutput = true)
|
|---|
| 53 | .addShortAlias("switch-output", "s")
|
|---|
| 54 | .parseOptionsOrExit(Arrays.asList(argArray));
|
|---|
| 55 |
|
|---|
| 56 | List<String> projParamFrom = new ArrayList<>();
|
|---|
| 57 | List<String> projParamTo = new ArrayList<>();
|
|---|
| 58 | List<String> otherPositional = new ArrayList<>();
|
|---|
| 59 | boolean toTokenSeen = false;
|
|---|
| 60 | // positional arguments:
|
|---|
| 61 | for (String arg: positionalArguments) {
|
|---|
| 62 | if (arg.isEmpty()) throw new IllegalArgumentException("non-empty argument expected");
|
|---|
| 63 | if (arg.startsWith("+")) {
|
|---|
| 64 | if ("+to".equals(arg)) {
|
|---|
| 65 | toTokenSeen = true;
|
|---|
| 66 | } else {
|
|---|
| 67 | (toTokenSeen ? projParamTo : projParamFrom).add(arg);
|
|---|
| 68 | }
|
|---|
| 69 | } else {
|
|---|
| 70 | otherPositional.add(arg);
|
|---|
| 71 | }
|
|---|
| 72 | }
|
|---|
| 73 | String fromStr = String.join(" ", projParamFrom);
|
|---|
| 74 | String toStr = String.join(" ", projParamTo);
|
|---|
| 75 | try {
|
|---|
| 76 | run(fromStr, toStr, otherPositional);
|
|---|
| 77 | } catch (ProjectionConfigurationException | IllegalArgumentException | IOException ex) {
|
|---|
| 78 | System.err.println(tr("Error: {0}", ex.getMessage()));
|
|---|
| 79 | Lifecycle.exitJosm(true, 1);
|
|---|
| 80 | }
|
|---|
| 81 | Lifecycle.exitJosm(true, 0);
|
|---|
| 82 | }
|
|---|
| 83 |
|
|---|
| 84 | /**
|
|---|
| 85 | * Displays help on the console
|
|---|
| 86 | */
|
|---|
| 87 | private static void showHelp() {
|
|---|
| 88 | System.out.println(getHelp());
|
|---|
| 89 | Lifecycle.exitJosm(true, 0);
|
|---|
| 90 | }
|
|---|
| 91 |
|
|---|
| 92 | private static String getHelp() {
|
|---|
| 93 | return tr("JOSM projection command line interface")+"\n\n"+
|
|---|
| 94 | tr("Usage")+":\n"+
|
|---|
| 95 | "\tjava -jar josm.jar project <options> <crs> +to <crs> [file]\n\n"+
|
|---|
| 96 | tr("Description")+":\n"+
|
|---|
| 97 | tr("Converts coordinates from one coordinate reference system to another.")+"\n\n"+
|
|---|
| 98 | tr("Options")+":\n"+
|
|---|
| 99 | "\t--help|-h "+tr("Show this help")+"\n"+
|
|---|
| 100 | "\t-I "+tr("Switch input and output crs")+"\n"+
|
|---|
| 101 | "\t-r "+tr("Switch order of input coordinates (east/north, lon/lat)")+"\n"+
|
|---|
| 102 | "\t-s "+tr("Switch order of output coordinates (east/north, lon/lat)")+"\n\n"+
|
|---|
| 103 | tr("<crs>")+":\n"+
|
|---|
| 104 | tr("The format for input and output coordinate reference system"
|
|---|
| 105 | + " is similar to that of the PROJ.4 software.")+"\n\n"+
|
|---|
| 106 | tr("[file]")+":\n"+
|
|---|
| 107 | tr("Reads input data from one or more files listed as positional arguments. "
|
|---|
| 108 | + "When no files are given, or the filename is \"-\", data is read from "
|
|---|
| 109 | + "standard input.")+"\n\n"+
|
|---|
| 110 | tr("Examples")+":\n"+
|
|---|
| 111 | " java -jar josm.jar project +init=epsg:4326 +to +init=epsg:3857 <<<\"11.232274 50.5685716\"\n"+
|
|---|
| 112 | " => 1250371.1334500168 6545331.055189664\n\n"+
|
|---|
| 113 | " java -jar josm.jar project +proj=lonlat +datum=WGS84 +to +proj=merc +a=6378137 +b=6378137 +nadgrids=@null <<EOF\n" +
|
|---|
| 114 | " 11d13'56.19\"E 50d34'6.86\"N\n" +
|
|---|
| 115 | " 118d39'30.42\"W 37d20'18.76\"N\n"+
|
|---|
| 116 | " EOF\n"+
|
|---|
| 117 | " => 1250371.1334500168 6545331.055189664\n" +
|
|---|
| 118 | " -1.3208998232319113E7 4486401.160664663\n";
|
|---|
| 119 | }
|
|---|
| 120 |
|
|---|
| 121 | private void run(String fromStr, String toStr, List<String> files) throws ProjectionConfigurationException, IOException {
|
|---|
| 122 | CustomProjection fromProj = createProjection(fromStr);
|
|---|
| 123 | CustomProjection toProj = createProjection(toStr);
|
|---|
| 124 | if (this.argInverse) {
|
|---|
| 125 | CustomProjection tmp = fromProj;
|
|---|
| 126 | fromProj = toProj;
|
|---|
| 127 | toProj = tmp;
|
|---|
| 128 | }
|
|---|
| 129 |
|
|---|
| 130 | if (files.isEmpty() || "-".equals(files.get(0))) {
|
|---|
| 131 | processInput(fromProj, toProj, new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset())));
|
|---|
| 132 | } else {
|
|---|
| 133 | for (String file : files) {
|
|---|
| 134 | try (BufferedReader br = Files.newBufferedReader(Paths.get(file), StandardCharsets.UTF_8)) {
|
|---|
| 135 | processInput(fromProj, toProj, br);
|
|---|
| 136 | }
|
|---|
| 137 | }
|
|---|
| 138 | }
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | private void processInput(CustomProjection fromProj, CustomProjection toProj, BufferedReader reader) throws IOException {
|
|---|
| 142 | String line;
|
|---|
| 143 | while ((line = reader.readLine()) != null) {
|
|---|
| 144 | line = line.trim();
|
|---|
| 145 | if (line.isEmpty() || line.startsWith("#"))
|
|---|
| 146 | continue;
|
|---|
| 147 | EastNorth enIn;
|
|---|
| 148 | if (fromProj.isGeographic()) {
|
|---|
| 149 | enIn = parseEastNorth(line, LatLonParser::parseCoordinate);
|
|---|
| 150 | } else {
|
|---|
| 151 | enIn = parseEastNorth(line, ProjectionCLI::parseDouble);
|
|---|
| 152 | }
|
|---|
| 153 | LatLon ll = fromProj.eastNorth2latlon(enIn);
|
|---|
| 154 | EastNorth enOut = toProj.latlon2eastNorth(ll);
|
|---|
| 155 | double cOut1 = argSwitchOutput ? enOut.north() : enOut.east();
|
|---|
| 156 | double cOut2 = argSwitchOutput ? enOut.east() : enOut.north();
|
|---|
| 157 | System.out.println(Double.toString(cOut1) + " " + Double.toString(cOut2));
|
|---|
| 158 | System.out.flush();
|
|---|
| 159 | }
|
|---|
| 160 | }
|
|---|
| 161 |
|
|---|
| 162 | private static CustomProjection createProjection(String params) throws ProjectionConfigurationException {
|
|---|
| 163 | CustomProjection proj = new CustomProjection();
|
|---|
| 164 | proj.update(params);
|
|---|
| 165 | return proj;
|
|---|
| 166 | }
|
|---|
| 167 |
|
|---|
| 168 | private EastNorth parseEastNorth(String s, ToDoubleFunction<String> parser) {
|
|---|
| 169 | String[] en = s.split("[;, ]+", -1);
|
|---|
| 170 | if (en.length != 2)
|
|---|
| 171 | throw new IllegalArgumentException(tr("Expected two coordinates, separated by white space, found {0} in ''{1}''", en.length, s));
|
|---|
| 172 | double east = parser.applyAsDouble(en[0]);
|
|---|
| 173 | double north = parser.applyAsDouble(en[1]);
|
|---|
| 174 | if (this.argSwitchInput)
|
|---|
| 175 | return new EastNorth(north, east);
|
|---|
| 176 | else
|
|---|
| 177 | return new EastNorth(east, north);
|
|---|
| 178 | }
|
|---|
| 179 |
|
|---|
| 180 | private static double parseDouble(String s) {
|
|---|
| 181 | try {
|
|---|
| 182 | return Double.parseDouble(s);
|
|---|
| 183 | } catch (NumberFormatException nfe) {
|
|---|
| 184 | throw new IllegalArgumentException(tr("Unable to parse number ''{0}''", s), nfe);
|
|---|
| 185 | }
|
|---|
| 186 | }
|
|---|
| 187 |
|
|---|
| 188 | /**
|
|---|
| 189 | * Main class to run just the projection CLI.
|
|---|
| 190 | * @param args command line arguments
|
|---|
| 191 | */
|
|---|
| 192 | public static void main(String[] args) {
|
|---|
| 193 | ProjectionCLI.INSTANCE.processArguments(args);
|
|---|
| 194 | }
|
|---|
| 195 | }
|
|---|