package ext_tools; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Cursor; import java.awt.GridBagLayout; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.SequenceCommand; import org.openstreetmap.josm.data.ProjectionBounds; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.DataSet; import org.openstreetmap.josm.gui.MainMenu; import org.openstreetmap.josm.gui.MapView; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.gui.progress.NullProgressMonitor; import org.openstreetmap.josm.gui.widgets.JMultilineLabel; import org.openstreetmap.josm.io.IllegalDataException; import org.openstreetmap.josm.io.OsmReader; import org.openstreetmap.josm.tools.GBC; public class ExtTool { protected boolean enabled; public String name; public String cmdline; public String description; public String url; protected ExtToolAction action; protected JMenuItem menuItem; public boolean isEnabled() { return enabled; } public void setEnabled(boolean enabled) { if (!this.enabled ^ enabled) return; this.enabled = enabled; if (enabled) { if (action == null) action = new ExtToolAction(this); menuItem = MainMenu.add(Main.main.menu.toolsMenu, action); } else { Main.main.menu.toolsMenu.remove(menuItem); } } public String getName() { return name; } public void setName(String name) { this.name = name; } public ExtTool() { this.enabled = false; } public ExtTool(String name) { this(); this.name = name; } public String serialize() { StringBuilder sb = new StringBuilder(); sb.append("name=").append(name).append('\n'); sb.append("cmdline=").append(cmdline).append('\n'); sb.append("description=").append(description).append('\n'); sb.append("url=").append(url).append('\n'); sb.append('\n'); return sb.toString(); } public static ExtTool unserialize(String str) { ExtTool t = new ExtTool(); String[] lines = str.split("\n"); for (String line : lines) { String[] parts = line.split("=", 2); if (parts[0].equals("name")) t.name = parts[1]; else if (parts[0].equals("cmdline")) t.cmdline = parts[1]; else if (parts[0].equals("description")) t.description = parts[1]; else if (parts[0].equals("url")) t.url = parts[1]; } return t; } private class ToolProcess { public Process process; public volatile boolean running; } static double getPPD() { ProjectionBounds bounds = Main.map.mapView.getProjectionBounds(); return Main.map.mapView.getWidth() / (bounds.maxEast - bounds.minEast); } private double latToTileY(double lat, int zoom) { double l = lat / 180 * Math.PI; double pf = Math.log(Math.tan(l) + (1 / Math.cos(l))); return Math.pow(2.0, zoom - 1) * (Math.PI - pf) / Math.PI; } private double lonToTileX(double lon, int zoom) { return Math.pow(2.0, zoom - 3) * (lon + 180.0) / 45.0; } private double getTMSZoom() { if (Main.map == null || Main.map.mapView == null) return 1; MapView mv = Main.map.mapView; LatLon topLeft = mv.getLatLon(0, 0); LatLon botRight = mv.getLatLon(mv.getWidth(), mv.getHeight()); double x1 = lonToTileX(topLeft.lon(), 1); double y1 = latToTileY(topLeft.lat(), 1); double x2 = lonToTileX(botRight.lon(), 1); double y2 = latToTileY(botRight.lat(), 1); int screenPixels = mv.getWidth()*mv.getHeight(); double tilePixels = Math.abs((y2-y1)*(x2-x1)*65536); if (screenPixels == 0 || tilePixels == 0) return 1; return Math.log(screenPixels/tilePixels)/Math.log(2)/2+1; } protected void showErrorMessage(final String message, final String details) { SwingUtilities.invokeLater(new Runnable() { public void run() { final JPanel p = new JPanel(new GridBagLayout()); p.add(new JMultilineLabel(message),GBC.eol()); if (details != null) { JTextArea info = new JTextArea(details, 20, 60); info.setCaretPosition(0); info.setEditable(false); p.add(new JScrollPane(info), GBC.eop()); } JOptionPane.showMessageDialog(Main.parent, p, tr("External tool error"), JOptionPane.ERROR_MESSAGE); } }); } public void runTool(LatLon pos) { Main.map.mapView.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR)); // parse cmdline and build cmdParams array HashMap<String, String> replace = new HashMap<>(); replace.put("{lat}", "" + pos.lat()); replace.put("{lon}", "" + pos.lon()); replace.put("{PPD}", "" + getPPD()); replace.put("{TZoom}", "" + getTMSZoom()); ArrayList<String> cmdParams = new ArrayList<>(); StringTokenizer st = new StringTokenizer(cmdline); while (st.hasMoreTokens()) { String token = st.nextToken(); if (replace.containsKey(token)) { cmdParams.add(replace.get(token)); } else { cmdParams.add(token); } } // create the process final Object syncObj = new Object(); ProcessBuilder builder; builder = new ProcessBuilder(cmdParams); builder.directory(new File(ExtToolsPlugin.plugin.getPluginDir())); final StringBuilder debugstr = new StringBuilder(); // debug: print resulting cmdline for (String s : builder.command()) debugstr.append(s + " "); debugstr.append("\n"); System.out.print(debugstr.toString()); final ToolProcess tp = new ToolProcess(); try { tp.process = builder.start(); } catch (final IOException e) { e.printStackTrace(); synchronized (debugstr) { showErrorMessage( tr("Error executing the script:"), debugstr.toString() + e.getMessage() + "\n" + Arrays.toString(e.getStackTrace())); } return; } tp.running = true; // redirect child process's stderr to JOSM stderr new Thread(new Runnable() { public void run() { try { byte[] buffer = new byte[1024]; InputStream errStream = tp.process.getErrorStream(); int len; while ((len = errStream.read(buffer)) > 0) { synchronized (debugstr) { debugstr.append(new String(buffer, 0, len)); } System.err.write(buffer, 0, len); } } catch (IOException e) { } } }).start(); // read stdout stream Thread osmParseThread = new Thread(new Runnable() { public void run() { try { final InputStream inputStream = tp.process.getInputStream(); final DataSet ds = OsmReader.parseDataSet(inputStream, NullProgressMonitor.INSTANCE); final List<Command> cmdlist = new DataSetToCmd(ds).getCommandList(); if (!cmdlist.isEmpty()) { SequenceCommand cmd = new SequenceCommand(getName(), cmdlist); Main.main.undoRedo.add(cmd); } } catch (IllegalDataException e) { e.printStackTrace(); if (tp.running) { tp.process.destroy(); synchronized (debugstr) { showErrorMessage( tr("Child script have returned invalid data.\n\nstderr contents:"), debugstr.toString()); } } } finally { synchronized (syncObj) { tp.running = false; syncObj.notifyAll(); } } } }); osmParseThread.start(); synchronized (syncObj) { try { syncObj.wait(10000); } catch (InterruptedException e) { } } if (tp.running) { new Thread(new PleaseWaitRunnable(name) { @Override protected void realRun() { try { progressMonitor.indeterminateSubTask(null); synchronized (syncObj) { if (tp.running) syncObj.wait(); } } catch (InterruptedException e) { } } @Override protected void cancel() { synchronized (syncObj) { tp.running = false; tp.process.destroy(); syncObj.notifyAll(); } } @Override protected void finish() { } }).start(); } } }