Extracted Multipolygon class from Boundary class and tested with junits.

This could resolve some address issues, we will need to check. (recreate
maps, etc...)
This commit is contained in:
Pavol Zibrita 2012-07-15 04:53:37 +02:00
parent 547b156c5b
commit 16922dffc8
5 changed files with 467 additions and 166 deletions

View file

@ -0,0 +1,173 @@
package net.osmand.data;
import static org.junit.Assert.*;
import net.osmand.osm.LatLon;
import net.osmand.osm.Node;
import net.osmand.osm.Way;
import org.junit.Before;
import org.junit.Test;
public class MultipolygonTest {
private Multipolygon testee;
private Way poly1_1_of_2;
private Way poly1_2_of_2;
private int wayid;
private Way poly2;
private Way openedBaseCircle;
private Way closedBaseCircle;
@Before
public void setUp()
{
testee = new Multipolygon();
poly1_1_of_2 = polygon(n(0,0),n(1,0),n(1,1),n(1,2));
poly1_2_of_2 = polygon(n(1,2),n(0,2),n(-1,2),n(0,0));
poly2 = polygon(n(4,4), n(4,5), n(3,5), n(4,4));
openedBaseCircle = polygon(n(1,-1), n(1,1), n(-1,1), n(-1,-1));
closedBaseCircle = polygon(n(1,-1), n(1,1), n(-1,1), n(-1,-1), n(1,-1));
}
public Way polygon(Node... n) {
Way way = new Way(wayid++);
for (Node nn : n) {
way.addNode(nn);
}
return way;
}
public Way scale(int i, Way w) {
Way way = new Way(wayid++);
for (Node nn : w.getNodes()) {
way.addNode(n(i*(int)nn.getLatitude(),i*(int)nn.getLongitude()));
}
return way;
}
public Way move(int i, int j, Way w) {
Way way = new Way(wayid++);
for (Node nn : w.getNodes()) {
way.addNode(n(i+(int)nn.getLatitude(),j+(int)nn.getLongitude()));
}
return way;
}
public Node n(int i, int j) {
return new Node(i, j, i*i + j*j + i*j + i + j); //Node has ID derived from i,j
}
@Test
public void test_twoWayPolygon() {
testee.addOuterWay(poly1_1_of_2);
testee.addOuterWay(poly1_2_of_2);
assertEquals(1, testee.countOuterPolygons());
assertFalse(testee.hasOpenedPolygons());
}
@Test
public void test_oneWayPolygon() {
testee.addOuterWay(poly2);
assertEquals(1, testee.countOuterPolygons());
assertFalse(testee.hasOpenedPolygons());
}
@Test
public void test_containsPoint()
{
testee.addOuterWay(scale(4,poly2));
LatLon center = testee.getCenterPoint();
assertTrue(testee.containsPoint(center));
}
@Test
public void test_containsPointOpenedCircle()
{
testee.addOuterWay(scale(4,openedBaseCircle));
LatLon center = testee.getCenterPoint();
assertTrue(testee.containsPoint(center));
}
@Test
public void test_containsPointClosedCircle()
{
testee.addOuterWay(scale(4,openedBaseCircle));
LatLon center = testee.getCenterPoint();
assertTrue(testee.containsPoint(center));
}
@Test
public void test_oneInnerRingOneOuterRingOpenedCircle()
{
test_oneInnerRingOneOuterRing(openedBaseCircle);
}
@Test
public void test_oneInnerRingOneOuterRingClosedCircle()
{
test_oneInnerRingOneOuterRing(closedBaseCircle);
}
public void test_oneInnerRingOneOuterRing(Way polygon)
{
testee.addOuterWay(scale(4,polygon));
LatLon center = testee.getCenterPoint();
assertTrue(testee.containsPoint(center));
Multipolygon mpoly2 = new Multipolygon();
mpoly2.addOuterWay(polygon);
assertTrue(testee.containsPoint(mpoly2.getCenterPoint()));
testee.addInnerWay(polygon);
assertFalse(testee.containsPoint(mpoly2.getCenterPoint()));
}
@Test
public void test_twoInnerRingsOneOuterRingOpenedCircle()
{
test_twoInnerRingsOneOuterRing(openedBaseCircle);
}
@Test
public void test_twoInnerRingsOneOuterRingClosedCircle()
{
test_twoInnerRingsOneOuterRing(closedBaseCircle);
}
public void test_twoInnerRingsOneOuterRing(Way polygon)
{
testee.addOuterWay(scale(40,polygon));
LatLon center = testee.getCenterPoint();
assertTrue(testee.containsPoint(center));
Multipolygon mpoly2 = new Multipolygon();
mpoly2.addOuterWay(polygon);
Multipolygon movepoly2 = new Multipolygon();
movepoly2.addOuterWay(move(10,10,polygon));
assertTrue(testee.containsPoint(mpoly2.getCenterPoint()));
assertTrue(testee.containsPoint(movepoly2.getCenterPoint()));
testee.addInnerWay(polygon);
testee.addInnerWay(move(10,10,polygon));
assertFalse(testee.containsPoint(mpoly2.getCenterPoint()));
assertFalse(testee.containsPoint(movepoly2.getCenterPoint()));
}
@Test
public void test_multipolygon1twoWay2oneWay()
{
testee.addOuterWay(poly1_1_of_2);
testee.addOuterWay(poly1_2_of_2);
testee.addOuterWay(poly2);
assertEquals(2, testee.countOuterPolygons());
assertFalse(testee.hasOpenedPolygons());
}
}

View file

@ -1,146 +1,18 @@
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 Boundary {
public class Boundary
extends Multipolygon
{
private long boundaryId;
private String name;
private int adminLevel;
// not necessary ready rings
private List<Way> outerWays = new ArrayList<Way>(1);
private List<Way> innerWays = new ArrayList<Way>(0);
private boolean closedWay;
private long adminCenterId;
public Boundary(boolean closedWay){
this.closedWay = closedWay;
}
public boolean isClosedWay() {
return closedWay;
}
public void setClosedWay(boolean closedWay) {
this.closedWay = closedWay;
}
public boolean computeIsClosedWay() {
if (getOuterWays().size() > 0) {
// now we try to merge the ways until we have only one
int oldSize = 0;
while (getOuterWays().size() != oldSize && !getOuterWays().isEmpty()) {
oldSize = getOuterWays().size();
mergeOuterWays();
}
if (!getOuterWays().isEmpty()) {
// there is one way and last element is equal to the first...
List<Node> nodes = getOuterWays().get(0).getNodes();
closedWay = getOuterWays().size() == 1 && nodes.get(0).getId() == nodes.get(nodes.size() - 1).getId();
//if not closed, but we have only one way, make it close
if (!closedWay && getOuterWays().size() == 1) {
nodes.add(nodes.get(0));
closedWay = true;
}
}
} else {
closedWay = false;
}
return closedWay;
}
private void mergeOuterWays() {
Way way = getOuterWays().get(0);
List<Node> nodes = way.getNodes();
if (!nodes.isEmpty()) {
int nodesSize = nodes.size();
Node first = nodes.get(0);
Node last = nodes.get(nodesSize-1);
int size = getOuterWays().size();
for (int i = size-1; i >= 1; i--) {
//try to find way, that matches the one ...
Way anotherWay = getOuterWays().get(i);
if (anotherWay.getNodes().isEmpty()) {
//remove empty one...
getOuterWays().remove(i);
} else {
if (anotherWay.getNodes().get(0).getId() == first.getId()) {
//reverese this way and add it to the actual
Collections.reverse(anotherWay.getNodes());
way.getNodes().addAll(0,anotherWay.getNodes());
getOuterWays().remove(i);
} else if (anotherWay.getNodes().get(0).getId() == last.getId()) {
way.getNodes().addAll(anotherWay.getNodes());
getOuterWays().remove(i);
} else if (anotherWay.getNodes().get(anotherWay.getNodes().size()-1).getId() == first.getId()) {
//add at begging
way.getNodes().addAll(0,anotherWay.getNodes());
getOuterWays().remove(i);
} else if (anotherWay.getNodes().get(anotherWay.getNodes().size()-1).getId() == last.getId()) {
Collections.reverse(anotherWay.getNodes());
way.getNodes().addAll(anotherWay.getNodes());
getOuterWays().remove(i);
}
}
}
} else {
//remove way with no nodes!
getOuterWays().remove(0);
}
}
public boolean containsPoint(LatLon point) {
return containsPoint(point.getLatitude(), point.getLongitude());
}
public boolean containsPoint(double latitude, double longitude) {
int intersections = 0;
for(Way w : outerWays){
for(int i=0; i<w.getNodes().size() - 1; i++){
if(MapAlgorithms.ray_intersect_lon(w.getNodes().get(i), w.getNodes().get(i+1), latitude, longitude) != -360d){
intersections ++;
}
}
}
for(Way w : innerWays){
for(int i=0; i<w.getNodes().size() - 1; i++){
if(MapAlgorithms.ray_intersect_lon(w.getNodes().get(i), w.getNodes().get(i+1), latitude, longitude) != -360d){
intersections ++;
}
}
}
return intersections % 2 == 1;
}
public LatLon getCenterPoint(){
List<Node> points = new ArrayList<Node>();
for(Way w : outerWays){
points.addAll(w.getNodes());
}
for(Way w : innerWays){
points.addAll(w.getNodes());
}
return MapUtils.getWeightCenterForNodes(points);
}
private List<Way> getOuterWays() {
return outerWays;
}
private List<Way> getInnerWays() {
return innerWays;
public Boundary() {
}
public long getBoundaryId() {
@ -169,7 +41,7 @@ public class Boundary {
@Override
public String toString() {
return getName() + " alevel:" + getAdminLevel() + " type: relation closed:" + isClosedWay();
return getName() + " alevel:" + getAdminLevel() + " type: has opened polygons:" + hasOpenedPolygons() + " no. of outer polygons:" + countOuterPolygons();
}
public void setAdminCenterId(long l) {
@ -180,21 +52,4 @@ public class Boundary {
return adminCenterId;
}
public void addInnerWay(Way es) {
innerWays.add(new Way(es));
}
public void addOuterWay(Way es) {
outerWays.add(new Way(es));
}
public void copyWaysFrom(Boundary boundary) {
getInnerWays().addAll(boundary.getInnerWays());
getOuterWays().addAll(boundary.getOuterWays());
}
public void addOuterWays(List<Way> ring) {
outerWays.addAll(ring);
}
}

View file

@ -0,0 +1,278 @@
package net.osmand.data;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Stack;
import net.osmand.osm.LatLon;
import net.osmand.osm.MapUtils;
import net.osmand.osm.Node;
import net.osmand.osm.Way;
/**
* The idea of multipolygon:
* - we treat each outer way as closed polygon
* - multipolygon is always closed!
* - each way we try to assign to existing way and form
* so a more complex polygon
* - number of outer ways, is number of polygons
*
* @author Pavol Zibrita
*/
public class Multipolygon {
protected List<Way> closedOuterWays;
protected List<Way> outerWays;
protected List<Way> closedInnerWays;
protected List<Way> innerWays;
protected IdentityHashMap<Way,List<Way>> outerInnerMapping;
private void addNewPolygonPart(List<Way> polygons, List<Way> closedPolygons, Way newPoly) {
if (isClosed(newPoly)) {
closedPolygons.add(newPoly); //if closed, put directly to closed polygons
} else if (polygons.isEmpty()) {
polygons.add(newPoly); //if open, and first, put to polygons..
} else {
// now we try to merge the ways to form bigger polygons
Stack<Way> wayStack = new Stack<Way>();
wayStack.push(newPoly);
addAndMergePolygon(polygons, closedPolygons, wayStack);
}
//reset the mapping
outerInnerMapping = null;
}
private boolean isClosed(Way newPoly) {
List<Node> ns = newPoly.getNodes();
return !ns.isEmpty() && ns.get(0).getId() == ns.get(ns.size()-1).getId();
}
private void addAndMergePolygon(List<Way> polygons, List<Way> closedPolygons, Stack<Way> workStack) {
while (!workStack.isEmpty()) {
Way changedWay = workStack.pop();
List<Node> nodes = changedWay.getNodes();
if (nodes.isEmpty()) {
//don't bother with it!
continue;
}
if (isClosed(changedWay)) {
polygons.remove(changedWay);
closedPolygons.add(changedWay);
continue;
}
Node first = nodes.get(0);
Node last = nodes.get(nodes.size()-1);
for (Way anotherWay : polygons) {
if (anotherWay == changedWay) {
continue;
}
//try to find way, that matches the one ...
if (anotherWay.getNodes().get(0).getId() == first.getId()) {
Collections.reverse(changedWay.getNodes());
anotherWay.getNodes().addAll(0,changedWay.getNodes());
workStack.push(anotherWay);
break;
} else if (anotherWay.getNodes().get(0).getId() == last.getId()) {
anotherWay.getNodes().addAll(0,changedWay.getNodes());
workStack.push(anotherWay);
break;
} else if (anotherWay.getNodes().get(anotherWay.getNodes().size()-1).getId() == first.getId()) {
anotherWay.getNodes().addAll(changedWay.getNodes());
workStack.push(anotherWay);
break;
} else if (anotherWay.getNodes().get(anotherWay.getNodes().size()-1).getId() == last.getId()) {
Collections.reverse(changedWay.getNodes());
anotherWay.getNodes().addAll(changedWay.getNodes());
workStack.push(anotherWay);
break;
}
}
//if we could not merge the new polygon, and it is not already there, add it!
if (workStack.isEmpty() && !polygons.contains(changedWay)) {
polygons.add(changedWay);
}
}
}
public boolean containsPoint(LatLon point) {
return containsPoint(point.getLatitude(), point.getLongitude());
}
public boolean containsPoint(double latitude, double longitude) {
return containsPointInPolygons(closedOuterWays, latitude, longitude) || containsPointInPolygons(outerWays, latitude, longitude);
}
private boolean containsPointInPolygons(List<Way> outerPolygons, double latitude, double longitude) {
if (outerPolygons != null) {
for (Way polygon : outerPolygons) {
List<Way> inners = getOuterInnerMapping().get(polygon);
if (polygonContainsPoint(latitude, longitude, polygon, inners)) {
return true;
}
}
}
return false;
}
private boolean polygonContainsPoint(double latitude, double longitude,
Way polygon, List<Way> inners) {
int intersections = 0;
intersections = countIntersections(latitude, longitude, polygon,
intersections);
if (inners != null) {
for (Way w : inners) {
intersections = countIntersections(latitude, longitude, w,
intersections);
}
}
return intersections % 2 == 1;
}
private int countIntersections(double latitude, double longitude,
Way polygon, int intersections) {
List<Node> polyNodes = polygon.getNodes();
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;
}
private IdentityHashMap<Way, List<Way>> getOuterInnerMapping() {
if (outerInnerMapping == null) {
outerInnerMapping = new IdentityHashMap<Way, List<Way>>();
//compute the mapping
if ((innerWays != null || closedInnerWays != null)
&& countOuterPolygons() != 0) {
fillOuterInnerMapping(closedOuterWays);
fillOuterInnerMapping(outerWays);
}
}
return outerInnerMapping;
}
private void fillOuterInnerMapping(List<Way> outerPolygons) {
for (Way outer : outerPolygons) {
List<Way> inners = new ArrayList<Way>();
inners.addAll(findInnersFor(outer, innerWays));
inners.addAll(findInnersFor(outer, closedInnerWays));
outerInnerMapping.put(outer, inners);
}
}
private Collection<Way> findInnersFor(Way outer, List<Way> inners) {
List<Way> result = new ArrayList<Way>(inners.size());
for (Way in : inners) {
boolean inIsIn = true;
for (Node n : in.getNodes()) {
if (!polygonContainsPoint(n.getLatitude(), n.getLongitude(), outer, null)) {
inIsIn = false;
break;
}
}
if (inIsIn) {
result.add(in);
}
}
return result;
}
private List<Way> getOuterWays() {
if (outerWays == null) {
outerWays = new ArrayList<Way>(1);
}
return outerWays;
}
private List<Way> getClosedOuterWays() {
if (closedOuterWays == null) {
closedOuterWays = new ArrayList<Way>(1);
}
return closedOuterWays;
}
private List<Way> getInnerWays() {
if (innerWays == null) {
innerWays = new ArrayList<Way>(1);
}
return innerWays;
}
private List<Way> getClosedInnerWays() {
if (closedInnerWays == null) {
closedInnerWays = new ArrayList<Way>(1);
}
return closedInnerWays;
}
public int countOuterPolygons()
{
return zeroSizeIfNull(outerWays) + zeroSizeIfNull(closedOuterWays);
}
public boolean hasOpenedPolygons()
{
return zeroSizeIfNull(outerWays) != 0;
}
private int zeroSizeIfNull(List<Way> list) {
return list != null ? list.size() : 0;
}
public void addInnerWay(Way es) {
addNewPolygonPart(getInnerWays(), getClosedInnerWays(), new Way(es));
}
public void addOuterWay(Way es) {
addNewPolygonPart(getOuterWays(), getClosedOuterWays(), new Way(es));
}
public void copyPolygonsFrom(Multipolygon multipolygon) {
for (Way inner : multipolygon.getInnerWays()) {
addInnerWay(inner);
}
for (Way outer : multipolygon.getOuterWays()) {
addOuterWay(outer);
}
getClosedInnerWays().addAll(multipolygon.getClosedInnerWays());
getClosedOuterWays().addAll(multipolygon.getClosedOuterWays());
}
public void addOuterWays(List<Way> ring) {
for (Way outer : ring) {
addOuterWay(outer);
}
}
public LatLon getCenterPoint() {
List<Node> points = new ArrayList<Node>();
collectPoints(points, outerWays);
collectPoints(points, closedOuterWays);
collectPoints(points, innerWays);
collectPoints(points, closedInnerWays);
return MapUtils.getWeightCenterForNodes(points);
}
private void collectPoints(List<Node> points, List<Way> polygons) {
if (polygons != null) {
for(Way w : polygons){
points.addAll(w.getNodes());
}
}
}
}

View file

@ -33,6 +33,7 @@ import net.osmand.data.City;
import net.osmand.data.City.CityType;
import net.osmand.data.DataTileManager;
import net.osmand.data.MapObject;
import net.osmand.data.Multipolygon;
import net.osmand.data.Street;
import net.osmand.data.WayBoundary;
import net.osmand.data.preparation.DBStreetDAO.SimpleStreet;
@ -134,7 +135,7 @@ public class IndexAddressCreator extends AbstractIndexPartCreator{
public void indexBoundariesRelation(Entity e, OsmDbAccessorContext ctx) throws SQLException {
Boundary boundary = extractBoundary(e, ctx);
if (boundary != null && boundary.isClosedWay() && boundary.getAdminLevel() >= 4 && boundary.getCenterPoint() != null && !Algoritms.isEmpty(boundary.getName())) {
if (boundary != null && boundary.getAdminLevel() >= 4 && boundary.getCenterPoint() != null && !Algoritms.isEmpty(boundary.getName())) {
LatLon boundaryCenter = boundary.getCenterPoint();
List<City> citiesToSearch = new ArrayList<City>();
citiesToSearch.addAll(cityManager.getClosestObjects(boundaryCenter.getLatitude(), boundaryCenter.getLongitude(), 3));
@ -170,11 +171,11 @@ public class IndexAddressCreator extends AbstractIndexPartCreator{
putCityBoundary(boundary, cityFound);
}
allBoundaries.add(boundary);
} else if (boundary != null && !boundary.isClosedWay()){
} else if (boundary != null){
if(logMapDataWarn != null) {
logMapDataWarn.warn("Not using opened boundary: " + boundary);
logMapDataWarn.warn("Not using boundary: " + boundary);
} else {
log.info("Not using opened boundary: " + boundary);
log.info("Not using boundary: " + boundary);
}
}
}
@ -261,9 +262,7 @@ public class IndexAddressCreator extends AbstractIndexPartCreator{
&& oldBoundary != boundary
&& boundary.getName().equalsIgnoreCase(
oldBoundary.getName())) {
if (!oldBoundary.isClosedWay() && !boundary.isClosedWay()) {
oldBoundary.copyWaysFrom(boundary);
}
oldBoundary.copyPolygonsFrom(boundary);
}
} else {
cityBoundaries.put(cityFound, boundary);
@ -299,7 +298,7 @@ public class IndexAddressCreator extends AbstractIndexPartCreator{
if (e instanceof Relation) {
Relation aRelation = (Relation) e;
ctx.loadEntityRelation(aRelation);
boundary = new Boundary(true); //is computed later
boundary = new Boundary(); //is computed later
boundary.setName(aRelation.getTag(OSMTagKey.NAME));
boundary.setBoundaryId(aRelation.getId());
boundary.setAdminLevel(extractBoundaryAdminLevel(aRelation));
@ -321,14 +320,9 @@ public class IndexAddressCreator extends AbstractIndexPartCreator{
boundary.setAdminCenterId(es.getId());
}
}
boundary.computeIsClosedWay();
} else if (e instanceof Way) {
if (!visitedBoundaryWays.contains(e.getId())) {
boolean closed = false;
if(((Way) e).getNodeIds().size() > 1){
closed = ((Way) e).getFirstNodeId() == ((Way) e).getLastNodeId();
}
boundary = new WayBoundary(closed);
boundary = new WayBoundary();
boundary.setName(e.getTag(OSMTagKey.NAME));
boundary.setBoundaryId(e.getId());
boundary.setAdminLevel(extractBoundaryAdminLevel(e));
@ -543,7 +537,7 @@ public class IndexAddressCreator extends AbstractIndexPartCreator{
nearestObjects.addAll(cityVillageManager.getClosestObjects(location.getLatitude(),location.getLongitude()));
//either we found a city boundary the street is in
for (City c : nearestObjects) {
Boundary boundary = cityBoundaries.get(c);
Multipolygon boundary = cityBoundaries.get(c);
if (isInNames.contains(c.getName()) || (boundary != null && boundary.containsPoint(location))) {
result.add(c);
}

View file

@ -222,7 +222,7 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator {
private Node checkOuterWaysEncloseInnerWays(List<List<Way>> completedRings, Map<Entity, String> entities) {
List<List<Way>> innerWays = new ArrayList<List<Way>>();
Boundary outerBoundary = new Boundary(true);
Boundary outerBoundary = new Boundary();
Node toReturn = null;
for (List<Way> ring : completedRings) {
boolean innerType = "inner".equals(entities.get(ring.get(0))); //$NON-NLS-1$
@ -263,6 +263,7 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator {
return w1;
}
//TODO Can the Multipolygon class be the one that replaces this?
private void combineMultiPolygons(Way w, List<List<Way>> completedRings, List<List<Way>> incompletedRings) {
long lId = w.getEntityIds().get(w.getEntityIds().size() - 1).getId().longValue();
long fId = w.getEntityIds().get(0).getId().longValue();