// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.lakewalker; import static org.openstreetmap.josm.tools.I18n.tr; import java.awt.Cursor; import java.awt.Point; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.File; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.LinkedList; import javax.swing.JOptionPane; import org.openstreetmap.josm.Main; import org.openstreetmap.josm.actions.JosmAction; import org.openstreetmap.josm.command.AddCommand; import org.openstreetmap.josm.command.Command; import org.openstreetmap.josm.command.SequenceCommand; import org.openstreetmap.josm.data.coor.LatLon; import org.openstreetmap.josm.data.osm.Node; import org.openstreetmap.josm.data.osm.Way; import org.openstreetmap.josm.gui.PleaseWaitRunnable; import org.openstreetmap.josm.gui.progress.ProgressMonitor; import org.openstreetmap.josm.tools.ImageProvider; import org.openstreetmap.josm.tools.Shortcut; import org.xml.sax.SAXException; /** * Interface to Darryl Shpak's Lakewalker module * * @author Brent Easton */ class LakewalkerAction extends JosmAction implements MouseListener { private static final long serialVersionUID = 1L; protected String name; protected Cursor oldCursor; protected Thread executeThread; protected boolean cancel; protected boolean active = false; protected Collection<Command> commands = new LinkedList<>(); protected Collection<Way> ways = new ArrayList<>(); LakewalkerAction(String name) { super(name, "lakewalker-sml", tr("Lake Walker."), Shortcut.registerShortcut("tools:lakewalker", tr("Tool: {0}", tr("Lake Walker")), KeyEvent.VK_L, Shortcut.ALT_CTRL_SHIFT), true); this.name = name; setEnabled(true); } @Override public void actionPerformed(ActionEvent e) { if (Main.map == null || Main.map.mapView == null || active) return; active = true; oldCursor = Main.map.mapView.getCursor(); Main.map.mapView.setCursor(ImageProvider.getCursor("crosshair", "lakewalker-sml")); Main.map.mapView.addMouseListener(this); } /** * check for presence of cache folder and trim cache to specified size. * size/age limit is on a per folder basis. */ private void cleanupCache() { final long maxCacheAge = System.currentTimeMillis()-Main.pref.getInteger(LakewalkerPreferences.PREF_MAXCACHEAGE, 100)*24*60*60*1000L; final long maxCacheSize = Main.pref.getInteger(LakewalkerPreferences.PREF_MAXCACHESIZE, 300)*1024*1024L; for (String wmsFolder : LakewalkerPreferences.WMSLAYERS) { File wmsCacheDir = new File(LakewalkerPlugin.getLakewalkerCacheDir(), wmsFolder); if (wmsCacheDir.exists() && wmsCacheDir.isDirectory()) { File[] wmsCache = wmsCacheDir.listFiles(); // sort files by date (most recent first) Arrays.sort(wmsCache, Comparator.comparingLong(File::lastModified)); // delete aged or oversized, keep newest. Once size/age limit was reached delete all older files long folderSize = 0; boolean quickdelete = false; for (File cacheEntry : wmsCache) { if (!cacheEntry.isFile()) continue; if (!quickdelete) { folderSize += cacheEntry.length(); if (folderSize > maxCacheSize) { quickdelete = true; } else if (cacheEntry.lastModified() < maxCacheAge) { quickdelete = true; } } if (quickdelete) { cacheEntry.delete(); } } } else { // create cache directory if (!wmsCacheDir.mkdirs()) { JOptionPane.showMessageDialog(Main.parent, tr("Error creating cache directory: {0}", wmsCacheDir.getPath())); } } } } protected void lakewalk(Point clickPoint) { // Positional data final LatLon pos = Main.map.mapView.getLatLon(clickPoint.x, clickPoint.y); final LatLon topLeft = Main.map.mapView.getLatLon(0, 0); final LatLon botRight = Main.map.mapView.getLatLon(Main.map.mapView.getWidth(), Main.map.mapView.getHeight()); /* * Collect options */ final int waylen = Main.pref.getInteger(LakewalkerPreferences.PREF_MAX_SEG, 500); final int maxnode = Main.pref.getInteger(LakewalkerPreferences.PREF_MAX_NODES, 50000); final int threshold = Main.pref.getInteger(LakewalkerPreferences.PREF_THRESHOLD_VALUE, 90); final double epsilon = Main.pref.getDouble(LakewalkerPreferences.PREF_EPSILON, 0.0003); final int resolution = Main.pref.getInteger(LakewalkerPreferences.PREF_LANDSAT_RES, 4000); final int tilesize = Main.pref.getInteger(LakewalkerPreferences.PREF_LANDSAT_SIZE, 2000); final String startdir = Main.pref.get(LakewalkerPreferences.PREF_START_DIR, "east"); final String wmslayer = Main.pref.get(LakewalkerPreferences.PREF_WMS, "IR1"); try { PleaseWaitRunnable lakewalkerTask = new PleaseWaitRunnable(tr("Tracing")) { @Override protected void realRun() throws SAXException { progressMonitor.subTask(tr("checking cache...")); cleanupCache(); processnodelist(new Lakewalker(waylen, maxnode, threshold, epsilon, resolution, tilesize, startdir, wmslayer), pos, topLeft, botRight, epsilon, progressMonitor.createSubTaskMonitor(ProgressMonitor.ALL_TICKS, false)); } @Override protected void finish() { } @Override protected void cancel() { LakewalkerAction.this.cancel(); } }; new Thread(lakewalkerTask).start(); } catch (Exception ex) { Main.error(ex); } } private void processnodelist(Lakewalker lw, LatLon pos, LatLon topLeft, LatLon botRight, double epsilon, ProgressMonitor progressMonitor) { progressMonitor.beginTask(null, 3); try { ArrayList<double[]> nodelist = new ArrayList<>(); try { nodelist = lw.trace(pos.lat(), pos.lon(), topLeft.lon(), botRight.lon(), topLeft.lat(), botRight.lat(), progressMonitor.createSubTaskMonitor(1, false)); } catch (LakewalkerException e) { Main.error(e); } System.out.println(nodelist.size()+" nodes generated"); /** * Run the nodelist through a vertex reduction algorithm */ progressMonitor.subTask(tr("Running vertex reduction...")); nodelist = lw.vertexReduce(nodelist, epsilon); //System.out.println("After vertex reduction "+nodelist.size()+" nodes remain."); /** * And then through douglas-peucker approximation */ progressMonitor.worked(1); progressMonitor.subTask(tr("Running Douglas-Peucker approximation...")); nodelist = lw.douglasPeucker(nodelist, epsilon, 0); //System.out.println("After Douglas-Peucker approximation "+nodelist.size()+" nodes remain."); /** * And then through a duplicate node remover */ progressMonitor.worked(1); progressMonitor.subTask(tr("Removing duplicate nodes...")); nodelist = lw.duplicateNodeRemove(nodelist); //System.out.println("After removing duplicate nodes, "+nodelist.size()+" nodes remain."); // if for some reason (image loading failed, ...) nodelist is empty, no more processing required. if (nodelist.size() == 0) { return; } /** * Turn the arraylist into osm nodes */ Way way = new Way(); Node n = null; Node fn = null; double eastOffset = Main.pref.getDouble(LakewalkerPreferences.PREF_EAST_OFFSET, 0.0); double northOffset = Main.pref.getDouble(LakewalkerPreferences.PREF_NORTH_OFFSET, 0.0); int nodesinway = 0; for (int i = 0; i < nodelist.size(); i++) { if (cancel) { return; } try { LatLon ll = new LatLon(nodelist.get(i)[0]+northOffset, nodelist.get(i)[1]+eastOffset); n = new Node(ll); if (fn == null) { fn = n; } commands.add(new AddCommand(n)); } catch (Exception ex) { ex.printStackTrace(); } way.addNode(n); if (nodesinway > Main.pref.getInteger(LakewalkerPreferences.PREF_MAX_SEG, 500)) { String waytype = Main.pref.get(LakewalkerPreferences.PREF_WAYTYPE, "water"); if (!waytype.equals("none")) { way.put("natural", waytype); } way.put("source", Main.pref.get(LakewalkerPreferences.PREF_SOURCE, "Landsat")); commands.add(new AddCommand(way)); way = new Way(); way.addNode(n); nodesinway = 0; } nodesinway++; } String waytype = Main.pref.get(LakewalkerPreferences.PREF_WAYTYPE, "water"); if (!waytype.equals("none")) { way.put("natural", waytype); } way.put("source", Main.pref.get(LakewalkerPreferences.PREF_SOURCE, "Landsat")); way.addNode(fn); commands.add(new AddCommand(way)); if (!commands.isEmpty()) { Main.main.undoRedo.add(new SequenceCommand(tr("Lakewalker trace"), commands)); getLayerManager().getEditDataSet().setSelected(ways); } else { System.out.println("Failed"); } commands = new LinkedList<>(); ways = new ArrayList<>(); } finally { progressMonitor.finishTask(); } } public void cancel() { cancel = true; } @Override public void mouseClicked(MouseEvent e) { if (active) { active = false; Main.map.mapView.removeMouseListener(this); Main.map.mapView.setCursor(oldCursor); lakewalk(e.getPoint()); } } @Override public void mouseEntered(MouseEvent e) { } @Override public void mouseExited(MouseEvent e) { } @Override public void mousePressed(MouseEvent e) { } @Override public void mouseReleased(MouseEvent e) { } }