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