fix multipolygon center for map and poi

This commit is contained in:
MadWasp79 2020-02-26 15:38:27 +02:00
parent e6613ddb12
commit 6a56535c9f
4 changed files with 960 additions and 9 deletions

View 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;
}
}

View file

@ -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;
}
}

View 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());
}
}

View file

@ -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;