// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.data.osm.visitor.paint.relations;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;
import org.openstreetmap.josm.gui.NavigatableComponent;
public class Multipolygon {
public static class JoinedWay {
private final List<Node> nodes;
private final boolean selected;
public JoinedWay(List<Node> nodes, boolean selected) {
this.nodes = nodes;
this.selected = selected;
}
public List<Node> getNodes() {
return nodes;
}
public boolean isSelected() {
return selected;
}
public boolean isClosed() {
return nodes.isEmpty() || nodes.get(nodes.size() - 1).equals(nodes.get(0));
}
}
public static class PolyData {
public enum Intersection {INSIDE, OUTSIDE, CROSSING}
public Polygon poly = new Polygon();
public final boolean selected;
private Point lastP;
private Rectangle bounds;
public PolyData(NavigatableComponent nc, JoinedWay joinedWay) {
this(nc, joinedWay.getNodes(), joinedWay.isSelected());
}
public PolyData(NavigatableComponent nc, List<Node> nodes, boolean selected) {
this.selected = selected;
Point p = null;
for (Node n : nodes)
{
p = nc.getPoint(n);
poly.addPoint(p.x,p.y);
}
if (!nodes.get(0).equals(nodes.get(nodes.size() - 1))) {
p = nc.getPoint(nodes.get(0));
poly.addPoint(p.x, p.y);
}
lastP = p;
}
public PolyData(PolyData copy) {
poly = new Polygon(copy.poly.xpoints, copy.poly.ypoints, copy.poly.npoints);
this.selected = copy.selected;
lastP = copy.lastP;
}
public Intersection contains(Polygon p) {
int contains = p.npoints;
for(int i = 0; i < p.npoints; ++i)
{
if(poly.contains(p.xpoints[i],p.ypoints[i])) {
--contains;
}
}
if(contains == 0) return Intersection.INSIDE;
if(contains == p.npoints) return Intersection.OUTSIDE;
return Intersection.CROSSING;
}
public void addInner(Polygon p) {
for(int i = 0; i < p.npoints; ++i) {
poly.addPoint(p.xpoints[i],p.ypoints[i]);
}
poly.addPoint(lastP.x, lastP.y);
}
public Polygon get() {
return poly;
}
public Rectangle getBounds() {
if (bounds == null) {
bounds = poly.getBounds();
}
return bounds;
}
@Override
public String toString() {
return "Points: " + poly.npoints + " Selected: " + selected;
}
}
private final NavigatableComponent nc;
private final List<Way> innerWays = new ArrayList<Way>();
private final List<Way> outerWays = new ArrayList<Way>();
private final List<PolyData> innerPolygons = new ArrayList<PolyData>();
private final List<PolyData> outerPolygons = new ArrayList<PolyData>();
private final List<PolyData> combinedPolygons = new ArrayList<PolyData>();
public Multipolygon(NavigatableComponent nc) {
this.nc = nc;
}
public void load(Relation r) {
// Fill inner and outer list with valid ways
for (RelationMember m : r.getMembers()) {
if (m.getMember().isDrawable()) {
if(m.isWay()) {
Way w = m.getWay();
if(w.getNodesCount() < 2) {
continue;
}
if("inner".equals(m.getRole())) {
getInnerWays().add(w);
} else if("outer".equals(m.getRole())) {
getOuterWays().add(w);
} else if (!m.hasRole()) {
getOuterWays().add(w);
} // Remaining roles ignored
} // Non ways ignored
}
}
createPolygons(innerWays, innerPolygons);
createPolygons(outerWays, outerPolygons);
if (!outerPolygons.isEmpty()) {
addInnerToOuters();
}
}
private void createPolygons(List<Way> ways, List<PolyData> result) {
List<Way> waysToJoin = new ArrayList<Way>();
for (Way way: ways) {
if (way.isClosed()) {
result.add(new PolyData(nc, way.getNodes(), way.isSelected()));
} else {
waysToJoin.add(way);
}
}
for (JoinedWay jw: joinWays(waysToJoin)) {
result.add(new PolyData(nc, jw));
}
}
public static Collection<JoinedWay> joinWays(Collection<Way> join)
{
Collection<JoinedWay> res = new ArrayList<JoinedWay>();
Way[] joinArray = join.toArray(new Way[join.size()]);
int left = join.size();
while(left != 0)
{
Way w = null;
boolean selected = false;
List<Node> n = null;
boolean joined = true;
while(joined && left != 0)
{
joined = false;
for(int i = 0; i < joinArray.length && left != 0; ++i)
{
if(joinArray[i] != null)
{
Way c = joinArray[i];
if(w == null)
{ w = c; selected = w.isSelected(); joinArray[i] = null; --left; }
else
{
int mode = 0;
int cl = c.getNodesCount()-1;
int nl;
if(n == null)
{
nl = w.getNodesCount()-1;
if(w.getNode(nl) == c.getNode(0)) {
mode = 21;
} else if(w.getNode(nl) == c.getNode(cl)) {
mode = 22;
} else if(w.getNode(0) == c.getNode(0)) {
mode = 11;
} else if(w.getNode(0) == c.getNode(cl)) {
mode = 12;
}
}
else
{
nl = n.size()-1;
if(n.get(nl) == c.getNode(0)) {
mode = 21;
} else if(n.get(0) == c.getNode(cl)) {
mode = 12;
} else if(n.get(0) == c.getNode(0)) {
mode = 11;
} else if(n.get(nl) == c.getNode(cl)) {
mode = 22;
}
}
if(mode != 0)
{
joinArray[i] = null;
joined = true;
if(c.isSelected()) {
selected = true;
}
--left;
if(n == null) {
n = w.getNodes();
}
n.remove((mode == 21 || mode == 22) ? nl : 0);
if(mode == 21) {
n.addAll(c.getNodes());
} else if(mode == 12) {
n.addAll(0, c.getNodes());
} else if(mode == 22)
{
for(Node node : c.getNodes()) {
n.add(nl, node);
}
}
else /* mode == 11 */
{
for(Node node : c.getNodes()) {
n.add(0, node);
}
}
}
}
}
} /* for(i = ... */
} /* while(joined) */
if (n == null) {
n = w.getNodes();
}
res.add(new JoinedWay(n, selected));
} /* while(left != 0) */
return res;
}
public PolyData findOuterPolygon(PolyData inner, List<PolyData> outerPolygons) {
PolyData result = null;
{// First try to test only bbox, use precise testing only if we don't get unique result
Rectangle innerBox = inner.getBounds();
PolyData insidePolygon = null;
PolyData intersectingPolygon = null;
int insideCount = 0;
int intersectingCount = 0;
for (PolyData outer: outerPolygons) {
if (outer.getBounds().contains(innerBox)) {
insidePolygon = outer;
insideCount++;
} else if (outer.getBounds().intersects(innerBox)) {
intersectingPolygon = outer;
intersectingCount++;
}
}
if (insideCount == 1)
return insidePolygon;
else if (intersectingCount == 1)
return intersectingPolygon;
}
for (PolyData combined : outerPolygons) {
Intersection c = combined.contains(inner.poly);
if(c != Intersection.OUTSIDE)
{
if(result == null || result.contains(combined.poly) != Intersection.INSIDE) {
result = combined;
}
}
}
return result;
}
private void addInnerToOuters() {
if (innerPolygons.isEmpty()) {
combinedPolygons.addAll(outerPolygons);
} else if (outerPolygons.size() == 1) {
PolyData combinedOuter = new PolyData(outerPolygons.get(0));
for (PolyData inner: innerPolygons) {
combinedOuter.addInner(inner.poly);
}
combinedPolygons.add(combinedOuter);
} else {
for (PolyData outer: outerPolygons) {
combinedPolygons.add(new PolyData(outer));
}
for (PolyData pdInner: innerPolygons) {
PolyData o = findOuterPolygon(pdInner, combinedPolygons);
if(o == null) {
o = outerPolygons.get(0);
}
o.addInner(pdInner.poly);
}
}
}
public List<Way> getOuterWays() {
return outerWays;
}
public List<Way> getInnerWays() {
return innerWays;
}
public List<PolyData> getInnerPolygons() {
return innerPolygons;
}
public List<PolyData> getOuterPolygons() {
return outerPolygons;
}
public List<PolyData> getCombinedPolygons() {
return combinedPolygons;
}
}