fix multipolygon center for map and poi
This commit is contained in:
parent
e6613ddb12
commit
6a56535c9f
4 changed files with 960 additions and 9 deletions
242
OsmAnd-java/src/main/java/net/osmand/data/Multipolygon.java
Normal file
242
OsmAnd-java/src/main/java/net/osmand/data/Multipolygon.java
Normal file
|
@ -0,0 +1,242 @@
|
|||
package net.osmand.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||
import com.vividsolutions.jts.geom.LinearRing;
|
||||
import com.vividsolutions.jts.geom.MultiPolygon;
|
||||
import com.vividsolutions.jts.geom.Polygon;
|
||||
import net.osmand.osm.edit.Node;
|
||||
import net.osmand.osm.edit.OsmMapUtils;
|
||||
import net.osmand.util.Algorithms;
|
||||
|
||||
public class Multipolygon {
|
||||
private List<Ring> innerRings, outerRings;
|
||||
private Map<Ring, Set<Ring>> containedInnerInOuter = new LinkedHashMap<Ring, Set<Ring>>();
|
||||
|
||||
private float maxLat = -90;
|
||||
private float minLat = 90;
|
||||
private float maxLon = -180;
|
||||
private float minLon = 180;
|
||||
private long id;
|
||||
|
||||
|
||||
public Multipolygon(List<Ring> outer, List<Ring> inner, long id) {
|
||||
outerRings = outer;
|
||||
innerRings = inner;
|
||||
this.id = id;
|
||||
updateRings();
|
||||
}
|
||||
|
||||
public Multipolygon(Ring outer, List<Ring> inner, long id, boolean checkedIsIn) {
|
||||
outerRings = new ArrayList<Ring>();
|
||||
outerRings.add(outer);
|
||||
innerRings = inner;
|
||||
this.id = id;
|
||||
updateRings(checkedIsIn);
|
||||
}
|
||||
|
||||
public MultiPolygon toMultiPolygon() {
|
||||
GeometryFactory geometryFactory = new GeometryFactory();
|
||||
MultiPolygon emptyMultiPolygon = geometryFactory.createMultiPolygon(new Polygon[0]);
|
||||
List<Polygon> polygons = new ArrayList<>();
|
||||
for (Ring outerRing : outerRings) {
|
||||
if (!outerRing.isClosed()) {
|
||||
return emptyMultiPolygon;
|
||||
}
|
||||
List<LinearRing> innerLinearRings = new ArrayList<>();
|
||||
Set<Ring> innerRings = containedInnerInOuter.get(outerRing);
|
||||
if (!Algorithms.isEmpty(innerRings)) {
|
||||
for (Ring innerRing : innerRings) {
|
||||
if (!innerRing.isClosed()) {
|
||||
return emptyMultiPolygon;
|
||||
}
|
||||
innerLinearRings.add(innerRing.toLinearRing());
|
||||
}
|
||||
}
|
||||
polygons.add(geometryFactory.createPolygon(outerRing.toLinearRing(), innerLinearRings.toArray(new LinearRing[innerLinearRings.size()])));
|
||||
}
|
||||
return geometryFactory.createMultiPolygon(polygons.toArray(new Polygon[polygons.size()]));
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
private void updateRings() {
|
||||
updateRings(false);
|
||||
}
|
||||
|
||||
private void updateRings(boolean checkedIsIn) {
|
||||
maxLat = -90;
|
||||
minLat = 90;
|
||||
maxLon = -180;
|
||||
minLon = 180;
|
||||
for (Ring r : outerRings) {
|
||||
for (Node n : r.getBorder()) {
|
||||
maxLat = (float) Math.max(maxLat, n.getLatitude());
|
||||
minLat = (float) Math.min(minLat, n.getLatitude());
|
||||
maxLon = (float) Math.max(maxLon, n.getLongitude());
|
||||
minLon = (float) Math.min(minLon, n.getLongitude());
|
||||
}
|
||||
}
|
||||
// keep sorted
|
||||
Collections.sort(outerRings);
|
||||
for (Ring inner : innerRings) {
|
||||
HashSet<Ring> outContainingRings = new HashSet<Ring>();
|
||||
if (checkedIsIn && outerRings.size() == 1) {
|
||||
outContainingRings.add(outerRings.get(0));
|
||||
} else {
|
||||
for (Ring out : outerRings) {
|
||||
if (inner.isIn(out)) {
|
||||
outContainingRings.add(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
containedInnerInOuter.put(inner, outContainingRings);
|
||||
}
|
||||
// keep sorted
|
||||
Collections.sort(innerRings);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
// fast check
|
||||
if (maxLat + 0.3 < latitude || minLat - 0.3 > latitude ||
|
||||
maxLon + 0.3 < longitude || minLon - 0.3 > longitude) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Ring containedInOuter = null;
|
||||
// use a sortedset to get the smallest outer containing the point
|
||||
for (Ring outer : outerRings) {
|
||||
if (outer.containsPoint(latitude, longitude)) {
|
||||
containedInOuter = outer;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (containedInOuter == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//use a sortedSet to get the smallest inner Ring
|
||||
Ring containedInInner = null;
|
||||
for (Ring inner : innerRings) {
|
||||
if (inner.containsPoint(latitude, longitude)) {
|
||||
containedInInner = inner;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (containedInInner == null) return true;
|
||||
if (outerRings.size() == 1) {
|
||||
// return immediately false
|
||||
return false;
|
||||
}
|
||||
|
||||
// if it is both, in an inner and in an outer, check if the inner is indeed the smallest one
|
||||
Set<Ring> s = containedInnerInOuter.get(containedInInner);
|
||||
if (s == null) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
return !s.contains(containedInOuter);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
|
||||
}
|
||||
|
||||
public int countOuterPolygons() {
|
||||
return zeroSizeIfNull(outerRings);
|
||||
}
|
||||
|
||||
private int zeroSizeIfNull(Collection<?> l) {
|
||||
return l != null ? l.size() : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>();
|
||||
for (Ring w : outerRings) {
|
||||
points.addAll(w.getBorder());
|
||||
}
|
||||
if (points.isEmpty()) {
|
||||
for (Ring w : innerRings) {
|
||||
points.addAll(w.getBorder());
|
||||
}
|
||||
}
|
||||
|
||||
return OsmMapUtils.getWeightCenterForNodes(points);
|
||||
}
|
||||
|
||||
public void mergeWith(Multipolygon multipolygon) {
|
||||
innerRings.addAll(multipolygon.innerRings);
|
||||
outerRings.addAll(multipolygon.outerRings);
|
||||
updateRings();
|
||||
}
|
||||
|
||||
public boolean hasOpenedPolygons() {
|
||||
return !areRingsComplete();
|
||||
}
|
||||
|
||||
public boolean areRingsComplete() {
|
||||
List<Ring> l = outerRings;
|
||||
for (Ring r : l) {
|
||||
if (!r.isClosed()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
l = innerRings;
|
||||
for (Ring r : l) {
|
||||
if (!r.isClosed()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public QuadRect getLatLonBbox() {
|
||||
if(minLat == 90) {
|
||||
return new QuadRect();
|
||||
}
|
||||
return new QuadRect(minLon, maxLat, maxLon, minLat);
|
||||
}
|
||||
|
||||
|
||||
public List<Ring> getInnerRings() {
|
||||
return innerRings;
|
||||
}
|
||||
|
||||
public List<Ring> getOuterRings() {
|
||||
return outerRings;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,278 @@
|
|||
package net.osmand.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
|
||||
import gnu.trove.map.hash.TLongObjectHashMap;
|
||||
import net.osmand.osm.edit.Node;
|
||||
import net.osmand.osm.edit.Way;
|
||||
import net.osmand.util.MapUtils;
|
||||
|
||||
/**
|
||||
* 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 MultipolygonBuilder {
|
||||
|
||||
/* package */ List<Way> outerWays = new ArrayList<Way>();
|
||||
/* package */ List<Way> innerWays = new ArrayList<Way>();
|
||||
|
||||
long id;
|
||||
|
||||
/**
|
||||
* Create a multipolygon with initialized outer and inner ways
|
||||
*
|
||||
* @param outers a list of outer ways
|
||||
* @param inners a list of inner ways
|
||||
*/
|
||||
public MultipolygonBuilder(List<Way> outers, List<Way> inners) {
|
||||
this();
|
||||
outerWays.addAll(outers);
|
||||
innerWays.addAll(inners);
|
||||
}
|
||||
|
||||
public MultipolygonBuilder() {
|
||||
id = -1L;
|
||||
}
|
||||
|
||||
public void setId(long newId) {
|
||||
id = newId;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public MultipolygonBuilder addInnerWay(Way w) {
|
||||
innerWays.add(w);
|
||||
return this;
|
||||
}
|
||||
|
||||
public List<Way> getOuterWays() {
|
||||
return outerWays;
|
||||
}
|
||||
|
||||
public List<Way> getInnerWays() {
|
||||
return innerWays;
|
||||
}
|
||||
|
||||
public MultipolygonBuilder addOuterWay(Way w) {
|
||||
outerWays.add(w);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
SortedSet<Ring> inners = new TreeSet<Ring>(combineToRings(innerWays));
|
||||
ArrayList<Ring> outers = combineToRings(outerWays);
|
||||
ArrayList<Multipolygon> multipolygons = new ArrayList<Multipolygon>();
|
||||
// loop; start with the smallest outer ring
|
||||
for (Ring outer : outers) {
|
||||
ArrayList<Ring> innersInsideOuter = new ArrayList<Ring>();
|
||||
Iterator<Ring> innerIt = inners.iterator();
|
||||
while (innerIt.hasNext()) {
|
||||
Ring inner = innerIt.next();
|
||||
if (inner.isIn(outer)) {
|
||||
innersInsideOuter.add(inner);
|
||||
innerIt.remove();
|
||||
}
|
||||
}
|
||||
multipolygons.add(new Multipolygon(outer, innersInsideOuter, id, true));
|
||||
}
|
||||
|
||||
if (inners.size() != 0 && log != null) {
|
||||
log.warn("Multipolygon " + getId() + " has a mismatch in outer and inner rings");
|
||||
}
|
||||
|
||||
return multipolygons;
|
||||
}
|
||||
|
||||
public Multipolygon build() {
|
||||
return new Multipolygon(combineToRings(outerWays), combineToRings(innerWays), id);
|
||||
}
|
||||
|
||||
public ArrayList<Ring> combineToRings(List<Way> ways) {
|
||||
// make a list of multiLines (connecter pieces of way)
|
||||
TLongObjectHashMap<List<Way>> multilineStartPoint = new TLongObjectHashMap<List<Way>>();
|
||||
TLongObjectHashMap<List<Way>> multilineEndPoint = new TLongObjectHashMap<List<Way>>();
|
||||
for (Way toAdd : ways) {
|
||||
if (toAdd.getNodeIds().size() < 2) {
|
||||
continue;
|
||||
}
|
||||
// iterate over the multiLines, and add the way to the correct one
|
||||
Way changedWay = toAdd;
|
||||
Way newWay;
|
||||
do {
|
||||
newWay = merge(multilineStartPoint, getLastId(changedWay), changedWay,
|
||||
multilineEndPoint, getFirstId(changedWay));
|
||||
if(newWay == null) {
|
||||
newWay = merge(multilineEndPoint, getFirstId(changedWay), changedWay,
|
||||
multilineStartPoint, getLastId(changedWay));
|
||||
}
|
||||
if(newWay == null) {
|
||||
newWay = merge(multilineStartPoint, getFirstId(changedWay), changedWay,
|
||||
multilineEndPoint, getLastId(changedWay));
|
||||
}
|
||||
if(newWay == null) {
|
||||
newWay = merge(multilineEndPoint, getLastId(changedWay), changedWay,
|
||||
multilineStartPoint, getFirstId(changedWay));
|
||||
}
|
||||
if(newWay != null) {
|
||||
changedWay = newWay;
|
||||
}
|
||||
} while (newWay != null);
|
||||
|
||||
addToMap(multilineStartPoint, getFirstId(changedWay), changedWay);
|
||||
addToMap(multilineEndPoint, getLastId(changedWay), changedWay);
|
||||
|
||||
}
|
||||
|
||||
List<Way> multiLines = new ArrayList<Way>();
|
||||
for(List<Way> lst : multilineStartPoint.valueCollection()) {
|
||||
multiLines.addAll(lst);
|
||||
}
|
||||
ArrayList<Ring> result = new ArrayList<Ring>();
|
||||
for (Way multiLine : multiLines) {
|
||||
Ring r = new Ring(multiLine);
|
||||
result.add(r);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Way merge(TLongObjectHashMap<List<Way>> endMap, long stNodeId, Way changedWay,
|
||||
TLongObjectHashMap<List<Way>> startMap, long endNodeId) {
|
||||
List<Way> lst = endMap.get(stNodeId);
|
||||
if(lst != null && lst.size() > 0) {
|
||||
Way candToMerge = lst.get(0);
|
||||
Way newWay = combineTwoWaysIfHasPoints(candToMerge, changedWay);
|
||||
List<Way> otherLst = startMap.get(
|
||||
getLastId(candToMerge) == stNodeId ? getFirstId(candToMerge) : getLastId(candToMerge));
|
||||
boolean removed1 = lst.remove(candToMerge) ;
|
||||
boolean removed2 = otherLst != null && otherLst.remove(candToMerge);
|
||||
if(newWay == null || !removed1 || !removed2) {
|
||||
throw new UnsupportedOperationException("Can't merge way: " + changedWay.getId() + " " + stNodeId + " -> " + endNodeId);
|
||||
}
|
||||
return newWay;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void addToMap(TLongObjectHashMap<List<Way>> mp, long id, Way changedWay) {
|
||||
List<Way> lst = mp.get(id);
|
||||
if(lst == null) {
|
||||
lst = new ArrayList<>();
|
||||
mp.put(id, lst);
|
||||
}
|
||||
lst.add(changedWay);
|
||||
}
|
||||
|
||||
|
||||
private long getId(Node n) {
|
||||
if(n == null ) {
|
||||
return - nextRandId();
|
||||
}
|
||||
long l = MapUtils.get31TileNumberY(n.getLatitude());
|
||||
l = (l << 31) | MapUtils.get31TileNumberX(n.getLongitude());
|
||||
return l;
|
||||
}
|
||||
|
||||
/**
|
||||
* make a new Way with the nodes from two other ways
|
||||
*
|
||||
* @param w1 the first way
|
||||
* @param w2 the second way
|
||||
* @return null if it is not possible
|
||||
*/
|
||||
private Way combineTwoWaysIfHasPoints(Way w1, Way w2) {
|
||||
boolean combine = true;
|
||||
boolean firstReverse = false;
|
||||
boolean secondReverse = false;
|
||||
long w1f = getFirstId(w1);
|
||||
long w2f = getFirstId(w2);
|
||||
long w1l = getLastId(w1);
|
||||
long w2l = getLastId(w2);
|
||||
if (w1f == w2f) {
|
||||
firstReverse = true;
|
||||
secondReverse = false;
|
||||
} else if (w1l == w2f) {
|
||||
firstReverse = false;
|
||||
secondReverse = false;
|
||||
} else if (w1l == w2l) {
|
||||
firstReverse = false;
|
||||
secondReverse = true;
|
||||
} else if (w1f == w2l) {
|
||||
firstReverse = true;
|
||||
secondReverse = true;
|
||||
} else {
|
||||
combine = false;
|
||||
}
|
||||
if (combine) {
|
||||
Way newWay = new Way(nextRandId());
|
||||
boolean nodePresent = w1.getNodes() != null || w1.getNodes().size() != 0;
|
||||
int w1size = nodePresent ? w1.getNodes().size() : w1.getNodeIds().size();
|
||||
for (int i = 0; i < w1size; i++) {
|
||||
int ind = firstReverse ? (w1size - 1 - i) : i;
|
||||
if (nodePresent) {
|
||||
newWay.addNode(w1.getNodes().get(ind));
|
||||
} else {
|
||||
newWay.addNode(w1.getNodeIds().get(ind));
|
||||
}
|
||||
}
|
||||
int w2size = nodePresent ? w2.getNodes().size() : w2.getNodeIds().size();
|
||||
for (int i = 1; i < w2size; i++) {
|
||||
int ind = secondReverse ? (w2size - 1 - i) : i;
|
||||
if (nodePresent) {
|
||||
newWay.addNode(w2.getNodes().get(ind));
|
||||
} else {
|
||||
newWay.addNode(w2.getNodeIds().get(ind));
|
||||
}
|
||||
}
|
||||
return newWay;
|
||||
}
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private long getLastId(Way w1) {
|
||||
return w1.getLastNodeId() > 0 ? w1.getLastNodeId(): getId(w1.getLastNode());
|
||||
}
|
||||
|
||||
private long getFirstId(Way w1) {
|
||||
return w1.getFirstNodeId() > 0 ? w1.getFirstNodeId(): getId(w1.getFirstNode());
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static long initialValue = -1000;
|
||||
private final static long randomInterval = 5000;
|
||||
|
||||
/**
|
||||
* get a random long number
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
private static long nextRandId() {
|
||||
// exclude duplicates in one session (!) and be quazirandom every run
|
||||
long val = initialValue - Math.round(Math.random() * randomInterval);
|
||||
initialValue = val;
|
||||
return val;
|
||||
}
|
||||
|
||||
}
|
369
OsmAnd-java/src/main/java/net/osmand/data/Ring.java
Normal file
369
OsmAnd-java/src/main/java/net/osmand/data/Ring.java
Normal file
|
@ -0,0 +1,369 @@
|
|||
package net.osmand.data;
|
||||
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import com.vividsolutions.jts.geom.Coordinate;
|
||||
import com.vividsolutions.jts.geom.CoordinateList;
|
||||
import com.vividsolutions.jts.geom.GeometryFactory;
|
||||
import com.vividsolutions.jts.geom.LinearRing;
|
||||
import net.osmand.osm.edit.Node;
|
||||
import net.osmand.osm.edit.OsmMapUtils;
|
||||
import net.osmand.osm.edit.Way;
|
||||
import net.osmand.util.MapAlgorithms;
|
||||
|
||||
/**
|
||||
* A ring is a list of CONTIGUOUS 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 final ArrayList<Way> ways;
|
||||
|
||||
private static final int INDEX_RING_NODES_FAST_CHECK = 100;
|
||||
private static final int INDEX_SIZE = 100;
|
||||
private double[] indexedRingIntervals = null;
|
||||
private List<Node>[] indexedRingNodes = null;
|
||||
|
||||
/**
|
||||
* a concatenation of the ways to form the border
|
||||
* this is NOT necessarily a CLOSED way
|
||||
* The id is random, so this may never leave the Ring object
|
||||
*/
|
||||
private Way border;
|
||||
|
||||
/**
|
||||
* area can be asked a lot of times when comparing rings, cache it
|
||||
*/
|
||||
private double area = -1;
|
||||
|
||||
|
||||
/**
|
||||
* Construct a Ring with a list of ways
|
||||
*
|
||||
* @param ways the ways that make up the Ring
|
||||
*/
|
||||
Ring(Way w) {
|
||||
border = w;
|
||||
indexForFastCheck();
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void indexForFastCheck() {
|
||||
if(border.getNodes().size() > INDEX_RING_NODES_FAST_CHECK) {
|
||||
// calculate min/max lat
|
||||
double maxLat = Double.MIN_VALUE;
|
||||
double minLat = Double.MAX_VALUE;
|
||||
Node lastNode = null;
|
||||
for(Node n : border.getNodes()) {
|
||||
if(n == null) {
|
||||
continue;
|
||||
}
|
||||
lastNode = n;
|
||||
if(n.getLatitude() > maxLat) {
|
||||
maxLat = n.getLatitude();
|
||||
} else if(n.getLatitude() < minLat) {
|
||||
minLat = n.getLatitude();
|
||||
}
|
||||
}
|
||||
maxLat += 0.0001;
|
||||
minLat -= 0.0001;
|
||||
// create interval array [minLat, minLat+interval, ..., maxLat]
|
||||
double interval = (maxLat - minLat) / (INDEX_SIZE - 1);
|
||||
indexedRingIntervals = new double[INDEX_SIZE];
|
||||
indexedRingNodes = new List[INDEX_SIZE];
|
||||
for(int i = 0; i < INDEX_SIZE; i++) {
|
||||
indexedRingIntervals[i] = minLat + i * interval;
|
||||
indexedRingNodes[i] = new ArrayList<Node>();
|
||||
}
|
||||
// split nodes by intervals
|
||||
Node prev = lastNode;
|
||||
for(int i = 0; i < border.getNodes().size(); i++) {
|
||||
Node current = border.getNodes().get(i);
|
||||
if(current == null) {
|
||||
continue;
|
||||
}
|
||||
int i1 = getIndexedLessOrEq(current.getLatitude());
|
||||
int i2 = getIndexedLessOrEq(prev.getLatitude());
|
||||
int min, max;
|
||||
if(i1 > i2) {
|
||||
min = i2;
|
||||
max = i1;
|
||||
} else {
|
||||
min = i1;
|
||||
max = i2;
|
||||
}
|
||||
for (int j = min; j <= max; j++) {
|
||||
indexedRingNodes[j].add(prev);
|
||||
indexedRingNodes[j].add(current);
|
||||
}
|
||||
prev = current;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private int getIndexedLessOrEq(double latitude) {
|
||||
int ind1 = Arrays.binarySearch(indexedRingIntervals, latitude);
|
||||
if(ind1 < 0) {
|
||||
ind1 = -(ind1 + 1);
|
||||
}
|
||||
return ind1;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if this ring is closed by nature
|
||||
*
|
||||
* @return true if this ring is closed, false otherwise
|
||||
*/
|
||||
public boolean isClosed() {
|
||||
return border.getFirstNodeId() == border.getLastNodeId();
|
||||
}
|
||||
|
||||
public List<Node> getBorder() {
|
||||
return border.getNodes();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if(indexedRingIntervals != null) {
|
||||
int intersections = 0;
|
||||
int indx = getIndexedLessOrEq(latitude);
|
||||
if(indx == 0 || indx >= indexedRingNodes.length) {
|
||||
return false;
|
||||
}
|
||||
List<Node> lst = indexedRingNodes[indx];
|
||||
for (int k = 0; k < lst.size(); k += 2) {
|
||||
Node first = lst.get(k);
|
||||
Node last = lst.get(k + 1);
|
||||
if (OsmMapUtils.ray_intersect_lon(first, last, latitude, longitude) != -360.0d) {
|
||||
intersections++;
|
||||
}
|
||||
}
|
||||
return intersections % 2 == 1;
|
||||
}
|
||||
return MapAlgorithms.containsPoint(getBorder(), latitude, longitude);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is in Ring r
|
||||
* @param r the ring to check
|
||||
* @return true if this Ring is inside Ring r (false if it is undetermined)
|
||||
*/
|
||||
public boolean speedIsIn(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();
|
||||
if(points.size() < 2) {
|
||||
return false;
|
||||
}
|
||||
double minlat = points.get(0).getLatitude();
|
||||
double maxlat = points.get(0).getLatitude();
|
||||
double minlon = points.get(0).getLongitude();
|
||||
double maxlon = points.get(0).getLongitude();
|
||||
// r should contain all nodes of this
|
||||
for (Node n : points) {
|
||||
minlat = Math.min(n.getLatitude(), minlat);
|
||||
maxlat = Math.max(n.getLatitude(), maxlat);
|
||||
minlon = Math.min(n.getLongitude(), minlon);
|
||||
maxlon = Math.max(n.getLongitude(), maxlon);
|
||||
}
|
||||
|
||||
// r should contain all nodes of this
|
||||
if (!r.containsPoint(minlat, minlon)) {
|
||||
return false;
|
||||
}
|
||||
if (!r.containsPoint(maxlat, minlon)) {
|
||||
return false;
|
||||
}
|
||||
if (!r.containsPoint(minlat, maxlon)) {
|
||||
return false;
|
||||
}
|
||||
if (!r.containsPoint(maxlat, maxlon)) {
|
||||
return false;
|
||||
}
|
||||
// this should not contain a node from r
|
||||
for (Node n : r.getBorder()) {
|
||||
if(n.getLatitude() > minlat && n.getLatitude() < maxlat &&
|
||||
n.getLongitude() > minlon && n.getLongitude() < maxlon) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if(speedIsIn(r)) {
|
||||
return true;
|
||||
}
|
||||
/*
|
||||
* 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(getTheSegmentRingIntersectsSegment(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 />
|
||||
* <p>
|
||||
* 0 if the segment from a to b doesn't intersect with the Ring.
|
||||
*/
|
||||
private int getTheSegmentRingIntersectsSegment(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 = OsmMapUtils.getArea(getBorder());
|
||||
}
|
||||
return area;
|
||||
}
|
||||
|
||||
public LinearRing toLinearRing() {
|
||||
GeometryFactory geometryFactory = new GeometryFactory();
|
||||
CoordinateList coordinates = new CoordinateList();
|
||||
for (Node node : border.getNodes()) {
|
||||
coordinates.add(new Coordinate(node.getLatitude(), node.getLongitude()), true);
|
||||
}
|
||||
coordinates.closeRing();
|
||||
return geometryFactory.createLinearRing(coordinates.toCoordinateArray());
|
||||
}
|
||||
|
||||
/**
|
||||
* Use area size as comparable metric
|
||||
*/
|
||||
@Override
|
||||
public int compareTo(Ring r) {
|
||||
return Double.compare(getArea(), r.getArea());
|
||||
}
|
||||
}
|
|
@ -8,11 +8,17 @@ import java.util.List;
|
|||
import java.util.PriorityQueue;
|
||||
|
||||
import net.osmand.data.LatLon;
|
||||
import net.osmand.data.Multipolygon;
|
||||
import net.osmand.data.MultipolygonBuilder;
|
||||
import net.osmand.data.Ring;
|
||||
import net.osmand.osm.edit.Relation.RelationMember;
|
||||
import net.osmand.util.Algorithms;
|
||||
import net.osmand.util.MapAlgorithms;
|
||||
import net.osmand.util.MapUtils;
|
||||
|
||||
public class OsmMapUtils {
|
||||
|
||||
private static final double POLY_CENTER_PRECISION= 1e-6;
|
||||
|
||||
public static double getDistance(Node e1, Node e2) {
|
||||
return MapUtils.getDistance(e1.getLatitude(), e1.getLongitude(), e2.getLatitude(), e2.getLongitude());
|
||||
|
@ -33,6 +39,53 @@ public class OsmMapUtils {
|
|||
return getWeightCenterForWay(((Way) e));
|
||||
} else if (e instanceof Relation) {
|
||||
List<LatLon> list = new ArrayList<LatLon>();
|
||||
if (e.getTag("type").equals("multipolygon")) {
|
||||
MultipolygonBuilder original = new MultipolygonBuilder();
|
||||
original.setId(e.getId());
|
||||
|
||||
// fill the multipolygon with all ways from the Relation
|
||||
for (RelationMember es : ((Relation) e).getMembers()) {
|
||||
if (es.getEntity() instanceof Way) {
|
||||
boolean inner = "inner".equals(es.getRole()); //$NON-NLS-1$
|
||||
if (inner) {
|
||||
original.addInnerWay((Way) es.getEntity());
|
||||
} else if("outer".equals(es.getRole())){
|
||||
original.addOuterWay((Way) es.getEntity());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<Multipolygon> multipolygons = original.splitPerOuterRing(null);
|
||||
|
||||
List<List<LatLon>> rings = new ArrayList<List<LatLon>>();
|
||||
if (!Algorithms.isEmpty(multipolygons)){
|
||||
Multipolygon m = multipolygons.get(0);
|
||||
Ring out = m.getOuterRings().get(0);
|
||||
if(out.getBorder().size() != 0) {
|
||||
List<LatLon> ringll = new ArrayList<>();
|
||||
for (Node n : out.getBorder()) {
|
||||
ringll.add(n.getLatLon());
|
||||
}
|
||||
rings.add(ringll);
|
||||
|
||||
for (Ring r : m.getInnerRings()) {
|
||||
List<LatLon> innerRingll = new ArrayList<>();
|
||||
if (r.getBorder().size() == 0) {
|
||||
continue;
|
||||
}
|
||||
for (Node n : r.getBorder()) {
|
||||
ringll.add(n.getLatLon());
|
||||
}
|
||||
rings.add(ringll);
|
||||
}
|
||||
|
||||
}
|
||||
if (!Algorithms.isEmpty(rings)) {
|
||||
return getPolylabelPoint(rings);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (RelationMember fe : ((Relation) e).getMembers()) {
|
||||
LatLon c = null;
|
||||
// skip relations to avoid circular dependencies
|
||||
|
@ -48,16 +101,26 @@ public class OsmMapUtils {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static LatLon getComplexPolyCenter(Collection<Node> nodes) {
|
||||
double precision = 1e-5; //where to set precision constant?
|
||||
|
||||
public static LatLon getComplexPolyCenter(Collection<Node> outer, List<List<Node>> inner) {
|
||||
final List<List<LatLon>> rings = new ArrayList<>();
|
||||
List<LatLon> outerRing = new ArrayList<>();
|
||||
for (Node n : nodes) {
|
||||
|
||||
for (Node n : outer) {
|
||||
outerRing.add(new LatLon(n.getLatitude(), n.getLongitude()));
|
||||
}
|
||||
rings.add(outerRing);
|
||||
return getPolylabelPoint(rings, precision);
|
||||
if (!Algorithms.isEmpty(inner)) {
|
||||
for (List<Node> ring: inner) {
|
||||
if (!Algorithms.isEmpty(ring)) {
|
||||
List <LatLon> ringll = new ArrayList<LatLon>();
|
||||
for (Node n : ring) {
|
||||
ringll.add(n.getLatLon());
|
||||
}
|
||||
rings.add(ringll);
|
||||
}
|
||||
}
|
||||
}
|
||||
return getPolylabelPoint(rings);
|
||||
}
|
||||
|
||||
public static LatLon getWeightCenter(Collection<LatLon> nodes) {
|
||||
|
@ -109,7 +172,7 @@ public class OsmMapUtils {
|
|||
area = false;
|
||||
}
|
||||
}
|
||||
LatLon ll = area ? getComplexPolyCenter(nodes) : getWeightCenterForNodes(nodes);
|
||||
LatLon ll = area ? getComplexPolyCenter(nodes, null) : getWeightCenterForNodes(nodes);
|
||||
if(ll == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -464,11 +527,10 @@ public class OsmMapUtils {
|
|||
/**
|
||||
* Calculate "visual" center point of polygons (based on Mapbox' polylabel algorithm)
|
||||
* @param rings - list of lists of nodes
|
||||
* @param precision - precision of calculation, should be small, like 1e-4 or less.
|
||||
* @return coordinates of calculated center
|
||||
*/
|
||||
|
||||
private static LatLon getPolylabelPoint(List<List<LatLon>> rings, double precision) {
|
||||
public static LatLon getPolylabelPoint(List<List<LatLon>> rings) {
|
||||
// find the bounding box of the outer ring
|
||||
double minX = Double.MAX_VALUE;
|
||||
double minY = Double.MAX_VALUE;
|
||||
|
@ -520,7 +582,7 @@ public class OsmMapUtils {
|
|||
|
||||
// do not drill down further if there's no chance of a better solution
|
||||
// System.out.println(String.format("check for precision: cell.max - bestCell.d = %f Precision: %f", cell.max, precision));
|
||||
if (cell.max - bestCell.d <= precision) continue;
|
||||
if (cell.max - bestCell.d <= POLY_CENTER_PRECISION) continue;
|
||||
|
||||
// split the cell into four cells
|
||||
h = cell.h / 2;
|
||||
|
|
Loading…
Reference in a new issue