// License: GPL. For details, see LICENSE file. package org.openstreetmap.josm.plugins.lakewalker; import static org.openstreetmap.josm.tools.I18n.tr; import java.util.ArrayList; import java.util.List; import org.openstreetmap.josm.gui.progress.ProgressMonitor; public class Lakewalker { protected boolean cancel; private int maxnode; private int threshold; private int resolution; private int tilesize; private String startdir; private String wmslayer; private int[] dirslat = new int[] {0, 1, 1, 1, 0, -1, -1, -1}; private int[] dirslon = new int[] {1, 1, 0, -1, -1, -1, 0, 1}; double start_radius_big = 0.001; double start_radius_small = 0.0002; public Lakewalker(int waylen, int maxnode, int threshold, double epsilon, int resolution, int tilesize, String startdir, String wmslayer) { this.maxnode = maxnode; this.threshold = threshold; this.resolution = resolution; this.tilesize = tilesize; this.startdir = startdir; this.wmslayer = wmslayer; } /** * east = 0 * northeast = 1 * north = 2 * northwest = 3 * west = 4 * southwest = 5 * south = 6 * southeast = 7 */ private int getDirectionIndex(String direction) throws ArrayIndexOutOfBoundsException { int i = 0; if (direction.equals("East") || direction.equals("east")) { i = 0; } else if (direction.equals("Northeast") || direction.equals("northeast")) { i = 1; } else if (direction.equals("North") || direction.equals("north")) { i = 2; } else if (direction.equals("Northwest") || direction.equals("northwest")) { i = 3; } else if (direction.equals("West") || direction.equals("west")) { i = 4; } else if (direction.equals("Southwest") || direction.equals("southwest")) { i = 5; } else if (direction.equals("South") || direction.equals("south")) { i = 6; } else if (direction.equals("Southeast") || direction.equals("southeast")) { i = 7; } else { throw new ArrayIndexOutOfBoundsException(tr("Direction index ''{0}'' not found", direction)); } return i; } /** * Do a trace */ public ArrayList<double[]> trace(double lat, double lon, double tl_lon, double br_lon, double tl_lat, double br_lat, ProgressMonitor progressMonitor) throws LakewalkerException { progressMonitor.beginTask(null); try { LakewalkerWMS wms = new LakewalkerWMS(this.resolution, this.tilesize, this.wmslayer); LakewalkerBBox bbox = new LakewalkerBBox(tl_lat, tl_lon, br_lat, br_lon); Boolean detect_loop = false; ArrayList<double[]> nodelist = new ArrayList<>(); int[] xy = geo_to_xy(lat, lon, this.resolution); if (!bbox.contains(lat, lon)) { throw new LakewalkerException(tr("The starting location was not within the bbox")); } int v; progressMonitor.indeterminateSubTask(tr("Looking for shoreline...")); while (true) { double[] geo = xy_to_geo(xy[0], xy[1], this.resolution); if (bbox.contains(geo[0], geo[1]) == false) { break; } v = wms.getPixel(xy[0], xy[1], progressMonitor.createSubTaskMonitor(0, false)); if (v > this.threshold) { break; } int delta_lat = this.dirslat[getDirectionIndex(this.startdir)]; int delta_lon = this.dirslon[getDirectionIndex(this.startdir)]; xy[0] = xy[0]+delta_lon; xy[1] = xy[1]+delta_lat; } int[] startxy = new int[] {xy[0], xy[1]}; double[] startgeo = xy_to_geo(xy[0], xy[1], this.resolution); //System.out.printf("Found shore at lat %.4f lon %.4f\n",lat,lon); int last_dir = this.getDirectionIndex(this.startdir); for (int i = 0; i < this.maxnode; i++) { // Print a counter if (i % 250 == 0) { progressMonitor.indeterminateSubTask(tr("{0} nodes so far...", i)); //System.out.println(i+" nodes so far..."); } // Some variables we need int d; int test_x = 0; int test_y = 0; int new_dir = 0; // Loop through all the directions we can go for (d = 1; d <= this.dirslat.length; d++) { // Decide which direction we want to look at from this pixel new_dir = (last_dir + d + 4) % 8; test_x = xy[0] + this.dirslon[new_dir]; test_y = xy[1] + this.dirslat[new_dir]; double[] geo = xy_to_geo(test_x, test_y, this.resolution); if (!bbox.contains(geo[0], geo[1])) { System.out.println("Outside bbox"); break; } v = wms.getPixel(test_x, test_y, progressMonitor.createSubTaskMonitor(0, false)); if (v > this.threshold) { break; } if (d == this.dirslat.length-1) { System.out.println("Got stuck"); break; } } // Remember this direction last_dir = new_dir; // Set the pixel we found as current xy[0] = test_x; xy[1] = test_y; // Break the loop if we managed to get back to our starting point if (xy[0] == startxy[0] && xy[1] == startxy[1]) { break; } // Store this node double[] geo = xy_to_geo(xy[0], xy[1], this.resolution); nodelist.add(geo); //System.out.println("Adding node at "+xy[0]+","+xy[1]+" ("+geo[1]+","+geo[0]+")"); // Check if we got stuck in a loop double start_proximity = Math.pow((geo[0] - startgeo[0]), 2) + Math.pow((geo[1] - startgeo[1]), 2); if (detect_loop) { if (start_proximity < Math.pow(start_radius_small, 2)) { System.out.println("Detected loop"); break; } } else { if (start_proximity > Math.pow(start_radius_big, 2)) { detect_loop = true; } } } return nodelist; } finally { progressMonitor.finishTask(); } } /** * Remove duplicate nodes from the list */ public ArrayList<double[]> duplicateNodeRemove(ArrayList<double[]> nodes) { if (nodes.size() <= 1) { return nodes; } double[] lastnode = new double[] {nodes.get(0)[0], nodes.get(0)[1]}; for (int i = 1; i < nodes.size(); i++) { double[] thisnode = new double[] {nodes.get(i)[0], nodes.get(i)[1]}; if (thisnode[0] == lastnode[0] && thisnode[1] == lastnode[1]) { // Remove the node nodes.remove(i); // Shift back one index i = i - 1; } lastnode = thisnode; } return nodes; } /** * Reduce the number of vertices based on their proximity to each other */ public ArrayList<double[]> vertexReduce(ArrayList<double[]> nodes, double proximity) { // Check if node list is empty if (nodes.size() <= 1) { return nodes; } double[] test_v = nodes.get(0); ArrayList<double[]> reducednodes = new ArrayList<>(); double prox_sq = Math.pow(proximity, 2); for (int v = 0; v < nodes.size(); v++) { if (Math.pow(nodes.get(v)[0] - test_v[0], 2) + Math.pow(nodes.get(v)[1] - test_v[1], 2) > prox_sq) { reducednodes.add(nodes.get(v)); test_v = nodes.get(v); } } return reducednodes; } public double pointLineDistance(double[] p1, double[] p2, double[] p3) { double x0 = p1[0]; double y0 = p1[1]; double x1 = p2[0]; double y1 = p2[1]; double x2 = p3[0]; double y2 = p3[1]; if (x2 == x1 && y2 == y1) { return Math.sqrt(Math.pow(x1-x0, 2) + Math.pow(y1-y0, 2)); } else { return Math.abs((x2-x1)*(y1-y0) - (x1-x0)*(y2-y1)) / Math.sqrt(Math.pow(x2-x1, 2) + Math.pow(y2-y1, 2)); } } /* public ArrayList<double[]> douglasPeuckerNR(ArrayList<double[]> nodes, double epsilon) { command_stack = [(0, len(nodes) - 1)] Vector result_stack = new Vector(); while (command_stack.size() > 0) { cmd = command_stack.pop(); if (type(cmd) == tuple) { (start, end) = cmd (node, dist) = dp_findpoint(nodes, start, end) if (dist > epsilon) { command_stack.append("+") command_stack.append((start, node)) command_stack.append((node, end)) } else { result_stack.append((start, end)) } } elseif (cmd == "+") { first = result_stack.pop() second = result_stack.pop() if (first[-1] == second[0]) { result_stack.append(first + second[1:]) //print "Added %s and %s; result is %s" % (first, second, result_stack[-1]) }else { error("ERROR: Cannot connect nodestrings!") #print first #print second return; } } else { error("ERROR: Can't understand command \"%s\"" % (cmd,)) return if (len(result_stack) == 1) { return [nodes[x] for x in result_stack[0]]; } else { error("ERROR: Command stack is empty but result stack has %d nodes!" % len(result_stack)); return; } farthest_node = None farthest_dist = 0 first = nodes[0] last = nodes[-1] for (i in xrange(1, len(nodes) - 1) { d = point_line_distance(nodes[i], first, last) if (d > farthest_dist) { farthest_dist = d farthest_node = i } } if (farthest_dist > epsilon) { seg_a = douglas_peucker(nodes[0:farthest_node+1], epsilon) seg_b = douglas_peucker(nodes[farthest_node:-1], epsilon) //print "Minimized %d nodes to %d + %d nodes" % (len(nodes), len(seg_a), len(seg_b)) nodes = seg_a[:-1] + seg_b } else { return [nodes[0], nodes[-1]]; } return nodes; } */ public ArrayList<double[]> douglasPeucker(ArrayList<double[]> nodes, double epsilon, int depth) { // Check if node list is empty if (nodes.size() <= 1 || depth > 500) { return nodes; } int farthest_node = -1; double farthest_dist = 0; double[] first = nodes.get(0); double[] last = nodes.get(nodes.size()-1); ArrayList<double[]> new_nodes = new ArrayList<>(); double d = 0; for (int i = 1; i < nodes.size(); i++) { d = pointLineDistance(nodes.get(i), first, last); if (d > farthest_dist) { farthest_dist = d; farthest_node = i; } } List<double[]> seg_a; List<double[]> seg_b; if (farthest_dist > epsilon) { seg_a = douglasPeucker(sublist(nodes, 0, farthest_node+1), epsilon, depth+1); seg_b = douglasPeucker(sublist(nodes, farthest_node, nodes.size()-1), epsilon, depth+1); new_nodes.addAll(seg_a); new_nodes.addAll(seg_b); } else { new_nodes.add(nodes.get(0)); new_nodes.add(nodes.get(nodes.size()-1)); } return new_nodes; } private ArrayList<double[]> sublist(ArrayList<double[]> l, int i, int f) throws ArrayIndexOutOfBoundsException { ArrayList<double[]> sub = new ArrayList<>(); if (f < i || i < 0 || f < 0 || f > l.size()) { throw new ArrayIndexOutOfBoundsException(); } for (int j = i; j < f; j++) { sub.add(l.get(j)); } return sub; } public double[] xy_to_geo(int x, int y, double resolution) { double[] geo = new double[2]; geo[0] = y / resolution; geo[1] = x / resolution; return geo; } public int[] geo_to_xy(double lat, double lon, double resolution) { int[] xy = new int[2]; xy[0] = (int) Math.floor(lon * resolution + 0.5); xy[1] = (int) Math.floor(lat * resolution + 0.5); return xy; } /* * User has hit the cancel button */ public void cancel() { cancel = true; } /** * Class to do checking of whether the point is within our bbox * * @author Jason Reid */ private static class LakewalkerBBox { private double top = 90; private double left = -180; private double bottom = -90; private double right = 180; protected LakewalkerBBox(double top, double left, double bottom, double right) { this.left = left; this.right = right; this.top = top; this.bottom = bottom; } protected Boolean contains(double lat, double lon) { if (lat >= this.top || lat <= this.bottom) { return false; } if (lon >= this.right || lon <= this.left) { return false; } if ((this.right - this.left) % 360 == 0) { return true; } return (lon - this.left) % 360 <= (this.right - this.left) % 360; } } }