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; 023import org.openstreetmap.josm.tools.Utils; 024 025/** 026 * Command line interface for projecting coordinates. 027 * @since 12792 028 */ 029public class ProjectionCLI implements CLIModule { 030 031 /** The unique instance **/ 032 public static final ProjectionCLI INSTANCE = new ProjectionCLI(); 033 034 private boolean argInverse; 035 private boolean argSwitchInput; 036 private boolean argSwitchOutput; 037 038 @Override 039 public String getActionKeyword() { 040 return "project"; 041 } 042 043 @Override 044 public void processArguments(String[] argArray) { 045 List<String> positionalArguments = new OptionParser("JOSM projection") 046 .addFlagParameter("help", ProjectionCLI::showHelp) 047 .addShortAlias("help", "h") 048 .addFlagParameter("inverse", () -> argInverse = true) 049 .addShortAlias("inverse", "I") 050 .addFlagParameter("switch-input", () -> argSwitchInput = true) 051 .addShortAlias("switch-input", "r") 052 .addFlagParameter("switch-output", () -> argSwitchOutput = true) 053 .addShortAlias("switch-output", "s") 054 .parseOptionsOrExit(Arrays.asList(argArray)); 055 056 List<String> projParamFrom = new ArrayList<>(); 057 List<String> projParamTo = new ArrayList<>(); 058 List<String> otherPositional = new ArrayList<>(); 059 boolean toTokenSeen = false; 060 // positional arguments: 061 for (String arg: positionalArguments) { 062 if (arg.isEmpty()) throw new IllegalArgumentException("non-empty argument expected"); 063 if (arg.startsWith("+")) { 064 if ("+to".equals(arg)) { 065 toTokenSeen = true; 066 } else { 067 (toTokenSeen ? projParamTo : projParamFrom).add(arg); 068 } 069 } else { 070 otherPositional.add(arg); 071 } 072 } 073 String fromStr = Utils.join(" ", projParamFrom); 074 String toStr = Utils.join(" ", projParamTo); 075 try { 076 run(fromStr, toStr, otherPositional); 077 } catch (ProjectionConfigurationException | IllegalArgumentException | IOException ex) { 078 System.err.println(tr("Error: {0}", ex.getMessage())); 079 System.exit(1); 080 } 081 System.exit(0); 082 } 083 084 /** 085 * Displays help on the console 086 */ 087 private static void showHelp() { 088 System.out.println(getHelp()); 089 System.exit(0); 090 } 091 092 private static String getHelp() { 093 return tr("JOSM projection command line interface")+"\n\n"+ 094 tr("Usage")+":\n"+ 095 "\tjava -jar josm.jar project <options> <crs> +to <crs> [file]\n\n"+ 096 tr("Description")+":\n"+ 097 tr("Converts coordinates from one coordinate reference system to another.")+"\n\n"+ 098 tr("Options")+":\n"+ 099 "\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("[;, ]+"); 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}