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