Merge pull request #319 from sanderd17/multipolygon

Multipolygon
This commit is contained in:
vshcherb 2012-09-24 12:12:00 -07:00
commit 3da9366685
5 changed files with 580 additions and 358 deletions

View file

@ -2,6 +2,7 @@ package net.osmand.data;
import static org.junit.Assert.*; import static org.junit.Assert.*;
import net.osmand.osm.LatLon; import net.osmand.osm.LatLon;
import net.osmand.osm.Node; import net.osmand.osm.Node;
import net.osmand.osm.Way; import net.osmand.osm.Way;
@ -66,6 +67,27 @@ public class MultipolygonTest {
assertFalse(testee.hasOpenedPolygons()); assertFalse(testee.hasOpenedPolygons());
} }
@Test
public void test_ringArea(){
Way w = new Way(0L);
w.addNode(new Node(0.0, 0.0, 1));
w.addNode(new Node(1.0, 0.0, 2));
w.addNode(new Node(1.0, 0.5, 3));
w.addNode(new Node(1.5, 0.5, 4));
w.addNode(new Node(1.0, 1.0, 5));
Multipolygon m = new Multipolygon();
m.addOuterWay(w);
Ring r = m.getOuterRings().get(0);
// calculated with JOSM measurement tool
double expected = 7716818755.73;
// allow 1% deviation because of rounding errors and alternative projections
assertTrue(expected/r.getArea() > 0.99);
assertTrue(expected/r.getArea() < 1.01);
}
@Test @Test
public void test_oneWayPolygon() { public void test_oneWayPolygon() {
testee.addOuterWay(poly2); testee.addOuterWay(poly2);
@ -174,7 +196,7 @@ public class MultipolygonTest {
testee.addOuterWay(new Way(111)); testee.addOuterWay(new Way(111));
testee.addOuterWay(poly1_1_of_2); testee.addOuterWay(poly1_1_of_2);
assertEquals(1, testee.countOuterPolygons()); assertEquals(1, testee.countOuterPolygons());
// FIXME assertTrue(testee.hasOpenedPolygons()); assertTrue(testee.hasOpenedPolygons());
} }
@Test @Test

View file

@ -377,4 +377,143 @@ public class MapAlgorithms {
} }
return -1l; return -1l;
} }
/**
* return true if the line segment [a,b] intersects [c,d]
* @param a point 1
* @param b point 2
* @param c point 3
* @param d point 4
* @return true if the line segment [a,b] intersects [c,d]
*/
public static boolean linesIntersect(LatLon a, LatLon b, LatLon c, LatLon d){
return linesIntersect(
a.getLatitude(), a.getLongitude(),
b.getLatitude(), b.getLongitude(),
c.getLatitude(), c.getLongitude(),
d.getLatitude(), d.getLongitude());
}
/**
* Return true if two line segments intersect inside the segment
*
* source: http://www.java-gaming.org/index.php?topic=22590.0
* @param x1 line 1 point 1 latitude
* @param y1 line 1 point 1 longitude
* @param x2 line 1 point 2 latitude
* @param y2 line 1 point 2 longitude
* @param x3 line 2 point 1 latitude
* @param y3 line 2 point 1 longitude
* @param x4 line 2 point 2 latitude
* @param y4 line 2 point 2 longitude
* @return
*/
public static boolean linesIntersect(double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4){
// Return false if either of the lines have zero length
if (x1 == x2 && y1 == y2 ||
x3 == x4 && y3 == y4){
return false;
}
// Fastest method, based on Franklin Antonio's "Faster Line Segment Intersection" topic "in Graphics Gems III" book (http://www.graphicsgems.org/)
double ax = x2-x1;
double ay = y2-y1;
double bx = x3-x4;
double by = y3-y4;
double cx = x1-x3;
double cy = y1-y3;
double alphaNumerator = by*cx - bx*cy;
double commonDenominator = ay*bx - ax*by;
if (commonDenominator > 0){
if (alphaNumerator < 0 || alphaNumerator > commonDenominator){
return false;
}
}else if (commonDenominator < 0){
if (alphaNumerator > 0 || alphaNumerator < commonDenominator){
return false;
}
}
double betaNumerator = ax*cy - ay*cx;
if (commonDenominator > 0){
if (betaNumerator < 0 || betaNumerator > commonDenominator){
return false;
}
}else if (commonDenominator < 0){
if (betaNumerator > 0 || betaNumerator < commonDenominator){
return false;
}
}
if (commonDenominator == 0){
// This code wasn't in Franklin Antonio's method. It was added by Keith Woodward.
// The lines are parallel.
// Check if they're collinear.
double y3LessY1 = y3-y1;
double collinearityTestForP3 = x1*(y2-y3) + x2*(y3LessY1) + x3*(y1-y2); // see http://mathworld.wolfram.com/Collinear.html
// If p3 is collinear with p1 and p2 then p4 will also be collinear, since p1-p2 is parallel with p3-p4
if (collinearityTestForP3 == 0){
// The lines are collinear. Now check if they overlap.
if (x1 >= x3 && x1 <= x4 || x1 <= x3 && x1 >= x4 ||
x2 >= x3 && x2 <= x4 || x2 <= x3 && x2 >= x4 ||
x3 >= x1 && x3 <= x2 || x3 <= x1 && x3 >= x2){
if (y1 >= y3 && y1 <= y4 || y1 <= y3 && y1 >= y4 ||
y2 >= y3 && y2 <= y4 || y2 <= y3 && y2 >= y4 ||
y3 >= y1 && y3 <= y2 || y3 <= y1 && y3 >= y2){
return true;
}
}
}
return false;
}
return true;
}
/**
* Get the area (in m²) of a closed way, represented as a list of nodes
* @param nodes the list of nodes
* @return the area of it
*/
public static double getArea(List<Node> nodes) {
// x = longitude
// y = latitude
// calculate the reference point (lower left corner of the bbox)
// start with an arbitrary value, bigger than any lat or lon
double refX = 500, refY = 500;
for (Node n : nodes){
if (n.getLatitude() < refY) refY = n.getLatitude();
if (n.getLongitude() < refX) refX = n.getLongitude();
}
List<Double> xVal = new ArrayList<Double>();
List<Double> yVal = new ArrayList<Double>();
for (Node n : nodes) {
// distance from bottom line to x coordinate of node
double xDist = MapUtils.getDistance(refY, refX, refY, n.getLongitude());
// distance from left line to y coordinate of node
double yDist = MapUtils.getDistance(refY, refX, n.getLatitude(), refX);
xVal.add(xDist);
yVal.add(yDist);
}
double area = 0;
for(int i = 1; i<xVal.size(); i++) {
area += xVal.get(i-1)*yVal.get(i) - xVal.get(i)*yVal.get(i-1);
}
return Math.abs(area) / 2;
}
} }

View file

@ -105,23 +105,34 @@ public class Multipolygon {
* @return true if this multipolygon is correct and contains the point * @return true if this multipolygon is correct and contains the point
*/ */
public boolean containsPoint(double latitude, double longitude) { public boolean containsPoint(double latitude, double longitude) {
boolean outerContain = false; Ring containedInOuter = null;
for (Ring outer : getOuterRings()) { // use a sortedset to get the smallest outer containing the point
SortedSet<Ring> outers = new TreeSet<Ring> (getOuterRings());
for (Ring outer : outers) {
if (outer.containsPoint(latitude, longitude)) { if (outer.containsPoint(latitude, longitude)) {
outerContain = true; containedInOuter = outer;
break; break;
} }
} }
if (!outerContain) {
if (containedInOuter == null) {
return false; return false;
} }
for (Ring inner : getInnerRings()) {
//use a sortedSet to get the smallest inner Ring
SortedSet<Ring> inners = new TreeSet<Ring> (getInnerRings());
Ring containedInInner = null;
for (Ring inner : inners) {
if (inner.containsPoint(latitude, longitude)) { if (inner.containsPoint(latitude, longitude)) {
return false; containedInInner = inner;
break;
} }
} }
return true; if (containedInInner == null) return true;
// if it is both, in an inner and in an outer, check if the inner is indeed the smallest one
return !containedInInner.isIn(containedInOuter);
} }
/** /**
@ -297,7 +308,8 @@ public class Multipolygon {
ArrayList<Ring> inners = new ArrayList<Ring>(getInnerRings()); ArrayList<Ring> inners = new ArrayList<Ring>(getInnerRings());
// get the set of outer rings in a variable. This set will not be changed // get the set of outer rings in a variable. This set will not be changed
ArrayList<Ring> outers = new ArrayList<Ring>(getOuterRings()); // sort it to start with the smallest
SortedSet<Ring> outers = new TreeSet<Ring>(getOuterRings());
ArrayList<Multipolygon> multipolygons = new ArrayList<Multipolygon>(); ArrayList<Multipolygon> multipolygons = new ArrayList<Multipolygon>();
// loop; start with the smallest outer ring // loop; start with the smallest outer ring
@ -331,11 +343,11 @@ public class Multipolygon {
} }
/** /**
* This method only works when the multipolygon has exaclt one outer Ring * This method only works when the multipolygon has exactly one outer Ring
* @return the list of nodes in the outer ring * @return the list of nodes in the outer ring
*/ */
public List<Node> getOuterNodes() { public List<Node> getOuterNodes() {
return getOuterRings().get(0).getBorder().getNodes(); return getOuterRings().get(0).getBorder();
} }

View file

@ -1,6 +1,5 @@
package net.osmand.data; package net.osmand.data;
import gnu.trove.list.array.TLongArrayList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -17,24 +16,26 @@ import net.osmand.osm.Way;
* @author sander * @author sander
* *
*/ */
public class Ring { public class Ring implements Comparable<Ring> {
/** /**
* This is a list of the ways added by the user * This is a list of the ways added by the user
* The order can be changed with methods from this class * The order can be changed with methods from this class
*/ */
private final ArrayList<Way> ways; private final ArrayList<Way> ways;
/** /**
* This is the closure of the ways added by the user * a concatenation of the ways to form the border
* So simple two-node ways are added to close the ring * this is NOT necessarily a CLOSED way
* This is a cache from what can calculated with the ways * The id is random, so this may never leave the Ring object
*/ */
private ArrayList<Way> closedWays; private Way border;
/** /**
* This is a single way, consisting of all the nodes * area can be asked a lot of times when comparing rings, chace it
* from ways in the closedWays
* this is a cache from what can be calculated with the closedWays
*/ */
private Way closedBorder; private double area = -1;
/** /**
* Construct a Ring with a list of ways * Construct a Ring with a list of ways
@ -52,149 +53,330 @@ public class Ring {
* @return the ways added to the Ring * @return the ways added to the Ring
*/ */
public List<Way> getWays() { public List<Way> getWays() {
return ways; return new ArrayList<Way>(ways);
}
/**
* Get the closed ways that make up the Ring
* This method will sort the ways, so it is CPU intensive
* @return the closed ways
*/
public List<Way> getClosedWays() {
// Add ways to close the ring
closeWays();
return closedWays;
} }
/** /**
* check if this ring is closed by nature * check if this ring is closed by nature
* @return true if this ring is closed, false otherwise * @return true if this ring is closed, false otherwise
*/ */
public boolean isClosed() { public boolean isClosed() {
closeWays(); mergeWays();
for (int i = closedWays.size()-1; i>=0; i--) { return border.getFirstNodeId() == border.getLastNodeId();
if (!ways.contains(closedWays.get(i))){
return false;
}
}
return true;
} }
/** /**
* get a single closed way that represents the border * get a single closed way that represents the border
* this method is CPU intensive * this method is CPU intensive
* @return a closed way that represents the border * @return a list of Nodes that represents the border
* if the border can't be created, an empty list will be returned
*/ */
public Way getBorder() { public List<Node> getBorder() {
mergeWays(); mergeWays();
return closedBorder; List<Node> l = border.getNodes();
if (border.getNodes().size() != 0 && !isClosed()) {
l.add(border.getNodes().get(0));
}
return l;
} }
/** /**
* Merge all ways from the closedways into a single way * Merge all ways from the into a single border way
* If the original ways are initialized with nodes, the new one will be so too * If the original ways are initialized with nodes, the border will be so too
* If the original ways aren't initialized with nodes, the border won't be either
* If only some original ways are initialized with nodes, the border will only have the nodes of the initialized ways
*/ */
private void mergeWays() { private void mergeWays() {
if (closedBorder != null) return; if (border != null) return;
closeWays();
closedBorder = new Way(0L);
Long previousConnection = getMultiLineEndNodes(closedWays)[0]; //make a copy of the ways
List<Way> ways = new ArrayList<Way>(getWays());
for (Way w : closedWays) { // do we have to include ways with uninitialized nodes?
boolean firstNode = true; // Only if all ways have uninitialized nodes
TLongArrayList nodeIds = w.getNodeIds(); boolean unInitializedNodes = true;
List<Node> nodes = w.getNodes();
if (w.getFirstNodeId() == previousConnection) { for (Way w : ways) {
if (w.getNodes() != null && w.getNodes().size() != 0) {
unInitializedNodes = false;
break;
}
}
for (int i = 0; i< nodeIds.size(); i++) { List<Way> borderWays = new ArrayList<Way>();
// don't need to add the first node, that one was added by the previous way
if (!firstNode) {
if(nodes == null || i>=nodes.size()) { for (Way w : ways) {
closedBorder.addNode(nodeIds.get(i)); // if the way has no nodes initialized, and we should initialize them, continue
if ((w.getNodes() == null || w.getNodes().size() == 0) &&
!unInitializedNodes) continue;
Way newWay = null;
Way addedTo = null;
// merge the Way w with the first borderway suitable;
for (Way borderWay : borderWays) {
if (w.getFirstNodeId() == borderWay.getFirstNodeId()) {
newWay = combineTwoWays(w, borderWay, true, true);
addedTo = borderWay;
break;
} else if (w.getFirstNodeId() == borderWay.getLastNodeId()) {
newWay = combineTwoWays(w, borderWay, true, false);
addedTo = borderWay;
break;
} else if (w.getLastNodeId() == borderWay.getLastNodeId()) {
newWay = combineTwoWays(w, borderWay, false, false);
addedTo = borderWay;
break;
} else if (w.getLastNodeId() == borderWay.getFirstNodeId()) {
newWay = combineTwoWays(w, borderWay, false, true);
addedTo = borderWay;
break;
}
}
if (newWay == null) {
// no suitable borderWay has been found, add this way as one of the boundaries
borderWays.add(w);
} else { } else {
closedBorder.addNode(nodes.get(i)); // ways are combined, remove the original borderway
borderWays.remove(addedTo);
addedTo = null;
// search if it can be combined with something else
for (Way borderWay : borderWays) {
if (newWay.getFirstNodeId() == borderWay.getFirstNodeId()) {
newWay = combineTwoWays(newWay, borderWay, true, true);
addedTo = borderWay;
break;
} else if (newWay.getFirstNodeId() == borderWay.getLastNodeId()) {
newWay = combineTwoWays(newWay, borderWay, true, false);
addedTo = borderWay;
break;
} else if (newWay.getLastNodeId() == borderWay.getLastNodeId()) {
newWay = combineTwoWays(newWay, borderWay, false, false);
addedTo = borderWay;
break;
} else if (newWay.getLastNodeId() == borderWay.getFirstNodeId()) {
newWay = combineTwoWays(newWay, borderWay, false, true);
addedTo = borderWay;
break;
}
}
if (addedTo != null) {
// newWay has enlarged a second time
borderWays.remove(addedTo);
}
// newWay is now a concatenation of 2 or 3 ways, needs to be added to the borderWays
borderWays.add(newWay);
} }
} }
firstNode = false;
if (borderWays.size() != 1) {
border = new Way(randId());
return;
} }
previousConnection = w.getLastNodeId(); border = borderWays.get(0);
} else {
// add the nodes in reverse order return;
for (int i = nodeIds.size() - 1; i >= 0; i--) {
// don't need to add the first node, that one was added by the previous way
if (!firstNode) {
if(nodes == null || i>=nodes.size()) {
closedBorder.addNode(nodeIds.get(i));
} else {
closedBorder.addNode(nodes.get(i));
}
}
firstNode = false;
}
previousConnection = w.getFirstNodeId();
}
}
} }
/** /**
* Check if there exists a cache, if so, return it * check if this Ring contains the node
* If there isn't a cache, sort the ways to form connected strings <p /> * @param n the Node to check
* * @return yes if the node is inside the ring
* If a Ring contains a gap, one way (without initialized nodes and id=0) is added to the list
*/ */
private void closeWays(){ public boolean containsNode(Node n) {
// If the ways have been closed, return the cache return containsPoint(n.getLatitude(), n.getLongitude());
if (closedWays != null) return;
if (ways.size() == 0) {
closedWays = new ArrayList<Way>();
return;
}
closedWays = new ArrayList<Way>(ways);
long[] endNodes = getMultiLineEndNodes(ways);
if (endNodes[0] != endNodes[1]) {
if(ways.get(0).getNodes() == null) {
Way w = new Way(0L);
w.addNode(endNodes[0]);
w.addNode(endNodes[1]);
closedWays.add(w);
} else {
Node n1 = null, n2 = null;
if (ways.get(0).getFirstNodeId() == endNodes[0]) {
n1 = ways.get(0).getNodes().get(0);
} else {
int index = ways.get(0).getNodes().size() - 1;
n1 = ways.get(0).getNodes().get(index);
} }
int lastML = ways.size() - 1; /**
if (ways.get(lastML).getFirstNodeId() == endNodes[0]) { * check if this Ring contains the point
n2 = ways.get(lastML).getNodes().get(0); * @param latitude lat of the point
} else { * @param longitude lon of the point
int index = ways.get(lastML).getNodes().size() - 1; * @return yes if the point is inside the ring
n2 = ways.get(lastML).getNodes().get(index); */
public boolean containsPoint(double latitude, double longitude){
return countIntersections(latitude, longitude) % 2 == 1;
} }
Way w = new Way(0L); /**
w.addNode(n1); * count the intersections when going from lat, lon to outside the ring
w.addNode(n2); * @param latitude the lat to start
closedWays.add(w); * @param longitude the lon to start
* @param intersections the number of intersections to start with
* @return the number of intersections
*/
private int countIntersections(double latitude, double longitude) {
int intersections = 0;
List<Node> polyNodes = getBorder();
if (polyNodes.size() == 0) return 0;
for (int i = 0; i < polyNodes.size() - 1; i++) {
if (MapAlgorithms.ray_intersect_lon(polyNodes.get(i),
polyNodes.get(i + 1), latitude, longitude) != -360d) {
intersections++;
} }
} }
// special handling, also count first and last, might not be closed, but
// we want this!
if (MapAlgorithms.ray_intersect_lon(polyNodes.get(0),
polyNodes.get(polyNodes.size() - 1), latitude, longitude) != -360d) {
intersections++;
}
return intersections;
}
/**
* Check if this is in Ring r
* @param r the ring to check
* @return true if this Ring is inside Ring r
*/
public boolean isIn(Ring r) {
/*
* bi-directional check is needed because some concave rings can intersect
* and would only fail on one of the checks
*/
List<Node> points = this.getBorder();
// r should contain all nodes of this
for(Node n : points) {
if (!r.containsNode(n)) {
return false;
}
}
points = r.getBorder();
// this should not contain a node from r
for(Node n : points) {
if (this.containsNode(n)) {
return false;
}
}
return true;
}
/**
* If this Ring is not complete
* (some ways are not initialized
* because they are not included in the OSM file) <p />
*
* We are trying to close this Ring by using the other Ring.<p />
*
* The other Ring must be complete, and the part of this Ring
* inside the other Ring must also be complete.
* @param other the other Ring (which is complete) used to close this one
*/
public void closeWithOtherRing(Ring other) {
List<Node> thisBorder = getBorder();
List<Integer> thisSwitchPoints = new ArrayList<Integer>();
boolean insideOther = other.containsNode(thisBorder.get(0));
// Search the node pairs for which the ring goes inside or out the other
for (int i = 0; i<thisBorder.size(); i++) {
Node n = thisBorder.get(i);
if (other.containsNode(n) != insideOther) {
// we are getting out or in the boundary now.
// toggle switch
insideOther = !insideOther;
thisSwitchPoints.add(i);
}
}
List<Integer> otherSwitchPoints = new ArrayList<Integer>();
// Search the according node pairs in the other ring
for (int i : thisSwitchPoints) {
LatLon a = thisBorder.get(i-1).getLatLon();
LatLon b = thisBorder.get(i).getLatLon();
otherSwitchPoints.add(crossRingBorder(a, b));
}
return; /*
* TODO:
*
* * Split the other Ring into ways from splitPoint to splitPoint
*
* * Split this ring into ways from splitPoint to splitPoint
*
* * Filter out the parts of way from this that are inside the other Ring
* Use the insideOther var and the switchPoints list for this.
*
* * For each two parts of way from this, search a part of way connecting the two.
* If there are two, take the shortest.
*/
}
/**
* Get the segment of the Ring that intersects a segment
* going from point a to point b
*
* @param a the begin point of the segment
* @param b the end point of the segment
* @return an integer i which is the index so that the segment
* from getBorder().get(i-1) to getBorder().get(i) intersects with
* the segment from parameters a to b. <p />
*
* 0 if the segment from a to b doesn't intersect with the Ring.
*/
public int crossRingBorder(LatLon a, LatLon b) {
List<Node> border = getBorder();
for (int i = 1; i<border.size(); i++) {
LatLon c = border.get(i-1).getLatLon();
LatLon d = border.get(i).getLatLon();
if (MapAlgorithms.linesIntersect(
a.getLatitude(), a.getLongitude(),
b.getLatitude(), b.getLongitude(),
c.getLatitude(), c.getLongitude(),
d.getLatitude(), d.getLongitude())) {
return i;
}
}
return 0;
}
public double getArea() {
if (area == -1) {
//cache the area
area = MapAlgorithms.getArea(getBorder());
}
return area;
}
@Override
/**
* @param r the ring to compare with
* @return -1 if this is smaller than r <br />
* 1 if r is smaller than this <br />
* 0 if they have the same size
*/
public int compareTo(Ring r) {
double thisArea = getArea();
double rArea = r.getArea();
if (thisArea < rArea) return -1;
if (rArea < thisArea) return 1;
return 0;
} }
/** /**
@ -209,7 +391,7 @@ public class Ring {
/* /*
* Check if the way has at least 2 nodes * Check if the way has at least 2 nodes
* *
* FIXME TO LOG OR NOT TO LOG? * TO LOG OR NOT TO LOG?
* *
* logging this creates a whole bunch of log lines for all ways * logging this creates a whole bunch of log lines for all ways
* part of a multipolygon but not in the map * part of a multipolygon but not in the map
@ -327,59 +509,7 @@ public class Ring {
} }
/** /**
* Get the end nodes of a multiLine * Combine a list of ways into a list of rings
* The ways in the multiLine don't have to be initialized for this.
*
* @param multiLine the multiLine to get the end nodes of
* @return an array of size two with the end nodes on both sides. <br />
* * The first node is the end node of the first way in the multiLine. <br />
* * The second node is the end node of the last way in the multiLine.
*/
private long[] getMultiLineEndNodes(ArrayList<Way> multiLine) {
// special case, the multiLine contains only a single way, return the end nodes of the way
if (multiLine.size() == 1){
return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getLastNodeId()};
}
if (multiLine.size() == 2) {
// ring of two elements, arbitrary choice of the end nodes
if(multiLine.get(0).getFirstNodeId() == multiLine.get(1).getFirstNodeId() &&
multiLine.get(0).getLastNodeId() == multiLine.get(1).getLastNodeId()) {
return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getFirstNodeId()};
} else if(multiLine.get(0).getFirstNodeId() == multiLine.get(1).getLastNodeId() &&
multiLine.get(0).getLastNodeId() == multiLine.get(1).getFirstNodeId()) {
return new long[] {multiLine.get(0).getFirstNodeId(), multiLine.get(0).getFirstNodeId()};
}
}
// For all other multiLine lenghts, or for non-closed multiLines with two elements, proceed
long n1 = 0, n2 = 0;
if (multiLine.get(0).getFirstNodeId() == multiLine.get(1).getFirstNodeId() ||
multiLine.get(0).getFirstNodeId() == multiLine.get(1).getLastNodeId()) {
n1 = multiLine.get(0).getLastNodeId();
} else if (multiLine.get(0).getLastNodeId() == multiLine.get(1).getFirstNodeId() ||
multiLine.get(0).getLastNodeId() == multiLine.get(1).getLastNodeId()) {
n1 = multiLine.get(0).getFirstNodeId();
}
int lastIdx = multiLine.size()-1;
if (multiLine.get(lastIdx).getFirstNodeId() == multiLine.get(1).getFirstNodeId() ||
multiLine.get(lastIdx).getFirstNodeId() == multiLine.get(1).getLastNodeId()) {
n2 = multiLine.get(lastIdx).getLastNodeId();
} else if (multiLine.get(lastIdx).getLastNodeId() == multiLine.get(lastIdx - 1).getFirstNodeId() ||
multiLine.get(lastIdx).getLastNodeId() == multiLine.get(lastIdx - 1).getLastNodeId()) {
n2 = multiLine.get(lastIdx).getFirstNodeId();
}
return new long[] {n1, n2};
}
/**
* Combine a list of ways to a list of rings
* *
* The ways must not have initialized nodes for this * The ways must not have initialized nodes for this
* *
@ -400,185 +530,103 @@ public class Ring {
} }
/** /**
* check if this Ring contains the node * get a random long number
* @param n the Node to check * @return
* @return yes if the node is inside the ring
*/ */
public boolean containsNode(Node n) { private static long randId() {
return containsPoint(n.getLatitude(), n.getLongitude()); return Math.round(Math.random()*Long.MIN_VALUE);
} }
/** /**
* check if this Ring contains the point * make a new Way with the nodes from two other ways
* @param latitude lat of the point * @param w1 the first way
* @param longitude lon of the point * @param w2 the second way
* @return yes if the point is inside the ring * @param firstNodeW1 set true if the first node of w1 is also in the other way
* @param firstNodeW2 set true if the first node of w2 is also in the other way
*/ */
public boolean containsPoint(double latitude, double longitude){ private static Way combineTwoWays(Way w1, Way w2, boolean firstNodeW1, boolean firstNodeW2) {
return countIntersections(latitude, longitude) % 2 == 1; Way newWay = new Way(randId());
if(w1.getNodes() != null || w1.getNodes().size() != 0) {
if (firstNodeW1 && firstNodeW2) {
// add the nodes of w1 in reversed order, without the first node
for (int i = w1.getNodes().size() - 1; i>0; i--) {
newWay.addNode(w1.getNodes().get(i));
} }
//add the nodes from w2
/** for (Node n : w2.getNodes()) {
* count the intersections when going from lat, lon to outside the ring newWay.addNode(n);
* @param latitude the lat to start }
* @param longitude the lon to start } else if (firstNodeW1 && !firstNodeW2) {
* @param intersections the number of intersections to start with // add all nodes from w2
* @return the number of intersections for (Node n : w2.getNodes()) {
*/ newWay.addNode(n);
private int countIntersections(double latitude, double longitude) { }
int intersections = 0; // add the nodes from w1, except the first one
for (int i = 1; i < w1.getNodes().size(); i++) {
mergeWays(); newWay.addNode(w1.getNodes().get(i));
List<Node> polyNodes = closedBorder.getNodes(); }
for (int i = 0; i < polyNodes.size() - 1; i++) { } else if (!firstNodeW1 && firstNodeW2) {
if (MapAlgorithms.ray_intersect_lon(polyNodes.get(i), // add all nodes from w1
polyNodes.get(i + 1), latitude, longitude) != -360d) { for (Node n : w1.getNodes()) {
intersections++; newWay.addNode(n);
}
// add the nodes from w2, except the first one
for (int i = 1; i < w2.getNodes().size(); i++) {
newWay.addNode(w2.getNodes().get(i));
}
} else if (!firstNodeW1 && !firstNodeW2) {
// add all nodes from w1
for (Node n : w1.getNodes()) {
newWay.addNode(n);
}
// add the nodes from w2 in reversed order, except the last one
for (int i = w2.getNodes().size() -2 ; i >= 0; i--) {
newWay.addNode(w2.getNodes().get(i));
} }
} }
// special handling, also count first and last, might not be closed, but } else {
// we want this! if (firstNodeW1 && firstNodeW2) {
if (MapAlgorithms.ray_intersect_lon(polyNodes.get(0), // add the nodes of w1 in reversed order, without the first node
polyNodes.get(polyNodes.size() - 1), latitude, longitude) != -360d) { for (int i = w1.getNodeIds().size() - 1; i>0; i--) {
intersections++; newWay.addNode(w1.getNodeIds().get(i));
} }
return intersections; //add the nodes from w2
for (int i = 0; i < w2.getNodeIds().size(); i++) {
newWay.addNode(w2.getNodeIds().get(i));
} }
} else if (firstNodeW1 && !firstNodeW2) {
/** // add all nodes from w2
* collect the points of all ways added by the user <br /> for (int i = 0; i < w2.getNodeIds().size(); i++) {
* automatically added ways because of closing the Ring won't be added <br /> newWay.addNode(w2.getNodeIds().get(i));
* Only ways with initialized points can be handled.
* @return a List with nodes
*/
public List<Node> collectPoints() {
ArrayList<Node> collected = new ArrayList<Node>();
for (Way w : ways) {
collected.addAll(w.getNodes());
} }
// add the nodes from w1, except the first one
return collected; for (int i = 1; i < w1.getNodeIds().size(); i++) {
newWay.addNode(w1.getNodeIds().get(i));
}
} else if (!firstNodeW1 && firstNodeW2) {
// add all nodes from w1
for (int i = 0; i < w1.getNodeIds().size(); i++) {
newWay.addNode(w1.getNodeIds().get(i));
}
// add the nodes from w2, except the first one
for (int i = 1; i < w2.getNodeIds().size(); i++) {
newWay.addNode(w2.getNodeIds().get(i));
}
} else if (!firstNodeW1 && !firstNodeW2) {
// add all nodes from w1
for (int i = 0; i < w1.getNodeIds().size(); i++) {
newWay.addNode(w1.getNodeIds().get(i));
}
// add the nodes from w2 in reversed order, except the last one
for (int i = w2.getNodeIds().size() -2 ; i >= 0; i--) {
newWay.addNode(w2.getNodeIds().get(i));
} }
/**
* Check if this is in Ring r
* @param r the ring to check
* @return true if this Ring is inside Ring r
*/
public boolean isIn(Ring r) {
/*
* bi-directional check is needed because some concave rings can intersect
* and would only fail on one of the checks
*/
List<Node> points = this.collectPoints();
// r should contain all nodes of this
for(Node n : points) {
if (!r.containsNode(n)) {
return false;
} }
} }
points = r.collectPoints(); return newWay;
// this should not contain a node from r
for(Node n : points) {
if (this.containsNode(n)) {
return false;
}
}
return true;
} }
/**
* If this Ring is not complete
* (some ways are not initialized
* because they are not included in the OSM file) <p />
*
* We are trying to close this Ring by using the other Ring.<p />
*
* The other Ring must be complete, and the part of this Ring
* inside the other Ring must also be complete.
* @param other the other Ring (which is complete) used to close this one
*/
public void closeWithOtherRing(Ring other) {
Way thisBorder = getBorder();
List<Integer> thisSwitchPoints = new ArrayList<Integer>();
boolean insideOther = other.containsNode(thisBorder.getNodes().get(0));
// Search the node pairs for which the ring goes inside or out the other
for (int i = 0; i<thisBorder.getNodes().size(); i++) {
Node n = thisBorder.getNodes().get(i);
if (other.containsNode(n) != insideOther) {
// we are getting out or in the boundary now.
// toggle switch
insideOther = !insideOther;
thisSwitchPoints.add(i);
}
}
List<Integer> otherSwitchPoints = new ArrayList<Integer>();
// Search the according node pairs in the other ring
for (int i : thisSwitchPoints) {
LatLon a = thisBorder.getNodes().get(i-1).getLatLon();
LatLon b = thisBorder.getNodes().get(i).getLatLon();
otherSwitchPoints.add(crossRingBorder(a, b));
}
/*
* TODO:
*
* * Split the other Ring into ways from splitPoint to splitPoint
*
* * Split this ring into ways from splitPoint to splitPoint
*
* * Filter out the parts of way from this that are inside the other Ring
* Use the insideOther var and the switchPoints list for this.
*
* * For each two parts of way from this, search a part of way connecting the two.
* If there are two, take the shortest.
*/
}
/**
* Get the segment of the Ring that intersects a segment
* going from point a to point b
*
* @param a the begin point of the segment
* @param b the end point of the segment
* @return an integer i which is the index so that the segment
* from getBorder().get(i-1) to getBorder().get(i) intersects with
* the segment from parameters a to b. <p />
*
* 0 if the segment from a to b doesn't intersect with the Ring.
*/
public int crossRingBorder(LatLon a, LatLon b) {
Way border = getBorder();
for (int i = 1; i<border.getNodes().size(); i++) {
LatLon c = border.getNodes().get(i-1).getLatLon();
LatLon d = border.getNodes().get(i).getLatLon();
//FIXME find library that can do this not java.awt in Android
/*if (Line2D.linesIntersect(
a.getLatitude(), a.getLongitude(),
b.getLatitude(), b.getLongitude(),
c.getLatitude(), c.getLongitude(),
d.getLatitude(), d.getLongitude())) {
return i;
}*/
}
return 0;
}
} }

View file

@ -147,11 +147,6 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator {
} }
} }
// Log if something is wrong
if (!original.hasOpenedPolygons()) {
logMapDataWarn.warn("Multipolygon has unclosed parts: Multipoligon id=" + e.getId()); //$NON-NLS-1$ //$NON-NLS-2$
}
renderingTypes.encodeEntityWithType(e, mapZooms.getLevel(0).getMaxZoom(), typeUse, addtypeUse, namesUse, tempNameUse); renderingTypes.encodeEntityWithType(e, mapZooms.getLevel(0).getMaxZoom(), typeUse, addtypeUse, namesUse, tempNameUse);
//Don't add multipolygons with an unknown type //Don't add multipolygons with an unknown type
@ -169,11 +164,17 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator {
for (Multipolygon m : multipolygons) { for (Multipolygon m : multipolygons) {
if(m.getOuterNodes().size() == 0) {
logMapDataWarn.warn("Multipolygon has an outer ring that can't be formed: "+e.getId());
// don't index this
continue;
}
// innerWays are new closed ways // innerWays are new closed ways
List<List<Node>> innerWays = new ArrayList<List<Node>>(); List<List<Node>> innerWays = new ArrayList<List<Node>>();
for (Ring r : m.getInnerRings()) { for (Ring r : m.getInnerRings()) {
innerWays.add(r.getBorder().getNodes()); innerWays.add(r.getBorder());
} }
// don't use the relation ids. Create new ones // don't use the relation ids. Create new ones