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 java.util.PriorityQueue;
|
||||||
|
|
||||||
import net.osmand.data.LatLon;
|
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.osm.edit.Relation.RelationMember;
|
||||||
|
import net.osmand.util.Algorithms;
|
||||||
import net.osmand.util.MapAlgorithms;
|
import net.osmand.util.MapAlgorithms;
|
||||||
import net.osmand.util.MapUtils;
|
import net.osmand.util.MapUtils;
|
||||||
|
|
||||||
public class OsmMapUtils {
|
public class OsmMapUtils {
|
||||||
|
|
||||||
|
private static final double POLY_CENTER_PRECISION= 1e-6;
|
||||||
|
|
||||||
public static double getDistance(Node e1, Node e2) {
|
public static double getDistance(Node e1, Node e2) {
|
||||||
return MapUtils.getDistance(e1.getLatitude(), e1.getLongitude(), e2.getLatitude(), e2.getLongitude());
|
return MapUtils.getDistance(e1.getLatitude(), e1.getLongitude(), e2.getLatitude(), e2.getLongitude());
|
||||||
|
@ -33,6 +39,53 @@ public class OsmMapUtils {
|
||||||
return getWeightCenterForWay(((Way) e));
|
return getWeightCenterForWay(((Way) e));
|
||||||
} else if (e instanceof Relation) {
|
} else if (e instanceof Relation) {
|
||||||
List<LatLon> list = new ArrayList<LatLon>();
|
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()) {
|
for (RelationMember fe : ((Relation) e).getMembers()) {
|
||||||
LatLon c = null;
|
LatLon c = null;
|
||||||
// skip relations to avoid circular dependencies
|
// skip relations to avoid circular dependencies
|
||||||
|
@ -48,16 +101,26 @@ public class OsmMapUtils {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LatLon getComplexPolyCenter(Collection<Node> nodes) {
|
public static LatLon getComplexPolyCenter(Collection<Node> outer, List<List<Node>> inner) {
|
||||||
double precision = 1e-5; //where to set precision constant?
|
|
||||||
|
|
||||||
final List<List<LatLon>> rings = new ArrayList<>();
|
final List<List<LatLon>> rings = new ArrayList<>();
|
||||||
List<LatLon> outerRing = new ArrayList<>();
|
List<LatLon> outerRing = new ArrayList<>();
|
||||||
for (Node n : nodes) {
|
|
||||||
|
for (Node n : outer) {
|
||||||
outerRing.add(new LatLon(n.getLatitude(), n.getLongitude()));
|
outerRing.add(new LatLon(n.getLatitude(), n.getLongitude()));
|
||||||
}
|
}
|
||||||
rings.add(outerRing);
|
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) {
|
public static LatLon getWeightCenter(Collection<LatLon> nodes) {
|
||||||
|
@ -109,7 +172,7 @@ public class OsmMapUtils {
|
||||||
area = false;
|
area = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LatLon ll = area ? getComplexPolyCenter(nodes) : getWeightCenterForNodes(nodes);
|
LatLon ll = area ? getComplexPolyCenter(nodes, null) : getWeightCenterForNodes(nodes);
|
||||||
if(ll == null) {
|
if(ll == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -464,11 +527,10 @@ public class OsmMapUtils {
|
||||||
/**
|
/**
|
||||||
* Calculate "visual" center point of polygons (based on Mapbox' polylabel algorithm)
|
* Calculate "visual" center point of polygons (based on Mapbox' polylabel algorithm)
|
||||||
* @param rings - list of lists of nodes
|
* @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
|
* @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
|
// find the bounding box of the outer ring
|
||||||
double minX = Double.MAX_VALUE;
|
double minX = Double.MAX_VALUE;
|
||||||
double minY = 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
|
// 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));
|
// 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
|
// split the cell into four cells
|
||||||
h = cell.h / 2;
|
h = cell.h / 2;
|
||||||
|
|
Loading…
Reference in a new issue