001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.data.projection; 003 004import static org.openstreetmap.josm.tools.I18n.tr; 005 006import java.io.BufferedReader; 007import java.io.IOException; 008import java.io.InputStreamReader; 009import java.nio.charset.Charset; 010import java.nio.charset.StandardCharsets; 011import java.nio.file.Files; 012import java.nio.file.Paths; 013import java.util.ArrayList; 014import java.util.Arrays; 015import java.util.List; 016import java.util.function.ToDoubleFunction; 017 018import org.openstreetmap.josm.cli.CLIModule; 019import org.openstreetmap.josm.data.coor.EastNorth; 020import org.openstreetmap.josm.data.coor.LatLon; 021import org.openstreetmap.josm.data.coor.conversion.LatLonParser; 022import org.openstreetmap.josm.tools.OptionParser; 023 024/** 025 * Command line interface for projecting coordinates. 026 * @since 12792 027 */ 028public class ProjectionCLI implements CLIModule { 029 030 /** The unique instance **/ 031 public static final ProjectionCLI INSTANCE = new ProjectionCLI(); 032 033 private boolean argInverse; 034 private boolean argSwitchInput; 035 private boolean argSwitchOutput; 036 037 @Override 038 public String getActionKeyword() { 039 return "project"; 040 } 041 042 @Override 043 public void processArguments(String[] argArray) { 044 List<String> positionalArguments = new OptionParser("JOSM projection") 045 .addFlagParameter("help", ProjectionCLI::showHelp) 046 .addShortAlias("help", "h") 047 .addFlagParameter("inverse", () -> argInverse = true) 048 .addShortAlias("inverse", "I") 049 .addFlagParameter("switch-input", () -> argSwitchInput = true) 050 .addShortAlias("switch-input", "r") 051 .addFlagParameter("switch-output", () -> argSwitchOutput = true) 052 .addShortAlias("switch-output", "s") 053 .parseOptionsOrExit(Arrays.asList(argArray)); 054 055 List<String> projParamFrom = new ArrayList<>(); 056 List<String> projParamTo = new ArrayList<>(); 057 List<String> otherPositional = new ArrayList<>(); 058 boolean toTokenSeen = false; 059 // positional arguments: 060 for (String arg: positionalArguments) { 061 if (arg.isEmpty()) throw new IllegalArgumentException("non-empty argument expected"); 062 if (arg.startsWith("+")) { 063 if ("+to".equals(arg)) { 064 toTokenSeen = true; 065 } else { 066 (toTokenSeen ? projParamTo : projParamFrom).add(arg); 067 } 068 } else { 069 otherPositional.add(arg); 070 } 071 } 072 String fromStr = String.join(" ", projParamFrom); 073 String toStr = String.join(" ", projParamTo); 074 try { 075 run(fromStr, toStr, otherPositional); 076 } catch (ProjectionConfigurationException | IllegalArgumentException | IOException ex) { 077 System.err.println(tr("Error: {0}", ex.getMessage())); 078 System.exit(1); 079 } 080 System.exit(0); 081 } 082 083 /** 084 * Displays help on the console 085 */ 086 private static void showHelp() { 087 System.out.println(getHelp()); 088 System.exit(0); 089 } 090 091 private static String getHelp() { 092 return tr("JOSM projection command line interface")+"\n\n"+ 093 tr("Usage")+":\n"+ 094 "\tjava -jar josm.jar project <options> <crs> +to <crs> [file]\n\n"+ 095 tr("Description")+":\n"+ 096 tr("Converts coordinates from one coordinate reference system to another.")+"\n\n"+ 097 tr("Options")+":\n"+ 098 "\t--help|-h "+tr("Show this help")+"\n"+ 099 "\t-I "+tr("Switch input and output crs")+"\n"+ 100 "\t-r "+tr("Switch order of input coordinates (east/north, lon/lat)")+"\n"+ 101 "\t-s "+tr("Switch order of output coordinates (east/north, lon/lat)")+"\n\n"+ 102 tr("<crs>")+":\n"+ 103 tr("The format for input and output coordinate reference system" 104 + " is similar to that of the PROJ.4 software.")+"\n\n"+ 105 tr("[file]")+":\n"+ 106 tr("Reads input data from one or more files listed as positional arguments. " 107 + "When no files are given, or the filename is \"-\", data is read from " 108 + "standard input.")+"\n\n"+ 109 tr("Examples")+":\n"+ 110 " java -jar josm.jar project +init=epsg:4326 +to +init=epsg:3857 <<<\"11.232274 50.5685716\"\n"+ 111 " => 1250371.1334500168 6545331.055189664\n\n"+ 112 " java -jar josm.jar project +proj=lonlat +datum=WGS84 +to +proj=merc +a=6378137 +b=6378137 +nadgrids=@null <<EOF\n" + 113 " 11d13'56.19\"E 50d34'6.86\"N\n" + 114 " 118d39'30.42\"W 37d20'18.76\"N\n"+ 115 " EOF\n"+ 116 " => 1250371.1334500168 6545331.055189664\n" + 117 " -1.3208998232319113E7 4486401.160664663\n"; 118 } 119 120 private void run(String fromStr, String toStr, List<String> files) throws ProjectionConfigurationException, IOException { 121 CustomProjection fromProj = createProjection(fromStr); 122 CustomProjection toProj = createProjection(toStr); 123 if (this.argInverse) { 124 CustomProjection tmp = fromProj; 125 fromProj = toProj; 126 toProj = tmp; 127 } 128 129 if (files.isEmpty() || "-".equals(files.get(0))) { 130 processInput(fromProj, toProj, new BufferedReader(new InputStreamReader(System.in, Charset.defaultCharset()))); 131 } else { 132 for (String file : files) { 133 try (BufferedReader br = Files.newBufferedReader(Paths.get(file), StandardCharsets.UTF_8)) { 134 processInput(fromProj, toProj, br); 135 } 136 } 137 } 138 } 139 140 private void processInput(CustomProjection fromProj, CustomProjection toProj, BufferedReader reader) throws IOException { 141 String line; 142 while ((line = reader.readLine()) != null) { 143 line = line.trim(); 144 if (line.isEmpty() || line.startsWith("#")) 145 continue; 146 EastNorth enIn; 147 if (fromProj.isGeographic()) { 148 enIn = parseEastNorth(line, LatLonParser::parseCoordinate); 149 } else { 150 enIn = parseEastNorth(line, ProjectionCLI::parseDouble); 151 } 152 LatLon ll = fromProj.eastNorth2latlon(enIn); 153 EastNorth enOut = toProj.latlon2eastNorth(ll); 154 double cOut1 = argSwitchOutput ? enOut.north() : enOut.east(); 155 double cOut2 = argSwitchOutput ? enOut.east() : enOut.north(); 156 System.out.println(Double.toString(cOut1) + " " + Double.toString(cOut2)); 157 System.out.flush(); 158 } 159 } 160 161 private static CustomProjection createProjection(String params) throws ProjectionConfigurationException { 162 CustomProjection proj = new CustomProjection(); 163 proj.update(params); 164 return proj; 165 } 166 167 private EastNorth parseEastNorth(String s, ToDoubleFunction<String> parser) { 168 String[] en = s.split("[;, ]+"); 169 if (en.length != 2) 170 throw new IllegalArgumentException(tr("Expected two coordinates, separated by white space, found {0} in ''{1}''", en.length, s)); 171 double east = parser.applyAsDouble(en[0]); 172 double north = parser.applyAsDouble(en[1]); 173 if (this.argSwitchInput) 174 return new EastNorth(north, east); 175 else 176 return new EastNorth(east, north); 177 } 178 179 private static double parseDouble(String s) { 180 try { 181 return Double.parseDouble(s); 182 } catch (NumberFormatException nfe) { 183 throw new IllegalArgumentException(tr("Unable to parse number ''{0}''", s), nfe); 184 } 185 } 186 187 /** 188 * Main class to run just the projection CLI. 189 * @param args command line arguments 190 */ 191 public static void main(String[] args) { 192 ProjectionCLI.INSTANCE.processArguments(args); 193 } 194}