commit
f37049863d
3 changed files with 926 additions and 423 deletions
|
@ -2,16 +2,17 @@ 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 java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.osmand.osm.LatLon;
|
||||
import net.osmand.osm.MapUtils;
|
||||
import net.osmand.osm.Node;
|
||||
import net.osmand.osm.Way;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
/**
|
||||
* The idea of multipolygon:
|
||||
* - we treat each outer way as closed polygon
|
||||
|
@ -23,180 +24,155 @@ import net.osmand.osm.Way;
|
|||
* @author Pavol Zibrita
|
||||
*/
|
||||
public class Multipolygon {
|
||||
|
||||
/**
|
||||
* cache with the ways grouped per Ring
|
||||
*/
|
||||
private SortedSet<Ring> innerRings, outerRings;
|
||||
|
||||
protected List<Way> closedOuterWays;
|
||||
protected List<Way> outerWays;
|
||||
protected List<Way> closedInnerWays;
|
||||
protected List<Way> innerWays;
|
||||
/**
|
||||
* ways added by the user
|
||||
*/
|
||||
private List<Way> outerWays, innerWays;
|
||||
|
||||
protected IdentityHashMap<Way,List<Way>> outerInnerMapping;
|
||||
/**
|
||||
* an optional id of the multipolygon
|
||||
*/
|
||||
private long id;
|
||||
|
||||
private void addNewPolygonPart(List<Way> polygons, List<Way> closedPolygons, Way newPoly) {
|
||||
if (!newPoly.getNodes().isEmpty()) {
|
||||
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;
|
||||
} //else do nothing
|
||||
/**
|
||||
* create a multipolygon with these outer and inner rings
|
||||
* the rings have to be well formed or data inconsistency will happen
|
||||
* @param outerRings the collection of outer rings
|
||||
* @param innerRings the collection of inner rings
|
||||
*/
|
||||
public Multipolygon(SortedSet<Ring> outerRings, SortedSet<Ring> innerRings) {
|
||||
this();
|
||||
this.outerRings = outerRings;
|
||||
this.innerRings = innerRings;
|
||||
for (Ring r : outerRings) {
|
||||
outerWays.addAll(r.getWays());
|
||||
}
|
||||
|
||||
for (Ring r : innerRings) {
|
||||
innerWays.addAll(r.getWays());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClosed(Way newPoly) {
|
||||
List<Node> ns = newPoly.getNodes();
|
||||
return !ns.isEmpty() && ns.get(0).getId() == ns.get(ns.size()-1).getId();
|
||||
/**
|
||||
* Create a multipolygon with initialized outer and inner ways
|
||||
* @param outers a list of outer ways
|
||||
* @param inners a list of inner ways
|
||||
*/
|
||||
public Multipolygon(List<Way> outers, List<Way> inners) {
|
||||
this();
|
||||
outerWays.addAll(outers);
|
||||
innerWays.addAll(inners);
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new empty multipolygon
|
||||
*/
|
||||
public Multipolygon(){
|
||||
outerWays = new ArrayList<Way> ();
|
||||
innerWays = new ArrayList<Way> ();
|
||||
id = 0L;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a new empty multipolygon with specified id
|
||||
* @param id the id to set
|
||||
*/
|
||||
public Multipolygon(long id){
|
||||
this();
|
||||
setId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* set the id of the multipolygon
|
||||
* @param newId id to set
|
||||
*/
|
||||
public void setId(long newId) {
|
||||
id = newId;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the id of the multipolygon
|
||||
* @return id
|
||||
*/
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this multipolygon contains a point
|
||||
* @param point point to check
|
||||
* @return true if this multipolygon is correct and contains the point
|
||||
*/
|
||||
public boolean containsPoint(LatLon point) {
|
||||
|
||||
return containsPoint(point.getLatitude(), point.getLongitude());
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
/**
|
||||
* check if this multipolygon contains a point
|
||||
* @param latitude lat to check
|
||||
* @param longitude lon to check
|
||||
* @return true if this multipolygon is correct and contains the point
|
||||
*/
|
||||
public boolean containsPoint(double latitude, double longitude){
|
||||
|
||||
|
||||
TreeSet<Ring> outers = new TreeSet<Ring>();
|
||||
TreeSet<Ring> inners = new TreeSet<Ring>();
|
||||
|
||||
for (Ring outer : getOuterRings()) {
|
||||
if (outer.containsPoint(latitude, longitude)) {
|
||||
outers.add(outer);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
for(Ring inner : getInnerRings()) {
|
||||
if (inner.containsPoint(latitude, longitude)) {
|
||||
inners.add(inner);
|
||||
}
|
||||
}
|
||||
//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);
|
||||
} else if (!workStack.isEmpty()) {
|
||||
polygons.remove(changedWay);
|
||||
}
|
||||
}
|
||||
|
||||
if(outers.size() == 0) return false;
|
||||
if(inners.size() == 0) return true;
|
||||
|
||||
Ring smallestOuter = outers.first();
|
||||
Ring smallestInner = inners.first();
|
||||
|
||||
// if the smallest outer is in the smallest inner, the multiPolygon contains the point
|
||||
|
||||
return smallestOuter.isIn(smallestInner);
|
||||
|
||||
}
|
||||
|
||||
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) {
|
||||
if(inners == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
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;
|
||||
/**
|
||||
* get the Inner Rings
|
||||
* @return the inner rings
|
||||
*/
|
||||
public SortedSet<Ring> getInnerRings() {
|
||||
groupInRings();
|
||||
return innerRings;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the outer rings
|
||||
* @return outer rings
|
||||
*/
|
||||
public SortedSet<Ring> getOuterRings() {
|
||||
groupInRings();
|
||||
return outerRings;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the outer ways
|
||||
* @return outerWays or empty list if null
|
||||
*/
|
||||
private List<Way> getOuterWays() {
|
||||
if (outerWays == null) {
|
||||
outerWays = new ArrayList<Way>(1);
|
||||
|
@ -204,50 +180,88 @@ public class Multipolygon {
|
|||
return outerWays;
|
||||
}
|
||||
|
||||
private List<Way> getClosedOuterWays() {
|
||||
if (closedOuterWays == null) {
|
||||
closedOuterWays = new ArrayList<Way>(1);
|
||||
}
|
||||
return closedOuterWays;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* get the inner ways
|
||||
* @return innerWays or empty list if null
|
||||
*/
|
||||
private List<Way> getInnerWays() {
|
||||
if (innerWays == null) {
|
||||
innerWays = new ArrayList<Way>(1);
|
||||
}
|
||||
return innerWays;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the number of outer Rings
|
||||
* @return
|
||||
*/
|
||||
public int countOuterPolygons() {
|
||||
|
||||
groupInRings();
|
||||
return zeroSizeIfNull(getOuterRings());
|
||||
|
||||
|
||||
}
|
||||
|
||||
private List<Way> getClosedInnerWays() {
|
||||
if (closedInnerWays == null) {
|
||||
closedInnerWays = new ArrayList<Way>(1);
|
||||
/**
|
||||
* Check if this multiPolygon has outer ways
|
||||
* @return true if this has outer ways
|
||||
*/
|
||||
public boolean hasOpenedPolygons() {
|
||||
return zeroSizeIfNull(getOuterWays()) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* chekc if all rings are closed
|
||||
* @return true if all rings are closed by nature, false otherwise
|
||||
*/
|
||||
public boolean areRingsComplete() {
|
||||
SortedSet<Ring> set = getOuterRings();
|
||||
for (Ring r : set) {
|
||||
if (!r.isClosed()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return closedInnerWays;
|
||||
}
|
||||
|
||||
public int countOuterPolygons()
|
||||
{
|
||||
return zeroSizeIfNull(outerWays) + zeroSizeIfNull(closedOuterWays);
|
||||
}
|
||||
|
||||
public boolean hasOpenedPolygons()
|
||||
{
|
||||
return zeroSizeIfNull(outerWays) != 0;
|
||||
set = getInnerRings();
|
||||
for (Ring r : set) {
|
||||
if (!r.isClosed()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private int zeroSizeIfNull(List<Way> list) {
|
||||
return list != null ? list.size() : 0;
|
||||
/**
|
||||
* return 0 if the list is null
|
||||
* @param l the list to check
|
||||
* @return the size of the list, or 0 if the list is null
|
||||
*/
|
||||
private int zeroSizeIfNull(Collection<?> l) {
|
||||
return l != null ? l.size() : 0;
|
||||
}
|
||||
|
||||
public void addInnerWay(Way es) {
|
||||
addNewPolygonPart(getInnerWays(), getClosedInnerWays(), new Way(es));
|
||||
/**
|
||||
* Add an inner way to the multiPolygon
|
||||
* @param w the way to add
|
||||
*/
|
||||
public void addInnerWay(Way w) {
|
||||
getInnerWays().add(w);
|
||||
innerRings = null;
|
||||
}
|
||||
|
||||
public void addOuterWay(Way es) {
|
||||
addNewPolygonPart(getOuterWays(), getClosedOuterWays(), new Way(es));
|
||||
/**
|
||||
* Add an outer way to the multiPolygon
|
||||
* @param w the way to add
|
||||
*/
|
||||
public void addOuterWay(Way w) {
|
||||
getOuterWays().add(w);
|
||||
outerRings = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add everything from multipolygon to this
|
||||
* @param multipolygon the MultiPolygon to copy
|
||||
*/
|
||||
public void copyPolygonsFrom(Multipolygon multipolygon) {
|
||||
for (Way inner : multipolygon.getInnerWays()) {
|
||||
addInnerWay(inner);
|
||||
|
@ -255,31 +269,111 @@ public class Multipolygon {
|
|||
for (Way outer : multipolygon.getOuterWays()) {
|
||||
addOuterWay(outer);
|
||||
}
|
||||
getClosedInnerWays().addAll(multipolygon.getClosedInnerWays());
|
||||
getClosedOuterWays().addAll(multipolygon.getClosedOuterWays());
|
||||
// reset cache
|
||||
outerRings = null;
|
||||
innerRings = null;
|
||||
}
|
||||
|
||||
public void addOuterWays(List<Way> ring) {
|
||||
for (Way outer : ring) {
|
||||
/**
|
||||
* Add outer ways to the outer Ring
|
||||
* @param ways the ways to add
|
||||
*/
|
||||
public void addOuterWays(List<Way> ways) {
|
||||
for (Way outer : ways) {
|
||||
addOuterWay(outer);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the weighted center of all nodes in this multiPolygon <br />
|
||||
* This only works when the ways have initialized nodes
|
||||
* @return the weighted center
|
||||
*/
|
||||
public LatLon getCenterPoint() {
|
||||
List<Node> points = new ArrayList<Node>();
|
||||
collectPoints(points, outerWays);
|
||||
collectPoints(points, closedOuterWays);
|
||||
collectPoints(points, innerWays);
|
||||
collectPoints(points, closedInnerWays);
|
||||
for (Way w : getOuterWays()) {
|
||||
points.addAll(w.getNodes());
|
||||
}
|
||||
|
||||
for (Way w : getInnerWays()) {
|
||||
points.addAll(w.getNodes());
|
||||
}
|
||||
|
||||
return MapUtils.getWeightCenterForNodes(points);
|
||||
}
|
||||
|
||||
private void collectPoints(List<Node> points, List<Way> polygons) {
|
||||
if (polygons != null) {
|
||||
for(Way w : polygons){
|
||||
points.addAll(w.getNodes());
|
||||
}
|
||||
|
||||
/**
|
||||
* check if a cache has been created
|
||||
* @return true if the cache exists
|
||||
*/
|
||||
public boolean hasCache() {
|
||||
return outerRings != null && innerRings != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the cache <br />
|
||||
* The cache has to be null before it will be created
|
||||
*/
|
||||
private void groupInRings() {
|
||||
if (outerRings == null) {
|
||||
outerRings = Ring.combineToRings(getOuterWays());
|
||||
}
|
||||
if (innerRings == null) {
|
||||
innerRings = Ring.combineToRings(getInnerWays());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Split this multipolygon in several separate multipolygons with one outer ring each
|
||||
* @param log the stream to log problems to, if log = null, nothing will be logged
|
||||
* @return a list with multipolygons which have exactly one outer ring
|
||||
*/
|
||||
public List<Multipolygon> splitPerOuterRing(Log log) {
|
||||
|
||||
//make a clone of the inners set
|
||||
// this set will be changed through execution of the method
|
||||
SortedSet<Ring> inners = new TreeSet<Ring>(getInnerRings());
|
||||
|
||||
// get the set of outer rings in a variable. This set will not be changed
|
||||
SortedSet<Ring> outers = getOuterRings();
|
||||
ArrayList<Multipolygon> multipolygons = new ArrayList<Multipolygon>();
|
||||
|
||||
// loop; start with the smallest outer ring
|
||||
for (Ring outer : outers) {
|
||||
|
||||
// Search the inners inside this outer ring
|
||||
SortedSet<Ring> innersInsideOuter = new TreeSet<Ring>();
|
||||
for (Ring inner : inners) {
|
||||
if (inner.isIn(outer)) {
|
||||
innersInsideOuter.add(inner);
|
||||
}
|
||||
}
|
||||
|
||||
// the inners should belong to this outer, so remove them from the list to check
|
||||
inners.removeAll(innersInsideOuter);
|
||||
|
||||
SortedSet<Ring> thisOuter = new TreeSet<Ring>();
|
||||
thisOuter.add(outer);
|
||||
|
||||
// create a new multipolygon with this outer and a list of inners
|
||||
Multipolygon m = new Multipolygon(thisOuter, innersInsideOuter);
|
||||
|
||||
multipolygons.add(m);
|
||||
}
|
||||
|
||||
if (inners.size() != 0 && log != null)
|
||||
log.warn("Multipolygon "+getId() + " has a mismatch in outer and inner rings");
|
||||
|
||||
return multipolygons;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method only works when the multipolygon has exaclt one outer Ring
|
||||
* @return the list of nodes in the outer ring
|
||||
*/
|
||||
public List<Node> getOuterNodes() {
|
||||
return getOuterRings().first().getBorder().getNodes();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
542
DataExtractionOSM/src/net/osmand/data/Ring.java
Normal file
542
DataExtractionOSM/src/net/osmand/data/Ring.java
Normal file
|
@ -0,0 +1,542 @@
|
|||
package net.osmand.data;
|
||||
|
||||
import gnu.trove.list.array.TLongArrayList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import net.osmand.osm.Node;
|
||||
import net.osmand.osm.Way;
|
||||
|
||||
/**
|
||||
* A ring is a list of ways that form a simple boundary or an area. <p />
|
||||
*
|
||||
*
|
||||
*
|
||||
* @author sander
|
||||
*
|
||||
*/
|
||||
public class Ring implements Comparable<Ring>{
|
||||
/**
|
||||
* This is a list of the ways added by the user
|
||||
* The order can be changed with methods from this class
|
||||
*/
|
||||
private ArrayList<Way> ways;
|
||||
/**
|
||||
* This is the closure of the ways added by the user
|
||||
* So simple two-node ways are added to close the ring
|
||||
* This is a cache from what can calculated with the ways
|
||||
*/
|
||||
private ArrayList<Way> closedWays;
|
||||
/**
|
||||
* This is a single way, consisting of all the nodes
|
||||
* from ways in the closedWays
|
||||
* this is a cache from what can be calculated with the closedWays
|
||||
*/
|
||||
private Way closedBorder;
|
||||
|
||||
/**
|
||||
* Construct a Ring with a list of ways
|
||||
* @param ways the ways that make up the Ring
|
||||
*/
|
||||
public Ring(List<Way> ways) {
|
||||
this.ways = new ArrayList<Way>();
|
||||
this.ways.addAll(ways);
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct an empty Ring
|
||||
*/
|
||||
public Ring() {
|
||||
this.ways = new ArrayList<Way>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the ways added to the Ring.
|
||||
* This is not closed
|
||||
* The order is not fixed
|
||||
* @return the ways added to the Ring
|
||||
*/
|
||||
public List<Way> getWays() {
|
||||
return ways;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a way to the Ring
|
||||
* @param w the way to add
|
||||
*/
|
||||
public void addWay(Way w) {
|
||||
// Reset the cache
|
||||
closedWays = null;
|
||||
closedBorder = null;
|
||||
// Add the way
|
||||
ways.add(w);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @return true if this ring is closed, false otherwise
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
closeWays();
|
||||
for (int i = closedWays.size()-1; i>=0; i--) {
|
||||
if (!ways.contains(closedWays.get(i))){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* get a single closed way that represents the border
|
||||
* this method is CPU intensive
|
||||
* @return a closed way that represents the border
|
||||
*/
|
||||
public Way getBorder() {
|
||||
mergeWays();
|
||||
return closedBorder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge all ways from the closedways into a single way
|
||||
* If the original ways are initialized with nodes, the new one will be so too
|
||||
*/
|
||||
private void mergeWays() {
|
||||
if (closedBorder != null) return;
|
||||
|
||||
closeWays();
|
||||
|
||||
closedBorder = new Way(0L);
|
||||
|
||||
Long previousConnection = getMultiLineEndNodes(closedWays)[0];
|
||||
|
||||
for (Way w : closedWays) {
|
||||
boolean firstNode = true;
|
||||
TLongArrayList nodeIds = w.getNodeIds();
|
||||
List<Node> nodes = w.getNodes();
|
||||
|
||||
if (w.getFirstNodeId() == previousConnection) {
|
||||
|
||||
for (int i = 0; i< nodeIds.size(); 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.getLastNodeId();
|
||||
} else {
|
||||
|
||||
// add the nodes in reverse order
|
||||
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
|
||||
* If there isn't a cache, sort the ways to form connected strings <p />
|
||||
*
|
||||
* If a Ring contains a gap, one way (without initialized nodes and id=0) is added to the list
|
||||
*/
|
||||
private void closeWays(){
|
||||
// If the ways have been closed, return the cache
|
||||
if (closedWays != null) return;
|
||||
if (ways.size() == 0) {
|
||||
closedWays = new ArrayList<Way>();
|
||||
return;
|
||||
}
|
||||
ArrayList<ArrayList<Way>> multiLines = createMultiLines(ways);
|
||||
|
||||
// TODO try to close rings which consist out of multiple segments.
|
||||
// This is a data fault, but it could be solved a bit by OsmAnd
|
||||
if (multiLines.size() != 1) return;
|
||||
|
||||
ArrayList<Way> multiLine = multiLines.get(0);
|
||||
|
||||
closedWays = multiLine;
|
||||
|
||||
long[] endNodes = getMultiLineEndNodes(multiLine);
|
||||
if (endNodes[0] != endNodes[1]) {
|
||||
if(multiLine.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 (multiLine.get(0).getFirstNodeId() == endNodes[0]) {
|
||||
n1 = multiLine.get(0).getNodes().get(0);
|
||||
} else {
|
||||
int index = multiLine.get(0).getNodes().size() - 1;
|
||||
n1 = multiLine.get(0).getNodes().get(index);
|
||||
}
|
||||
|
||||
int lastML = multiLine.size() - 1;
|
||||
if (multiLine.get(lastML).getFirstNodeId() == endNodes[0]) {
|
||||
n2 = multiLine.get(lastML).getNodes().get(0);
|
||||
} else {
|
||||
int index = multiLine.get(lastML).getNodes().size() - 1;
|
||||
n2 = multiLine.get(lastML).getNodes().get(index);
|
||||
}
|
||||
|
||||
Way w = new Way(0L);
|
||||
w.addNode(n1);
|
||||
w.addNode(n2);
|
||||
closedWays.add(w);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Join the ways in connected strings for further processing
|
||||
* @return A list with list of connected ways
|
||||
*/
|
||||
private static ArrayList<ArrayList<Way>> createMultiLines(List<Way> ways){
|
||||
// make a list of multiLines (connecter pieces of way)
|
||||
// One ArrayList<Way> is one multiLine
|
||||
ArrayList<ArrayList<Way>> multiLines = new ArrayList<ArrayList<Way>>();
|
||||
for (Way toAdd : ways) {
|
||||
/*
|
||||
* Check if the way has at least 2 nodes
|
||||
*
|
||||
* FIXME TO LOG OR NOT TO LOG?
|
||||
*
|
||||
* logging this creates a whole bunch of log lines for all ways
|
||||
* part of a multipolygon but not in the map
|
||||
*/
|
||||
if (toAdd.getNodeIds().size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
long toAddBeginPt = toAdd.getFirstNodeId();
|
||||
long toAddEndPt = toAdd.getLastNodeId();
|
||||
|
||||
// the way has been added to this number of multiLines
|
||||
int addedTo = 0;
|
||||
|
||||
// save the first and second changed multiLine
|
||||
ArrayList<Way> firstMultiLine = new ArrayList<Way> ();
|
||||
ArrayList<Way> secondMultiLine = new ArrayList<Way> ();
|
||||
|
||||
|
||||
// iterate over the multiLines, and add the way to the correct one
|
||||
for( ArrayList<Way> multiLine : multiLines) {
|
||||
|
||||
// to check if this multiLine has been changed at the end of the loop
|
||||
int previousAddedTo = addedTo;
|
||||
|
||||
// get the first and last way of a multiLine
|
||||
Way firstWay = multiLine.get(0);
|
||||
Way lastWay = multiLine.get(multiLine.size() - 1);
|
||||
// add the way to the correct multiLines (maybe two)
|
||||
if (toAddBeginPt == firstWay.getFirstNodeId() ||
|
||||
toAddBeginPt == firstWay.getLastNodeId() ||
|
||||
toAddEndPt == firstWay.getFirstNodeId() ||
|
||||
toAddEndPt == firstWay.getLastNodeId() ) {
|
||||
// add the way to the begining to respect order
|
||||
multiLine.add(0, toAdd);
|
||||
addedTo++;
|
||||
} else if (toAddBeginPt == lastWay.getFirstNodeId() ||
|
||||
toAddBeginPt == lastWay.getLastNodeId() ||
|
||||
toAddEndPt == lastWay.getFirstNodeId() ||
|
||||
toAddEndPt == lastWay.getLastNodeId()) {
|
||||
// add the way to the end
|
||||
multiLine.add(toAdd);
|
||||
addedTo++;
|
||||
}
|
||||
|
||||
// save this multiLines if it has been changed
|
||||
if (previousAddedTo != addedTo) {
|
||||
|
||||
if (addedTo == 1) {
|
||||
firstMultiLine = multiLine;
|
||||
}
|
||||
|
||||
if (addedTo == 2) {
|
||||
secondMultiLine = multiLine;
|
||||
}
|
||||
|
||||
// a Ring may never contain a fork
|
||||
// if there is a third multiline, don't process
|
||||
// hope there is a fourth one, sot these two will be processed later on
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If the way is added to nothing, make a new multiLine
|
||||
if (addedTo == 0 ) {
|
||||
ArrayList<Way> multiLine = new ArrayList<Way>();
|
||||
multiLine.add(toAdd);
|
||||
multiLines.add(multiLine);
|
||||
continue;
|
||||
}
|
||||
|
||||
//everything OK
|
||||
if (addedTo == 1) continue;
|
||||
|
||||
|
||||
// only the case addedTo == 2 remains
|
||||
// two multiLines have to be merged
|
||||
|
||||
|
||||
|
||||
if (firstMultiLine.get(firstMultiLine.size() - 1) == secondMultiLine.get(0)) {
|
||||
// add the second to the first
|
||||
secondMultiLine.remove(0) ;
|
||||
for (Way w : secondMultiLine) {
|
||||
firstMultiLine.add(w);
|
||||
}
|
||||
multiLines.remove(secondMultiLine);
|
||||
} else if (secondMultiLine.get(secondMultiLine.size() - 1) == firstMultiLine.get(0)) {
|
||||
// just add the first to the second
|
||||
firstMultiLine.remove(0) ;
|
||||
for (Way w : firstMultiLine) {
|
||||
secondMultiLine.add(w);
|
||||
}
|
||||
multiLines.remove(firstMultiLine);
|
||||
} else if (firstMultiLine.get(0) == secondMultiLine.get(0)) {
|
||||
// add the first in reversed to the beginning of the second
|
||||
firstMultiLine.remove(toAdd);
|
||||
for (Way w : firstMultiLine) {
|
||||
secondMultiLine.add(0,w);
|
||||
}
|
||||
multiLines.remove(firstMultiLine);
|
||||
} else {
|
||||
// add the first in reversed to the end of the second
|
||||
firstMultiLine.remove(toAdd);
|
||||
int index = secondMultiLine.size();
|
||||
for (Way w : firstMultiLine) {
|
||||
secondMultiLine.add(index ,w);
|
||||
}
|
||||
multiLines.remove(firstMultiLine);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return multiLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the end nodes of a multiLine
|
||||
* 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
|
||||
*
|
||||
* @param ways the ways to group
|
||||
* @return a list of Rings
|
||||
*/
|
||||
public static SortedSet<Ring> combineToRings(List<Way> ways){
|
||||
ArrayList<ArrayList<Way>> multiLines = createMultiLines(ways);
|
||||
|
||||
SortedSet<Ring> result = new TreeSet<Ring> ();
|
||||
|
||||
for (ArrayList<Way> multiLine : multiLines) {
|
||||
Ring r = new Ring(multiLine);
|
||||
result.add(r);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this Ring contains the node
|
||||
* @param n the Node to check
|
||||
* @return yes if the node is inside the ring
|
||||
*/
|
||||
public boolean containsNode(Node n) {
|
||||
return containsPoint(n.getLatitude(), n.getLongitude());
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this Ring contains the point
|
||||
* @param latitude lat of the point
|
||||
* @param longitude lon of the point
|
||||
* @return yes if the point is inside the ring
|
||||
*/
|
||||
public boolean containsPoint(double latitude, double longitude){
|
||||
return countIntersections(latitude, longitude) % 2 == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* count the intersections when going from lat, lon to outside the ring
|
||||
* @param latitude the lat to start
|
||||
* @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;
|
||||
|
||||
mergeWays();
|
||||
List<Node> polyNodes = closedBorder.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;
|
||||
}
|
||||
|
||||
/**
|
||||
* collect the points of all ways added by the user <br />
|
||||
* automatically added ways because of closing the Ring won't be added <br />
|
||||
* 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());
|
||||
}
|
||||
|
||||
return collected;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
|
||||
// this should not contain a node from r
|
||||
for(Node n : points) {
|
||||
if (this.containsNode(n)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
/**
|
||||
* @return -1 if this Ring is inside r <br />
|
||||
* 1 if r is inside this Ring <br />
|
||||
* 0 otherwise (Rings are next to each other, Rings intersect or Rings are malformed)
|
||||
*/
|
||||
public int compareTo(Ring r) {
|
||||
if (this.isIn(r)) return -1;
|
||||
if (r.isIn(this)) return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -26,8 +26,9 @@ import net.osmand.Algoritms;
|
|||
import net.osmand.IProgress;
|
||||
import net.osmand.binary.OsmandOdb.MapData;
|
||||
import net.osmand.binary.OsmandOdb.MapDataBlock;
|
||||
import net.osmand.data.Boundary;
|
||||
import net.osmand.data.MapAlgorithms;
|
||||
import net.osmand.data.Multipolygon;
|
||||
import net.osmand.data.Ring;
|
||||
import net.osmand.data.preparation.MapZooms.MapZoomPair;
|
||||
import net.osmand.osm.Entity;
|
||||
import net.osmand.osm.Entity.EntityId;
|
||||
|
@ -113,234 +114,100 @@ public class IndexVectorMapCreator extends AbstractIndexPartCreator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* index a multipolygon into the database
|
||||
* only multipolygons without admin_level and with type=multipolygon are indexed
|
||||
* broken multipolygons are also indexed, inner ways are sometimes left out, broken rings are split and closed
|
||||
* broken multipolygons will normally be logged
|
||||
* @param e the entity to index
|
||||
* @param ctx the database context
|
||||
* @throws SQLException
|
||||
*/
|
||||
private void indexMultiPolygon(Entity e, OsmDbAccessorContext ctx) throws SQLException {
|
||||
if (e instanceof Relation && "multipolygon".equals(e.getTag(OSMTagKey.TYPE))) { //$NON-NLS-1$
|
||||
if(e.getTag(OSMTagKey.ADMIN_LEVEL) != null) {
|
||||
// don't index boundaries as multipolygon (only areas ideally are multipolygon)
|
||||
return;
|
||||
}
|
||||
ctx.loadEntityRelation((Relation) e);
|
||||
Map<Entity, String> entities = ((Relation) e).getMemberEntities();
|
||||
// Don't handle things that aren't multipolygon, and nothing administrative
|
||||
if (! (e instanceof Relation) ||
|
||||
! "multipolygon".equals(e.getTag(OSMTagKey.TYPE)) ||
|
||||
e.getTag(OSMTagKey.ADMIN_LEVEL) != null ) return;
|
||||
|
||||
boolean outerFound = false;
|
||||
for (Entity es : entities.keySet()) {
|
||||
if (es instanceof Way) {
|
||||
boolean inner = "inner".equals(entities.get(es)); //$NON-NLS-1$
|
||||
if (!inner) {
|
||||
outerFound = true;
|
||||
// This is incorrect (it should be intersection of all boundaries)
|
||||
// Currently it causes an issue with coastline (if one line is coastline)
|
||||
// for (String t : es.getTagKeySet()) {
|
||||
// e.putTag(t, es.getTag(t));
|
||||
// }
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!outerFound) {
|
||||
logMapDataWarn.warn("Probably map bug: Multipoligon id=" + e.getId() + " contains only inner ways : "); //$NON-NLS-1$ //$NON-NLS-2$
|
||||
return;
|
||||
}
|
||||
ctx.loadEntityRelation((Relation) e);
|
||||
Map<Entity, String> entities = ((Relation) e).getMemberEntities();
|
||||
|
||||
renderingTypes.encodeEntityWithType(e, mapZooms.getLevel(0).getMaxZoom(), typeUse, addtypeUse, namesUse, tempNameUse);
|
||||
if (typeUse.size() > 0) {
|
||||
List<List<Way>> completedRings = new ArrayList<List<Way>>();
|
||||
List<List<Way>> incompletedRings = new ArrayList<List<Way>>();
|
||||
for (Entity es : entities.keySet()) {
|
||||
if (es instanceof Way) {
|
||||
if (!((Way) es).getNodeIds().isEmpty()) {
|
||||
combineMultiPolygons((Way) es, completedRings, incompletedRings);
|
||||
}
|
||||
}
|
||||
}
|
||||
// skip incompleted rings and do not add whole relation ?
|
||||
if (!incompletedRings.isEmpty()) {
|
||||
logMapDataWarn.warn("In multipolygon " + e.getId() + " there are incompleted ways : " + incompletedRings);
|
||||
return;
|
||||
// completedRings.addAll(incompletedRings);
|
||||
}
|
||||
// create a multipolygon object for this
|
||||
Multipolygon original = new Multipolygon(e.getId());
|
||||
|
||||
// skip completed rings that are not one type
|
||||
for (List<Way> l : completedRings) {
|
||||
boolean innerType = "inner".equals(entities.get(l.get(0))); //$NON-NLS-1$
|
||||
for (Way way : l) {
|
||||
boolean inner = "inner".equals(entities.get(way)); //$NON-NLS-1$
|
||||
if (innerType != inner) {
|
||||
logMapDataWarn
|
||||
.warn("Probably map bug: Multipoligon contains outer and inner ways.\n" + //$NON-NLS-1$
|
||||
"Way:"
|
||||
+ way.getId()
|
||||
+ " is strange part of completed ring. InnerType:" + innerType + " way inner: " + inner + " way inner string:" + entities.get(way)); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// That check is not strictly needed on preproccessing step because client can handle it
|
||||
Node nodeOut = checkOuterWaysEncloseInnerWays(completedRings, entities);
|
||||
if (nodeOut != null) {
|
||||
logMapDataWarn.warn("Map bug: Multipoligon contains 'inner' way point outside of 'outer' border.\n" + //$NON-NLS-1$
|
||||
"Multipolygon id : " + e.getId() + ", inner node out id : " + nodeOut.getId()); //$NON-NLS-1$
|
||||
}
|
||||
|
||||
List<Node> outerWaySrc = new ArrayList<Node>();
|
||||
List<List<Node>> innerWays = new ArrayList<List<Node>>();
|
||||
|
||||
TIntArrayList typeToSave = new TIntArrayList(typeUse);
|
||||
long baseId = 0;
|
||||
for (List<Way> l : completedRings) {
|
||||
boolean innerType = "inner".equals(entities.get(l.get(0))); //$NON-NLS-1$
|
||||
if (!innerType && !outerWaySrc.isEmpty()) {
|
||||
logMapDataWarn.warn("Map bug: Multipoligon contains many 'outer' borders.\n" + //$NON-NLS-1$
|
||||
"Multipolygon id : " + e.getId() + ", outer way id : " + l.get(0).getId()); //$NON-NLS-1$
|
||||
return;
|
||||
}
|
||||
List<Node> toCollect;
|
||||
if (innerType) {
|
||||
toCollect = new ArrayList<Node>();
|
||||
innerWays.add(toCollect);
|
||||
} else {
|
||||
toCollect = outerWaySrc;
|
||||
}
|
||||
|
||||
for (Way way : l) {
|
||||
toCollect.addAll(way.getNodes());
|
||||
if (!innerType) {
|
||||
TIntArrayList out = multiPolygonsWays.put(way.getId(), typeToSave);
|
||||
if(out == null){
|
||||
baseId = -way.getId();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if(baseId == 0){
|
||||
// use base id as well?
|
||||
baseId = notUsedId --;
|
||||
}
|
||||
nextZoom: for (int level = 0; level < mapZooms.size(); level++) {
|
||||
renderingTypes.encodeEntityWithType(e, mapZooms.getLevel(level).getMaxZoom(), typeUse, addtypeUse, namesUse,
|
||||
tempNameUse);
|
||||
if (typeUse.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
long id = convertBaseIdToGeneratedId(baseId, level);
|
||||
// simplify route
|
||||
List<Node> outerWay = outerWaySrc;
|
||||
int zoomToSimplify = mapZooms.getLevel(level).getMaxZoom() - 1;
|
||||
if (zoomToSimplify < 15) {
|
||||
outerWay = simplifyCycleWay(outerWay, zoomToSimplify, zoomWaySmothness);
|
||||
if (outerWay == null) {
|
||||
continue nextZoom;
|
||||
}
|
||||
List<List<Node>> newinnerWays = new ArrayList<List<Node>>();
|
||||
for (List<Node> ls : innerWays) {
|
||||
ls = simplifyCycleWay(ls, zoomToSimplify, zoomWaySmothness);
|
||||
if (ls != null) {
|
||||
newinnerWays.add(ls);
|
||||
}
|
||||
}
|
||||
innerWays = newinnerWays;
|
||||
}
|
||||
insertBinaryMapRenderObjectIndex(mapTree[level], outerWay, innerWays, namesUse, id, true, typeUse, addtypeUse, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Node checkOuterWaysEncloseInnerWays(List<List<Way>> completedRings, Map<Entity, String> entities) {
|
||||
List<List<Way>> innerWays = new ArrayList<List<Way>>();
|
||||
Boundary outerBoundary = new Boundary();
|
||||
Node toReturn = null;
|
||||
for (List<Way> ring : completedRings) {
|
||||
boolean innerType = "inner".equals(entities.get(ring.get(0))); //$NON-NLS-1$
|
||||
if (!innerType) {
|
||||
outerBoundary.addOuterWays(ring);
|
||||
} else {
|
||||
innerWays.add(ring);
|
||||
}
|
||||
}
|
||||
|
||||
for (List<Way> innerRing : innerWays) {
|
||||
ring: for (Way innerWay : innerRing) {
|
||||
for (Node node : innerWay.getNodes()) {
|
||||
if (!outerBoundary.containsPoint(node.getLatitude(), node.getLongitude())) {
|
||||
if (toReturn == null) {
|
||||
toReturn = node;
|
||||
}
|
||||
completedRings.remove(innerRing);
|
||||
break ring;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return toReturn;
|
||||
}
|
||||
|
||||
private List<Way> reverse(List<Way> l) {
|
||||
Collections.reverse(l);
|
||||
for(Way w : l){
|
||||
w.getNodeIds().reverse();
|
||||
Collections.reverse(w.getNodes());
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
private List<Way> appendLists(List<Way> w1, List<Way> w2){
|
||||
w1.addAll(w2);
|
||||
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();
|
||||
if (fId == lId) {
|
||||
completedRings.add(Collections.singletonList(w));
|
||||
} else {
|
||||
List<Way> l = new ArrayList<Way>();
|
||||
l.add(w);
|
||||
boolean add = true;
|
||||
for (int k = 0; k < incompletedRings.size();) {
|
||||
boolean remove = false;
|
||||
List<Way> i = incompletedRings.get(k);
|
||||
Way last = i.get(i.size() - 1);
|
||||
Way first = i.get(0);
|
||||
long lastId = last.getEntityIds().get(last.getEntityIds().size() - 1).getId().longValue();
|
||||
long firstId = first.getEntityIds().get(0).getId().longValue();
|
||||
if (fId == lastId) {
|
||||
remove = true;
|
||||
l = appendLists(i, l);
|
||||
fId = firstId;
|
||||
} else if (lId == firstId) {
|
||||
l = appendLists(l, i);
|
||||
remove = true;
|
||||
lId = lastId;
|
||||
} else if (lId == lastId) {
|
||||
l = appendLists(l, reverse(i));
|
||||
remove = true;
|
||||
lId = firstId;
|
||||
} else if (fId == firstId) {
|
||||
l = appendLists(reverse(i), l);
|
||||
remove = true;
|
||||
fId = lastId;
|
||||
}
|
||||
if (remove) {
|
||||
incompletedRings.remove(k);
|
||||
// fill the multipolygon with all ways from the Relation
|
||||
for (Entity es : entities.keySet()) {
|
||||
if (es instanceof Way) {
|
||||
boolean inner = "inner".equals(entities.get(es)); //$NON-NLS-1$
|
||||
if (inner) {
|
||||
original.addInnerWay((Way) es);
|
||||
} else {
|
||||
k++;
|
||||
}
|
||||
if (fId == lId) {
|
||||
completedRings.add(l);
|
||||
add = false;
|
||||
break;
|
||||
original.addOuterWay((Way) es);
|
||||
}
|
||||
}
|
||||
if (add) {
|
||||
incompletedRings.add(l);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
//Don't add multipolygons with an unknown type
|
||||
if (typeUse.size() == 0) return;
|
||||
|
||||
// Log the fact that Rings aren't complete, but continue with the relation, try to close it as well as possible
|
||||
if (!original.areRingsComplete()) {
|
||||
logMapDataWarn.warn("In multipolygon " + e.getId() + " there are incompleted ways");
|
||||
}
|
||||
// Rings with different types (inner or outer) in one ring will be logged in the previous case
|
||||
// The Rings are only composed by type, so if one way gets in a different Ring, the rings will be incomplete
|
||||
|
||||
List<Multipolygon> multipolygons = original.splitPerOuterRing(logMapDataWarn);
|
||||
|
||||
|
||||
for (Multipolygon m : multipolygons) {
|
||||
|
||||
// innerWays are new closed ways
|
||||
List<List<Node>> innerWays = new ArrayList<List<Node>>();
|
||||
|
||||
for (Ring r : m.getInnerRings()) {
|
||||
innerWays.add(r.getBorder().getNodes());
|
||||
}
|
||||
|
||||
// don't use the relation ids. Create new ones
|
||||
long baseId = notUsedId --;
|
||||
nextZoom: for (int level = 0; level < mapZooms.size(); level++) {
|
||||
renderingTypes.encodeEntityWithType(e, mapZooms.getLevel(level).getMaxZoom(), typeUse, addtypeUse, namesUse,
|
||||
tempNameUse);
|
||||
if (typeUse.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
long id = convertBaseIdToGeneratedId(baseId, level);
|
||||
// simplify route
|
||||
List<Node> outerWay = m.getOuterNodes();
|
||||
int zoomToSimplify = mapZooms.getLevel(level).getMaxZoom() - 1;
|
||||
if (zoomToSimplify < 15) {
|
||||
outerWay = simplifyCycleWay(outerWay, zoomToSimplify, zoomWaySmothness);
|
||||
if (outerWay == null) {
|
||||
continue nextZoom;
|
||||
}
|
||||
List<List<Node>> newinnerWays = new ArrayList<List<Node>>();
|
||||
for (List<Node> ls : innerWays) {
|
||||
ls = simplifyCycleWay(ls, zoomToSimplify, zoomWaySmothness);
|
||||
if (ls != null) {
|
||||
newinnerWays.add(ls);
|
||||
}
|
||||
}
|
||||
innerWays = newinnerWays;
|
||||
}
|
||||
insertBinaryMapRenderObjectIndex(mapTree[level], outerWay, innerWays, namesUse, id, true, typeUse, addtypeUse, true);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static List<Node> simplifyCycleWay(List<Node> ns, int zoom, int zoomWaySmothness) throws SQLException {
|
||||
if (checkForSmallAreas(ns, zoom + Math.min(zoomWaySmothness / 2, 3), 2, 4)) {
|
||||
return null;
|
||||
|
|
Loading…
Reference in a new issue