package net.osmand.data; import java.util.ArrayList; import java.util.Collections; import java.util.List; import net.osmand.osm.LatLon; import net.osmand.osm.MapUtils; import net.osmand.osm.Node; import net.osmand.osm.Way; public class MapAlgorithms { public static void simplifyDouglasPeucker(List n, int zoom, int epsilon, Way w){ ArrayList l = new ArrayList(); int first = 0; while(first < n.size()){ if(n.get(first) != null){ break; } first++; } int last = n.size() - 1; while (last >= 0) { if (n.get(last) != null) { break; } last--; } if(last - first < 1){ return; } boolean cycle = n.get(first).getId() == n.get(last).getId(); simplifyDouglasPeucker(n, zoom, epsilon, l, first, cycle ? last - 1: last); w.addNode(n.get(first)); for (int i = 0; i < l.size(); i++) { w.addNode(n.get(l.get(i))); } if (cycle) { w.addNode(n.get(first)); } } private static void simplifyDouglasPeucker(List n, int zoom, int epsilon, List ints, int start, int end){ double dmax = -1; int index = -1; for (int i = start + 1; i <= end - 1; i++) { if(n.get(i) == null){ continue; } double d = orthogonalDistance(zoom, n.get(start), n.get(end), n.get(i));// calculate distance from line if (d > dmax) { dmax = d; index = i; } } if(dmax >= epsilon){ simplifyDouglasPeucker(n, zoom, epsilon, ints, start, index); simplifyDouglasPeucker(n, zoom, epsilon, ints, index, end); } else { ints.add(end); } } private static double orthogonalDistance(int zoom, Node nodeLineStart, Node nodeLineEnd, Node node) { if(zoom > 31){ zoom = 31; } double x1 = MapUtils.getTileNumberX(zoom, nodeLineStart.getLongitude()); double y1 = MapUtils.getTileNumberY(zoom, nodeLineStart.getLatitude()); double x2 = MapUtils.getTileNumberX(zoom, nodeLineEnd.getLongitude()); double y2 = MapUtils.getTileNumberY(zoom, nodeLineEnd.getLatitude()); double x = MapUtils.getTileNumberX(zoom, node.getLongitude()); double y = MapUtils.getTileNumberY(zoom, node.getLatitude()); double A = x - x1; double B = y - y1; double C = x2 - x1; double D = y2 - y1; return Math.abs(A * D - C * B) / Math.sqrt(C * C + D * D); } public static boolean isClockwiseWay(Way w){ return isClockwiseWay(Collections.singletonList(w)); } public static boolean isClockwiseWay(List ways){ if(ways.isEmpty()){ return true; } LatLon latLon = ways.get(0).getLatLon(); double lat = latLon.getLatitude(); double lon = 180; double firstLon = -360; boolean firstDirectionUp = false; double previousLon = -360; double clockwiseSum = 0; Node prev = null; boolean firstWay = true; for(Way w : ways){ List ns = w.getNodes(); int startInd = 0; if(firstWay && ns.size() > 0){ prev = ns.get(0); startInd = 1; firstWay = false; } for(int i = startInd; i < ns.size();i++) { Node next = ns.get(i); double rlon = ray_intersect_lon(prev, next, lat, lon); if(rlon != - 360d){ boolean skipSameSide = (prev.getLatitude() <= lat) == (next.getLatitude() <= lat); if(skipSameSide){ continue; } boolean directionUp = prev.getLatitude() <= lat; if(firstLon == - 360){ firstDirectionUp = directionUp; firstLon = rlon; } else { boolean clockwise = (!directionUp) == (previousLon < rlon); if(clockwise){ clockwiseSum += Math.abs(previousLon - rlon); } else { clockwiseSum -= Math.abs(previousLon - rlon); } } previousLon = rlon; } prev = next; } } if(firstLon != -360){ boolean clockwise = (!firstDirectionUp) == (previousLon < firstLon); if(clockwise){ clockwiseSum += Math.abs(previousLon - firstLon); } else { clockwiseSum -= Math.abs(previousLon - firstLon); } } return clockwiseSum >= 0; } public static double ray_intersect_lon(Node node, Node node2, double latitude, double longitude) { // a node below Node a = node.getLatitude() < node2.getLatitude() ? node : node2; // b node above Node b = a == node2 ? node : node2; if (latitude == a.getLatitude() || latitude == b.getLatitude()) { latitude += 0.00000001d; } if (latitude < a.getLatitude() || latitude > b.getLatitude()) { return -360d; } else { if (longitude < Math.min(a.getLongitude(), b.getLongitude())) { return -360d; } else { if (a.getLongitude() == b.getLongitude() && longitude == a.getLongitude()) { // the node on the boundary !!! return longitude; } // that tested on all cases (left/right) double lon = b.getLongitude()+ (b.getLatitude() - latitude) * (b.getLongitude() - a.getLongitude()) / (b.getLatitude() - a.getLatitude()); if (lon <= longitude) { return lon; } else { return -360d; } } } } // Try to intersect with ray from left to right public static boolean ray_intersect(Node node, Node node2, double latitude, double longitude) { // a node below Node a = node.getLatitude() < node2.getLatitude() ? node : node2; // b node above Node b = a == node2 ? node : node2; if(latitude == a.getLatitude() || latitude == b.getLatitude()){ latitude += 0.00000001d; } if(latitude < a.getLatitude() || latitude > b.getLatitude()){ return false; } else { if(longitude > Math.max(a.getLongitude(), b.getLongitude())) { return true; } else if(longitude < Math.min(a.getLongitude(), b.getLongitude())){ return false; } else { if(a.getLongitude() == b.getLongitude()) { // the node on the boundary !!! return true; } // that tested on all cases (left/right) double mR = (b.getLatitude() - a.getLatitude()) / (b.getLongitude() - a.getLongitude()); double mB = (latitude - a.getLatitude()) / (longitude - a.getLongitude()); if(mB <= mR){ return true; } else { return false; } } } } }