// 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;
}
}
}