001// License: GPL. For details, see LICENSE file.
002package org.openstreetmap.josm.actions;
003
004import org.openstreetmap.josm.Main;
005import org.openstreetmap.josm.data.coor.EastNorth;
006import org.openstreetmap.josm.data.osm.BBox;
007import org.openstreetmap.josm.data.osm.Node;
008import org.openstreetmap.josm.data.osm.OsmPrimitive;
009import org.openstreetmap.josm.data.osm.Relation;
010import org.openstreetmap.josm.data.osm.RelationMember;
011import org.openstreetmap.josm.data.osm.Way;
012import org.openstreetmap.josm.tools.Geometry;
013
014import java.awt.event.ActionEvent;
015import java.util.ArrayList;
016import java.util.Collection;
017import java.util.TreeMap;
018
019/**
020 * This allows to select a polygon/multipolgon by an internal point.
021 */
022public class SelectByInternalPointAction extends JosmAction {
023
024    /**
025     * Returns the surrounding polygons/multipolgons
026     * ordered by their area size (from small to large)
027     * which contain the internal point.
028     *
029     * @param internalPoint the internal point.
030     */
031    public static Collection<OsmPrimitive> getSurroundingObjects(EastNorth internalPoint) {
032        final Node n = new Node(internalPoint);
033        final TreeMap<Double, OsmPrimitive> found = new TreeMap<>();
034        for (Way w : getCurrentDataSet().getWays()) {
035            if (w.isUsable() && w.isClosed()) {
036                if (Geometry.nodeInsidePolygon(n, w.getNodes())) {
037                    found.put(Geometry.closedWayArea(w), w);
038                }
039            }
040        }
041        for (Relation r : getCurrentDataSet().getRelations()) {
042            if (r.isUsable() && r.isMultipolygon()) {
043                if (Geometry.isNodeInsideMultiPolygon(n, r, null)) {
044                    for (RelationMember m : r.getMembers()) {
045                        if (m.isWay() && m.getWay().isClosed()) {
046                            found.values().remove(m.getWay());
047                        }
048                    }
049                    // estimate multipolygon size by its bounding box area
050                    BBox bBox = r.getBBox();
051                    EastNorth en1 = Main.map.mapView.getProjection().latlon2eastNorth(bBox.getTopLeft());
052                    EastNorth en2 = Main.map.mapView.getProjection().latlon2eastNorth(bBox.getBottomRight());
053                    double s = Math.abs((en1.east() - en2.east()) * (en1.north() - en2.north()));
054                    if (s == 0) s = 1e8;
055                    found.put(s, r);
056                }
057            }
058        }
059        return found.values();
060    }
061
062
063    /**
064     * Returns the smallest surrounding polygon/multipolgon which contains the internal point.
065     *
066     * @param internalPoint the internal point.
067     */
068    public static OsmPrimitive getSmallestSurroundingObject(EastNorth internalPoint) {
069        final Collection<OsmPrimitive> surroundingObjects = getSurroundingObjects(internalPoint);
070        return surroundingObjects.isEmpty() ? null : surroundingObjects.iterator().next();
071    }
072
073    /**
074     * Select a polygon or multipolgon by an internal point.
075     *
076     * @param internalPoint the internal point.
077     * @param doAdd         whether to add selected polygon to the current selection.
078     * @param doRemove      whether to remove the selected polygon from the current selection.
079     */
080    public static void performSelection(EastNorth internalPoint, boolean doAdd, boolean doRemove) {
081        final Collection<OsmPrimitive> surroundingObjects = getSurroundingObjects(internalPoint);
082        if (surroundingObjects.isEmpty()) {
083            return;
084        } else if (doRemove) {
085            final Collection<OsmPrimitive> newSelection = new ArrayList<>(getCurrentDataSet().getSelected());
086            newSelection.removeAll(surroundingObjects);
087            getCurrentDataSet().setSelected(newSelection);
088        } else if (doAdd) {
089            final Collection<OsmPrimitive> newSelection = new ArrayList<>(getCurrentDataSet().getSelected());
090            newSelection.add(surroundingObjects.iterator().next());
091            getCurrentDataSet().setSelected(newSelection);
092        } else {
093            getCurrentDataSet().setSelected(surroundingObjects.iterator().next());
094        }
095    }
096
097    @Override
098    public void actionPerformed(ActionEvent e) {
099        throw new UnsupportedOperationException();
100    }
101}