/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2008 - 2009, Johann Sorel
* (C) 2009 - 2013, Geomatys
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotoolkit.gui.swing.render2d.control.selection;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.Polygon;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.geom.GeneralPath;
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JComponent;
import javax.swing.JPopupMenu;
import javax.swing.event.MouseInputListener;
import org.apache.sis.feature.FeatureExt;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.display2d.canvas.AbstractGraphicVisitor;
import org.geotoolkit.display2d.GraphicVisitor;
import org.geotoolkit.display.VisitFilter;
import org.geotoolkit.display2d.canvas.J2DCanvas;
import org.geotoolkit.display2d.container.ContextContainer2D;
import org.geotoolkit.display2d.primitive.ProjectedCoverage;
import org.geotoolkit.display2d.primitive.ProjectedFeature;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.gui.swing.render2d.CanvasHandler;
import org.geotoolkit.gui.swing.render2d.JMap2D;
import org.geotoolkit.map.FeatureMapLayer;
import org.geotoolkit.map.MapContext;
import org.geotoolkit.map.MapLayer;
import org.apache.sis.referencing.CRS;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureIterator;
import org.geotoolkit.data.query.Query;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.display.container.GraphicContainer;
import org.geotoolkit.display2d.canvas.RenderingContext2D;
import org.geotoolkit.display2d.primitive.SearchAreaJ2D;
import org.opengis.feature.AttributeType;
import org.opengis.feature.Feature;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.Id;
import org.opengis.filter.expression.Expression;
import org.opengis.filter.identity.FeatureId;
import org.opengis.filter.identity.Identifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* Selection handler
*
* @author Johann Sorel
* @module
*/
public class DefaultSelectionHandler implements CanvasHandler {
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.gui.swing.render2d.control.selection");
protected static final FilterFactory2 FF = (FilterFactory2) FactoryFinder.getFilterFactory(
new Hints(Hints.FILTER_FACTORY, FilterFactory2.class));
protected static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
private final EventListener mouseInputListener;
private final DefaultSelectionDecoration selectionPane = new DefaultSelectionDecoration();
private final GraphicVisitor visitor = new AbstractGraphicVisitor() {
private final Map<MapLayer,Set<FeatureId>> selection = new HashMap<>();
@Override
public void startVisit() {
super.startVisit();
selection.clear();
}
@Override
public void endVisit() {
super.endVisit();
//disable auto repaint to avoid a repaint on each layer selection change
map2D.getCanvas().setAutoRepaint(false);
MapContext context = ((ContextContainer2D)map2D.getCanvas().getContainer()).getContext();
for(final MapLayer layer : context.layers()){
if(layer instanceof FeatureMapLayer && layer.isSelectable()){
FeatureMapLayer fml = (FeatureMapLayer)layer;
Id f = fml.getSelectionFilter();
f = combine(f, selection.get(fml));
fml.setSelectionFilter(f);
}
}
selection.clear();
map2D.getCanvas().setAutoRepaint(true);
}
@Override
public void visit(ProjectedFeature feature, RenderingContext2D context, SearchAreaJ2D queryArea) {
final FeatureMapLayer layer = feature.getLayer();
Set<FeatureId> ids = selection.get(layer);
if(ids == null){
ids = new HashSet<>();
selection.put(layer, ids);
}
ids.add(feature.getFeatureId());
}
@Override
public void visit(ProjectedCoverage coverage, RenderingContext2D context, SearchAreaJ2D queryArea) {
}
};
private boolean squareArea;
private boolean withinArea;
private boolean geographicArea;
private JMap2D map2D;
private JPopupMenu menu;
private int key = -1;
public DefaultSelectionHandler() {
mouseInputListener = new EventListener();
}
public void setMenu(JPopupMenu menu) {
this.menu = menu;
}
public boolean isGeographicArea() {
return geographicArea;
}
public boolean isSquareArea() {
return squareArea;
}
public boolean isWithinArea() {
return withinArea;
}
public void setGeographicArea(final boolean geographicArea) {
this.geographicArea = geographicArea;
}
public void setSquareArea(final boolean squareArea) {
this.squareArea = squareArea;
}
public void setWithinArea(final boolean withinArea) {
this.withinArea = withinArea;
}
public JMap2D getMap() {
return map2D;
}
public void setMap(final JMap2D map2D) {
this.map2D = map2D;
}
private Id combine(Id original, Set<? extends Identifier> ids){
final Id f;
if(original == null){
original = FF.id(new HashSet<Identifier>());
}else if(ids == null){
ids = new HashSet<>();
}
if(key == KeyEvent.VK_SHIFT){
//add selection
final Set<Identifier> in = new HashSet<>(ids);
in.addAll(((Id)original).getIdentifiers());
f = FF.id(in);
} else if (key == KeyEvent.VK_CONTROL){
//remove the commun part selection
final Set<Identifier> in = new HashSet<>(((Id)original).getIdentifiers());
if(ids != null){
in.removeAll(ids);
}
f = FF.id(in);
} else {
if(ids != null){
f = FF.id(ids);
}else{
f = null;
}
}
return f;
}
private void doSelection(final List<Point> points, final int key) {
this.key = key;
if (points.size() > 2) {
if(geographicArea){
GraphicContainer container = map2D.getCanvas().getContainer();
if(container instanceof ContextContainer2D){
final ContextContainer2D cc = (ContextContainer2D) container;
final MapContext context = cc.getContext();
//make a geographic selection
final List<Coordinate> coords = new ArrayList<>();
for(Point p : points){
coords.add(new Coordinate(p.x, p.y));
}
Point last = points.get(0);
coords.add(new Coordinate(last.x,last.y));
final LinearRing ring = GEOMETRY_FACTORY.createLinearRing(coords.toArray(new Coordinate[coords.size()]));
final Polygon poly = GEOMETRY_FACTORY.createPolygon(ring, new LinearRing[0]);
final List<MapLayer> layers = new ArrayList<>(context.layers());
for(MapLayer layer : layers){
if(layer instanceof FeatureMapLayer && layer.isSelectable() && layer.isVisible()){
FeatureMapLayer fml = (FeatureMapLayer)layer;
final Set<Identifier> ids = new HashSet<>();
final FeatureMapLayer fl = (FeatureMapLayer) layer;
final AttributeType<?> geomAtt = FeatureExt.getDefaultGeometryAttribute(fl.getCollection().getFeatureType());
final String geoStr = geomAtt.getName().tip().toString();
final Expression geomField = FF.property(geoStr);
CoordinateReferenceSystem dataCrs = FeatureExt.getCRS(fl.getCollection().getFeatureType());
try {
final Geometry dataPoly = JTS.transform(poly, CRS.findOperation(map2D.getCanvas().getDisplayCRS(), dataCrs, null).getMathTransform());
final Expression geomData = FF.literal(dataPoly);
final Filter f = (withinArea) ? FF.within(geomField, geomData) : FF.intersects(geomField, geomData);
final QueryBuilder builder = new QueryBuilder();
builder.setTypeName(fml.getCollection().getFeatureType().getName());
builder.setFilter(f);
builder.setProperties(new String[]{geoStr});
final Query query = builder.buildQuery();
FeatureCollection fc = fl.getCollection().subCollection(query);
FeatureIterator fi = fc.iterator();
while(fi.hasNext()){
Feature fea = fi.next();
ids.add(FeatureExt.getId(fea));
}
fi.close();
} catch (Exception ex) {
LOGGER.log(Level.WARNING, null, ex);
}
Id selection = combine(fml.getSelectionFilter(), ids);
fl.setSelectionFilter(selection);
}
}
}
}else{
//make a graphic selection
final GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD);
path.moveTo(points.get(0).x, points.get(0).y);
for(int i=1;i<points.size();i++){
Point p = points.get(i);
path.lineTo(p.x, p.y);
}
map2D.getCanvas().getGraphicsIn(path, visitor, (withinArea) ? VisitFilter.WITHIN : VisitFilter.INTERSECTS);
}
map2D.getCanvas().repaint();
}
}
@Override
public J2DCanvas getCanvas() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void install(final Component component) {
map2D.addDecoration(selectionPane);
component.addMouseListener(mouseInputListener);
component.addMouseMotionListener(mouseInputListener);
component.addKeyListener(mouseInputListener);
((JComponent)component).setComponentPopupMenu(menu);
}
@Override
public void uninstall(final Component component) {
map2D.removeDecoration(selectionPane);
component.removeMouseListener(mouseInputListener);
component.removeMouseMotionListener(mouseInputListener);
component.removeKeyListener(mouseInputListener);
((JComponent)component).setComponentPopupMenu(null);
}
private class EventListener implements MouseInputListener,KeyListener {
private int key;
Point lastValid = null;
final List<Point> points = new ArrayList<>();
private int startX = 0;
private int startY = 0;
private boolean selecting = false;
@Override
public void mouseClicked(final MouseEvent e) {
if(e.getButton()!=MouseEvent.BUTTON1)return;
selecting = true;
final Point point = e.getPoint();
points.clear();
points.add(new Point(point.x-1, point.y-1));
points.add(new Point(point.x-1, point.y+1));
points.add(new Point(point.x+1, point.y+1));
points.add(new Point(point.x+1, point.y-1));
doSelection(points,key);
points.clear();
selecting = false;
}
@Override
public void mousePressed(final MouseEvent e) {
if(e.getButton()!=MouseEvent.BUTTON1)return;
selecting = true;
lastValid = e.getPoint();
points.clear();
if(squareArea){
startX = lastValid.x;
startY = lastValid.y;
}else{
points.add(lastValid);
}
}
@Override
public void mouseReleased(final MouseEvent e) {
if(e.getButton()!=MouseEvent.BUTTON1)return;
final Point lastPoint = e.getPoint();
if(squareArea){
points.clear();
points.add(lastPoint);
points.add(new Point(lastPoint.x, startY));
points.add(new Point(startX, startY));
points.add(new Point(startX, lastPoint.y));
}else{
points.add(lastPoint);
}
doSelection(points,key);
selectionPane.setPoints(null);
points.clear();
selecting = false;
}
@Override
public void mouseEntered(final MouseEvent e) {
map2D.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
map2D.getComponent().requestFocus();
}
@Override
public void mouseExited(final MouseEvent e) {
map2D.setCursor(null);
}
@Override
public void mouseDragged(final MouseEvent e) {
if(!selecting)return;
Point eventPoint = e.getPoint();
if(squareArea){
points.clear();
points.add(eventPoint);
points.add(new Point(eventPoint.x, startY));
points.add(new Point(startX, startY));
points.add(new Point(startX, eventPoint.y));
points.add(eventPoint);
selectionPane.setPoints(new ArrayList<>(points));
}else{
if(eventPoint.distance(lastValid) > 6){
lastValid = eventPoint;
points.add(new Point(e.getX(), e.getY()));
selectionPane.setPoints(new ArrayList<>(points));
}
}
}
@Override
public void mouseMoved(final MouseEvent e) {
}
@Override
public void keyTyped(final KeyEvent arg0) {
}
@Override
public void keyPressed(final KeyEvent arg0) {
key = arg0.getKeyCode();
}
@Override
public void keyReleased(final KeyEvent arg0) {
key = -1;
}
}
}