/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2014, 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.javafx.render2d.edition;
import org.opengis.feature.Feature;
import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.GeometryCollection;
import com.vividsolutions.jts.geom.GeometryFactory;
import com.vividsolutions.jts.geom.LineString;
import com.vividsolutions.jts.geom.LinearRing;
import com.vividsolutions.jts.geom.MultiLineString;
import com.vividsolutions.jts.geom.MultiPoint;
import com.vividsolutions.jts.geom.MultiPolygon;
import com.vividsolutions.jts.geom.Point;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.operation.buffer.BufferParameters;
import com.vividsolutions.jts.operation.distance.DistanceOp;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.ServiceLoader;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import org.apache.sis.feature.FeatureExt;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.factory.Hints;
import org.geotoolkit.data.FeatureCollection;
import org.geotoolkit.data.FeatureIterator;
import org.geotoolkit.data.memory.GenericFilterFeatureIterator;
import org.geotoolkit.data.query.QueryBuilder;
import org.geotoolkit.geometry.jts.JTS;
import org.geotoolkit.map.FeatureMapLayer;
import org.apache.sis.referencing.CRS;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.apache.sis.util.ArgumentChecks;
import org.geotoolkit.util.StringUtilities;
import org.apache.sis.util.logging.Logging;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory2;
import org.opengis.filter.expression.Expression;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import static org.apache.sis.util.ArgumentChecks.*;
import org.geotoolkit.display2d.GO2Utilities;
import static org.geotoolkit.display2d.GO2Utilities.FILTER_FACTORY;
import org.geotoolkit.gui.javafx.render2d.FXMap;
import org.geotoolkit.internal.Loggers;
import org.opengis.feature.AttributeType;
import org.opengis.feature.FeatureType;
/**
*
* @author Johann Sorel
*/
public class EditionHelper {
private static final Logger LOGGER = Logging.getLogger("org.geotoolkit.gui.javafx.render2d.edition");
private static final GeometryFactory GEOMETRY_FACTORY = new GeometryFactory();
private static final FilterFactory2 FF = (FilterFactory2) FactoryFinder.getFilterFactory(
new Hints(Hints.FILTER_FACTORY, FilterFactory2.class));
public static final Coordinate[] EMPTY_COORDINATE_ARRAY = new Coordinate[0];
public static class EditionGeometry{
public final ObjectProperty<Geometry> geometry = new SimpleObjectProperty<>();
public int numSubGeom = -1;
public int numHole = -1;
public final int[] selectedNode = new int[]{-1,-1};
public void reset() {
geometry.set(null);
numSubGeom = -1;
numHole = -1;
selectedNode[0] = -1;
selectedNode[1] = -1;
}
public void moveSelectedNode(final Coordinate newCoordinate){
moveSelectedNode(newCoordinate, false);
}
public void moveSelectedNode(final Coordinate newCoordinate, boolean newGeometry){
if(geometry == null) return;
if(numSubGeom < 0) return;
if(selectedNode[0] < 0) return;
Geometry base = geometry.get();
if(newGeometry) base = (Geometry) base.clone();
final Geometry geo = base.getGeometryN(numSubGeom);
if(numHole < 0){
final Coordinate[] coords = geo.getCoordinates();
coords[selectedNode[0]].setCoordinate(newCoordinate);
coords[selectedNode[1]].setCoordinate(newCoordinate);
geo.geometryChanged();
}else{
final Polygon p = (Polygon) geo;
final LineString ls = p.getInteriorRingN(numHole);
final Coordinate[] coords = ls.getCoordinates();
coords[selectedNode[0]].setCoordinate(newCoordinate);
coords[selectedNode[1]].setCoordinate(newCoordinate);
geo.geometryChanged();
}
geo.geometryChanged();
if(newGeometry){
geometry.set(base);
}else{
geometry.get().geometryChanged();
}
}
/**
* Indicate if a node is currently selected.
*
* @return true if a node is selected on the current geometry
*/
public boolean hasNodeSelected(){
return !(geometry.get() == null
|| (numSubGeom >= geometry.get().getNumGeometries())
|| numSubGeom < 0
|| selectedNode[0] < 0);
}
/**
* Remove the selected node from the geometry.
*
*/
public void deleteSelectedNode() {
if(!hasNodeSelected()) return;
//save datas
final int srid = geometry.get().getSRID();
final Object userData = geometry.get().getUserData();
final Geometry geo = geometry.get().getGeometryN(numSubGeom);
if(numHole < 0){
final List<Coordinate> newCoords = new ArrayList<Coordinate>();
newCoords.addAll(Arrays.asList(geo.getCoordinates()));
if(geo instanceof Polygon && selectedNode[0] == 0){
//remove first and last point
newCoords.remove(newCoords.size()-1);
newCoords.remove(0);
}else{
newCoords.remove(selectedNode[0]);
}
if(geometry.get() instanceof Point){
//can not delete node from a Point
}else if(geometry.get() instanceof MultiPoint){
//we have deleted the given subgeometry
final MultiPoint mp = (MultiPoint) geometry.get();
final List<Geometry> parts = new ArrayList<Geometry>();
for(int i=0,n=mp.getNumGeometries();i<n;i++){
parts.add(mp.getGeometryN(i));
}
if(parts.size()>1){
//remove only if we have more then one point
parts.remove(numSubGeom);
geometry.set( createMultiPoint(parts) );
}
}else if(geometry.get() instanceof LineString){
geometry.set(createLine(newCoords));
}else if(geometry.get() instanceof MultiLineString){
final MultiLineString ml = (MultiLineString) geometry.get();
final List<Geometry> strs = new ArrayList<Geometry>();
for(int i=0,n=ml.getNumGeometries();i<n;i++){
if(i==numSubGeom){
final LineString str = createLine(newCoords);
strs.add(str);
}else{
strs.add(ml.getGeometryN(i));
}
}
geometry.set( createMultiLine(strs) );
}else if(geometry.get() instanceof Polygon){
final Polygon poly = (Polygon) geometry.get();
final List<LinearRing> holes = new ArrayList<LinearRing>();
for(int i=0,n=poly.getNumInteriorRing();i<n;i++){
holes.add((LinearRing) poly.getInteriorRingN(i));
}
geometry.set( createPolygon(newCoords, holes.toArray(new LinearRing[holes.size()])) );
}else if(geometry.get() instanceof MultiPolygon){
final MultiPolygon mp = (MultiPolygon) geometry.get();
final List<Geometry> polys = new ArrayList<Geometry>();
for(int i=0,n=mp.getNumGeometries();i<n;i++){
Polygon poly = (Polygon) mp.getGeometryN(i);
if(i==numSubGeom){
final List<LinearRing> holes = new ArrayList<LinearRing>();
for(int j=0,k=poly.getNumInteriorRing();j<k;j++){
holes.add((LinearRing) poly.getInteriorRingN(j));
}
poly = createPolygon(newCoords, holes.toArray(new LinearRing[holes.size()]));
}
polys.add(poly);
}
geometry.set( createMultiPolygon(polys) );
}else{
throw new IllegalArgumentException("Unexpected geometry type :" + geometry.getClass());
}
selectedNode[0] = selectedNode[1] = -1;
}else{
throw new UnsupportedOperationException("not yet implemented");
}
geometry.get().setSRID(srid);
geometry.get().setUserData(userData);
}
@Override
public String toString() {
return "Selection\n"+ StringUtilities.toStringTree(geometry,numSubGeom,numHole,selectedNode[0]);
}
}
private final FXMap map;
private final FeatureMapLayer editedLayer;
private Geometry constraint = null;
private boolean showAtributeditor;
private int mousePointerSize = 4;
/**
*
* @param map source map
* @param editedLayer edited layer
*/
public EditionHelper(final FXMap map, final FeatureMapLayer editedLayer) {
ArgumentChecks.ensureNonNull("map", map);
ArgumentChecks.ensureNonNull("layer", editedLayer);
this.map = map;
this.editedLayer = editedLayer;
this.showAtributeditor = true;
}
/**
* The mouse pointer size is used when searching for node and geometries.
* It defines the buffer size of the intersection area.
* If size is too small picking will become impossible.
* If size is too big picking won't select the most appropriate element.
*
* Recommended size range 3 - 10
*
* @param mousePointerSize on click distance tolerance
*/
public void setMousePointerSize(int mousePointerSize) {
this.mousePointerSize = mousePointerSize;
}
public int getMousePointerSize() {
return mousePointerSize;
}
public boolean isShowAtributeditor() {
return showAtributeditor;
}
public void setShowAtributeditor(boolean showAtributeditor) {
this.showAtributeditor = showAtributeditor;
}
/**
* Set geometry constraint.
* This geometry will be used to restrict all coordinate creation.
* The closest intersection point will be returned if mouse is outside the constraint
* area.
*
* @param constraint geometry
*/
public void setConstraint(Geometry constraint) {
this.constraint = constraint;
}
/**
*
* @return constraint geometry
*/
public Geometry getConstraint() {
return constraint;
}
/**
* transform a mouse coordinate in JTS Geometry using the CRS of the map context
* @param mx : x coordinate of the mouse on the map (in pixel)
* @param my : y coordinate of the mouse on the map (in pixel)
* @return JTS geometry (corresponding to a square of 6x6 pixel around mouse coordinate)
*/
public Polygon mousePositionToGeometry(final double mx, final double my) {
final Coordinate[] coord = new Coordinate[5];
coord[0] = toCoord(mx - mousePointerSize, my - mousePointerSize);
coord[1] = toCoord(mx - mousePointerSize, my + mousePointerSize);
coord[2] = toCoord(mx + mousePointerSize, my + mousePointerSize);
coord[3] = toCoord(mx + mousePointerSize, my - mousePointerSize);
coord[4] = coord[0];
final LinearRing lr1 = GEOMETRY_FACTORY.createLinearRing(coord);
return GEOMETRY_FACTORY.createPolygon(lr1, null);
}
public Point toJTS(final double x, final double y){
final Coordinate coord = toCoord(x, y);
return GEOMETRY_FACTORY.createPoint(coord);
}
public Coordinate toCoord(final double x, final double y){
final AffineTransform2D trs = map.getCanvas().getObjectiveToDisplay();
AffineTransform dispToObj;
try {
dispToObj = trs.createInverse();
} catch (NoninvertibleTransformException ex) {
dispToObj = new AffineTransform();
LOGGER.log(Level.WARNING, null, ex);
}
final double[] crds = new double[]{x,y};
dispToObj.transform(crds, 0, crds, 0, 1);
final Coordinate coord = new Coordinate(crds[0], crds[1]);
if(constraint!=null){
final CoordinateReferenceSystem crs2d = map.getCanvas().getObjectiveCRS2D();
try {
final Geometry geom = JTS.transform(constraint, crs2d);
final DistanceOp distOp = new DistanceOp(geom, GO2Utilities.JTS_FACTORY.createPoint(coord));
final Coordinate[] nearest = distOp.nearestPoints();
return nearest[0];
} catch (Exception ex) {
Loggers.JAVAFX.log(Level.WARNING, ex.getMessage());
}
}
return coord;
}
/**
* Get feature at given mouse coordinate.
*
* @param mx mouse x coordinate in display crs
* @param my mouse y coordinate in display crs
* @param style consider the style for selection.
* @return Feature or null
*/
public Feature grabFeature(final double mx, final double my, final boolean style) {
if(editedLayer == null) return null;
Feature candidate = null;
FeatureCollection editgeoms = null;
FeatureIterator fi = null;
try {
final Polygon geo = mousePositionToGeometry(mx, my);
Filter flt = toFilter(geo, editedLayer);
//concatenate with temporal range if needed ----------------------------
for (final FeatureMapLayer.DimensionDef def : editedLayer.getExtraDimensions()) {
final CoordinateReferenceSystem crs = def.getCrs();
final org.opengis.geometry.Envelope canvasEnv = map.getCanvas().getVisibleEnvelope();
final org.opengis.geometry.Envelope dimEnv;
try {
dimEnv = Envelopes.transform(canvasEnv, crs);
} catch (TransformException ex) {
continue;
}
final Filter dimFilter = FILTER_FACTORY.and(
FF.or(
FF.isNull(def.getLower()),
FF.lessOrEqual(def.getLower(), FF.literal(dimEnv.getMaximum(0)) )),
FF.or(
FF.isNull(def.getUpper()),
FF.greaterOrEqual(def.getUpper(), FF.literal(dimEnv.getMinimum(0)) ))
);
flt = FF.and(flt, dimFilter);
}
QueryBuilder qb = new QueryBuilder(editedLayer.getCollection().getFeatureType().getName().toString());
//we filter in the map CRS
qb.setCRS(map.getCanvas().getObjectiveCRS2D());
editgeoms = (FeatureCollection) editedLayer.getCollection().subCollection(qb.buildQuery());
//we filter ourself since we want the filter to occure after the reprojection
editgeoms = GenericFilterFeatureIterator.wrap(editgeoms, flt);
fi = editgeoms.iterator();
if (fi.hasNext()) {
Feature sf = fi.next();
//get the original, in it's data crs
flt = FF.id(Collections.singleton(FeatureExt.getId(sf)));
sf = null;
fi.close();
qb.reset();
qb.setTypeName(editedLayer.getCollection().getFeatureType().getName());
qb.setFilter(flt);
editgeoms = (FeatureCollection) editedLayer.getCollection().subCollection(qb.buildQuery());
fi = editgeoms.iterator();
if (fi.hasNext()){
sf = fi.next();
}
return sf;
}
}catch(Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}finally{
if(fi != null){
fi.close();
}
}
return candidate;
}
public boolean grabGeometrynode(final Point pt, final double mx, final double my){
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
return pt.intersects(mouseGeo);
}catch(Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
return false;
}
}
public void grabGeometryNode(final double mx, final double my,final EditionGeometry edited) {
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
grabGeometryNode(mouseGeo, edited);
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
}
public void grabGeometryNode(final Geometry mouseGeo, final EditionGeometry edited) {
//reset selection
edited.numSubGeom = -1;
edited.numHole = -1;
edited.selectedNode[0] = -1;
edited.selectedNode[1] = -1;
for (int i=0,n=edited.geometry.get().getNumGeometries(); i<n; i++) {
final Geometry subgeo = edited.geometry.get().getGeometryN(i);
if (subgeo.intersects(mouseGeo)) {
//this geometry intersect the mouse
edited.numSubGeom = i;
//this far it can only be a point, linestring or polygon
if(subgeo instanceof Point || subgeo instanceof LineString){
simpleIntersect(mouseGeo, subgeo, edited.selectedNode);
break;
}else if(subgeo instanceof Polygon){
final Polygon poly = (Polygon) subgeo;
LineString ring = poly.getExteriorRing();
simpleIntersect(mouseGeo, ring,edited.selectedNode);
if(edited.selectedNode[0] != -1){
break;
}
for(int j=0,k=poly.getNumInteriorRing(); j<k; j++){
ring = poly.getInteriorRingN(j);
simpleIntersect(mouseGeo, ring,edited.selectedNode);
if(edited.selectedNode[0] != -1){
edited.numHole = j;
break;
}
}
}else{
throw new IllegalArgumentException("Was expecting a Point, LineString or Polygon, but was : " + subgeo.getClass());
}
break;
}
}
}
private void simpleIntersect(final Geometry mouseGeo, final Geometry geom, final int[] indexes){
final Coordinate[] coos = geom.getCoordinates();
for (int j=0,m=coos.length; j<m; j++) {
final Coordinate coo = coos[j];
final Point p = createPoint(coo);
if (p.intersects(mouseGeo)) {
if(geom instanceof LinearRing && (j==0 || j == m-1)){
indexes[0] = 0;
indexes[1] = m-1;
}else{
indexes[0] = indexes[1] = j;
}
return;
}
}
}
public void moveGeometry(final Geometry geo, final double dx, final double dy) {
try{
final Point2D pt0 = map.getCanvas().getObjectiveToDisplay().inverseTransform(new Point2D.Double(0, 0), null);
final Point2D pt = map.getCanvas().getObjectiveToDisplay().inverseTransform(new Point2D.Double(dx, dy), null);
pt.setLocation(pt.getX()-pt0.getX(), pt.getY()-pt0.getY());
for (int i=0,n=geo.getNumGeometries(); i<n; i++) {
final Geometry subgeo = geo.getGeometryN(i);
final Coordinate[] coos = subgeo.getCoordinates();
for (int j=0,m=coos.length; j<m; j++) {
final Coordinate coo = coos[j];
coo.x += pt.getX();
coo.y += pt.getY();
}
subgeo.geometryChanged();
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
geo.geometryChanged();
}
public void moveSubGeometry(final Geometry geo, final int indice, final double dx, final double dy) {
try{
final Point2D pt0 = map.getCanvas().getObjectiveToDisplay().inverseTransform(new Point2D.Double(0, 0), null);
final Point2D pt = map.getCanvas().getObjectiveToDisplay().inverseTransform(new Point2D.Double(dx, dy), null);
pt.setLocation(pt.getX()-pt0.getX(), pt.getY()-pt0.getY());
final Geometry subgeo = geo.getGeometryN(indice);
final Coordinate[] coos = subgeo.getCoordinates();
for (int j=0,m=coos.length; j<m; j++) {
final Coordinate coo = coos[j];
coo.x += pt.getX();
coo.y += pt.getY();
}
subgeo.geometryChanged();
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
geo.geometryChanged();
}
public Geometry insertNode(final Polygon geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Polygon mouseGeo = mousePositionToGeometry(mx, my);
final Point mousePoint = toJTS(mx, my);
if (geo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final Coordinate[] coos = geo.getCoordinates();
for (int j=0,m=coos.length-1; j<m; j++) {
//find the segment that intersect
final Coordinate coo1 = coos[j];
final Coordinate coo2 = coos[j+1];
final LineString segment = createLine(coo1,coo2);
if(mouseGeo.intersects(segment) && isOnLine(mouseGeo,mousePoint,segment)){
//we must add the new node on this segment
final List<Coordinate> ncs = new ArrayList<Coordinate>();
for (int d=0,p=coos.length; d<p; d++) {
ncs.add(coos[d]);
if(d==j){
//we must add the new node here
ncs.add(mousePoint.getCoordinate());
}
}
final Geometry ls = createPolygon(ncs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
public Geometry insertNode(final LineString geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Polygon mouseGeo = mousePositionToGeometry(mx, my);
final Point mousePoint = toJTS(mx, my);
if (geo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final Coordinate[] coos = geo.getCoordinates();
for (int j=0,m=coos.length-1; j<m; j++) {
//find the segment that intersect
final Coordinate coo1 = coos[j];
final Coordinate coo2 = coos[j+1];
final LineString segment = createLine(coo1,coo2);
if(mouseGeo.intersects(segment) && isOnLine(mouseGeo,mousePoint,segment)){
//we must add the new node on this segment
final List<Coordinate> ncs = new ArrayList<Coordinate>();
for (int d=0,p=coos.length; d<p; d++) {
ncs.add(coos[d]);
if(d==j){
//we must add the new node here
ncs.add(mousePoint.getCoordinate());
}
}
final LineString ls = createLine(ncs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
private static boolean isOnLine(final Polygon candidate, final Point center, final LineString line){
final Envelope env = line.getEnvelopeInternal();
final Coordinate coord = center.getCoordinate();
if(env.contains(coord)){
//get the nearest point on the line to avoid deformations
final Coordinate[] cnds = DistanceOp.nearestPoints(line, center);
coord.setCoordinate(cnds[0]);
return true;
}else{
//make a more accurate test, envelope might have a width or hight
//of zero which will return false on intersection wit the point
final Polygon buffer = (Polygon) line.buffer(candidate.getEnvelopeInternal().getWidth()/2, 10, BufferParameters.CAP_FLAT);
if(buffer.contains(center)){
//get the nearest point on the line to avoid deformations
final Coordinate[] cnds = DistanceOp.nearestPoints(line, center);
coord.setCoordinate(cnds[0]);
return true;
}else{
return false;
}
}
}
public Geometry insertNode(final GeometryCollection geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Polygon mouseGeo = mousePositionToGeometry(mx, my);
final Point mousePoint = toJTS(mx, my);
for (int i=0,n=geo.getNumGeometries(); i<n; i++) {
final Geometry subgeo = geo.getGeometryN(i);
if (subgeo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final Coordinate[] coos = subgeo.getCoordinates();
for (int j=0,m=coos.length-1; j<m; j++) {
//find the segment that intersect
final Coordinate coo1 = coos[j];
final Coordinate coo2 = coos[j+1];
final LineString segment = createLine(coo1,coo2);
if(mouseGeo.intersects(segment) && isOnLine(mouseGeo,mousePoint,segment)){
//we must add the new node on this segment
final List<Geometry> subs = new ArrayList<Geometry>();
for (int k=0,l=geo.getNumGeometries(); k<l; k++) {
if(k==i){
//this subgeo must be changed
final List<Coordinate> ncs = new ArrayList<Coordinate>();
for (int d=0,p=coos.length; d<p; d++) {
ncs.add(coos[d]);
if(d==j){
//we must add the new node here
ncs.add(mousePoint.getCoordinate());
}
}
if(geo instanceof MultiLineString && ncs.size() >1){
subs.add(createLine(ncs));
}else if(geo instanceof MultiPolygon && ncs.size() >2){
subs.add(createPolygon(ncs));
}
}else{
subs.add(geo.getGeometryN(k));
}
}
if(geo instanceof MultiLineString && !subs.isEmpty()){
final Geometry ls = createMultiLine(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else if(geo instanceof MultiPolygon && !subs.isEmpty()){
final Geometry ls = createMultiPolygon(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else{
return null;
}
}
}
break;
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
public Geometry deleteNode(final Polygon geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
if (geo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final Coordinate[] coos = geo.getCoordinates();
for (int j=0,m=coos.length; j<m; j++) {
final Coordinate coo = coos[j];
final Point p = createPoint(coo);
if (p.intersects(mouseGeo)) {
//delete this node
final List<Coordinate> ncs = new ArrayList<Coordinate>();
for (int d=0,z=coos.length; d<z; d++) {
if(d!=j){
ncs.add(coos[d]);
}
}
if(ncs.size() > 2){
final Geometry ls = createPolygon(ncs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}
break;
}
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
public Geometry deleteNode(final LineString geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
if (geo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final Coordinate[] coos = geo.getCoordinates();
for (int j=0,m=coos.length; j<m; j++) {
final Coordinate coo = coos[j];
final Point p = createPoint(coo);
if (p.intersects(mouseGeo)) {
//delete this node
final List<Coordinate> ncs = new ArrayList<Coordinate>();
for (int d=0,z=coos.length; d<z; d++) {
if(d!=j){
ncs.add(coos[d]);
}
}
if(ncs.size() > 1){
final Geometry ls = createLine(ncs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}
break;
}
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
public Geometry deleteNode(final GeometryCollection geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
for (int i=0,n=geo.getNumGeometries(); i<n; i++) {
final Geometry subgeo = geo.getGeometryN(i);
if (subgeo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final Coordinate[] coos = subgeo.getCoordinates();
for (int j=0,m=coos.length; j<m; j++) {
final Coordinate coo = coos[j];
final Point p = createPoint(coo);
if (p.intersects(mouseGeo)) {
//delete this node
final List<Geometry> subs = new ArrayList<Geometry>();
for (int k=0,l=geo.getNumGeometries(); k<l; k++) {
if(k==i){
//this subgeo must be changed
final List<Coordinate> ncs = new ArrayList<Coordinate>();
for (int d=0,z=coos.length; d<z; d++) {
if(d!=j){
ncs.add(coos[d]);
}
}
if(geo instanceof MultiLineString && ncs.size() >1){
subs.add(createLine(ncs));
}else if(geo instanceof MultiPolygon && ncs.size() >2){
subs.add(createPolygon(ncs));
}
}else{
subs.add(geo.getGeometryN(k));
}
}
if(geo instanceof MultiLineString && !subs.isEmpty()){
final Geometry ls = createMultiLine(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else if(geo instanceof MultiPolygon && !subs.isEmpty()){
final Geometry ls = createMultiPolygon(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else{
return null;
}
}
}
break;
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
public Geometry deleteSubGeometry(final GeometryCollection geo, final double mx, final double my) {
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
for (int i=0,n=geo.getNumGeometries(); i<n; i++) {
final Geometry subgeo = geo.getGeometryN(i);
if (subgeo.intersects(mouseGeo)) {
//this geometry intersect the mouse
final List<Geometry> subs = new ArrayList<Geometry>();
for (int k=0,l=geo.getNumGeometries(); k<l; k++) {
if(k!=i){
subs.add(geo.getGeometryN(k));
}
}
if(geo instanceof MultiLineString && !subs.isEmpty()){
final Geometry ls = createMultiLine(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else if(geo instanceof MultiPolygon && !subs.isEmpty()){
final Geometry ls = createMultiPolygon(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else if(geo instanceof MultiPoint && !subs.isEmpty()){
final Geometry ls = createMultiPoint(subs);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else{
return null;
}
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return geo;
}
public Geometry deleteHole(final Geometry geo, final double mx, final double my) {
if(!(geo instanceof Polygon || geo instanceof MultiPolygon)){
//this method only works on polygon or multipolygon
return geo;
}
try{
//transform our mouse in a geometry
final Geometry mouseGeo = mousePositionToGeometry(mx, my);
final List<Polygon> subGeometries = new ArrayList<Polygon>();
final List<LinearRing> holes = new ArrayList<LinearRing>();
for (int i=0,n=geo.getNumGeometries(); i<n; i++) {
//subgeo is a Polygon
subGeometries.add((Polygon) geo.getGeometryN(i));
}
for (int i=0,n=subGeometries.size();i<n;i++) {
final Polygon subgeo = subGeometries.get(i);
//find the hole to remove
int toRemove = -1;
holes.clear();
for(int j=0,k=subgeo.getNumInteriorRing(); j<k; j++){
final LinearRing ring = (LinearRing) subgeo.getInteriorRingN(j);
holes.add(ring);
if(toRemove == -1 && ring.intersects(mouseGeo)){
toRemove = j;
}
}
if(toRemove != -1){
//remove this ring and return geometry
holes.remove(toRemove);
if(geo instanceof Polygon){
final Geometry ls = GEOMETRY_FACTORY.createPolygon((LinearRing)subgeo.getExteriorRing(),
holes.toArray(new LinearRing[holes.size()]));
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else if(geo instanceof MultiPolygon){
//modify the subgeometry
final Polygon poly = GEOMETRY_FACTORY.createPolygon((LinearRing)subgeo.getExteriorRing(),
holes.toArray(new LinearRing[holes.size()]));
subGeometries.set(i, poly);
//recreate the multipolygon
final Geometry ls = createMultiPolygon(subGeometries);
ls.setSRID(geo.getSRID());
ls.setUserData(geo.getUserData());
return ls;
}else{
throw new IllegalStateException("Should not happen, expecting "
+ "Polygon or MultiPolygon but was "+ geo.getClass());
}
}
}
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
//nothing to remove return original geometry
return geo;
}
public Geometry toObjectiveCRS(final Feature sf){
final Object obj = FeatureExt.getDefaultGeometryAttributeValue(sf);
if (obj instanceof Geometry) {
return toObjectiveCRS((Geometry)obj);
}
return null;
}
public Geometry toObjectiveCRS(Geometry geom){
try{
final MathTransform trs = CRS.findOperation(
FeatureExt.getCRS(editedLayer.getCollection().getFeatureType()),
map.getCanvas().getObjectiveCRS2D(), null).getMathTransform();
geom = JTS.transform(geom, trs);
JTS.setCRS(geom, map.getCanvas().getObjectiveCRS2D());
return geom;
}catch(final Exception ex){
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
return null;
}
/**
*
* @param poly : in canvas objective CRS
* @param fl : target layer filter
* @return geometry filter
* @throws org.opengis.geometry.MismatchedDimensionException if crs dimensions do not match
*/
public Filter toFilter(final Geometry poly, final FeatureMapLayer fl) throws MismatchedDimensionException {
final AttributeType desc = FeatureExt.getDefaultGeometryAttribute(fl.getCollection().getFeatureType());
final String geoStr = desc.getName().tip().toString();
final Expression geomField = FF.property(geoStr);
final Geometry dataPoly = poly;
JTS.setCRS(dataPoly, map.getCanvas().getObjectiveCRS2D());
final Expression geomData = FF.literal(dataPoly);
final Filter f = FF.intersects(geomData,geomField);
return f;
}
//manipulating the feature source, transaction -----------------------------
public Feature sourceAddGeometry(Geometry geom) {
if (editedLayer != null && geom != null) {
final FeatureType featureType = (FeatureType) editedLayer.getCollection().getFeatureType();
final CoordinateReferenceSystem dataCrs = FeatureExt.getCRS(featureType);
final Feature feature = featureType.newInstance();
try {
geom = JTS.transform(geom, CRS.findOperation(map.getCanvas().getObjectiveCRS2D(), dataCrs, null).getMathTransform());
} catch (Exception ex) {
LOGGER.log(Level.WARNING, null, ex);
}
feature.setPropertyValue(AttributeConvention.GEOMETRY_PROPERTY.toString(),geom);
if(editedLayer.getCollection().isWritable()){
try {
((FeatureCollection)editedLayer.getCollection()).add(feature);
} catch (Exception ex) {
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
}
map.getCanvas().repaint();
return feature;
}
return null;
}
public void sourceModifyFeature(final Feature feature, final Geometry geo, boolean reprojectToDataCRS){
if(feature == null || geo == null){
//nothing to do
return;
}
final String ID = FeatureExt.getId(feature).getID();
ensureNonNull("geometry", geo);
ensureNonNull("id", ID);
if (editedLayer != null && editedLayer.getCollection().isWritable()) {
final Filter filter = FF.id(Collections.singleton(FF.featureId(ID)));
final FeatureType featureType = editedLayer.getCollection().getFeatureType();
final AttributeType geomAttribut = FeatureExt.getDefaultGeometryAttribute(featureType);
final CoordinateReferenceSystem dataCrs = FeatureExt.getCRS(geomAttribut);
try {
final Geometry geom;
if(reprojectToDataCRS){
geom = JTS.transform(geo,
CRS.findOperation(map.getCanvas().getObjectiveCRS(), dataCrs, null).getMathTransform());
JTS.setCRS(geom, dataCrs);
}else{
geom = geo;
}
editedLayer.getCollection().update(filter, Collections.singletonMap(geomAttribut.getName().toString(), geom));
} catch (final Exception ex) {
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
} finally {
map.getCanvas().repaint();
}
}
}
public void sourceRemoveFeature(final Feature feature){
sourceRemoveFeature(FeatureExt.getId(feature).getID());
}
public void sourceRemoveFeature(final String ID) {
ensureNonNull("id", ID);
if (editedLayer != null && editedLayer.getCollection().isWritable()) {
Filter filter = FF.id(Collections.singleton(FF.featureId(ID)));
try {
editedLayer.getCollection().remove(filter);
} catch (final Exception ex) {
LOGGER.log(Level.WARNING, ex.getLocalizedMessage(),ex);
}
map.getCanvas().repaint();
}
}
//static helper methods -----------------------------------------------------
public static Iterator<EditionTool.Spi> getToolSpis(){
final ServiceLoader<EditionTool.Spi> sl = ServiceLoader.load(EditionTool.Spi.class);
return sl.iterator();
}
public static EditionTool.Spi getToolSpi(String name){
final Iterator<EditionTool.Spi> ite = getToolSpis();
while(ite.hasNext()){
final EditionTool.Spi spi = ite.next();
if(name.equals(spi.getName())){
return spi;
}
}
return null;
}
public static Geometry createGeometry(final List<Coordinate> coords) {
int size = coords.size();
switch (size) {
case 0:
return null;
case 1:
return createPoint(coords.get(0));
case 2:
return createLine(coords);
default:
return createLine(coords);
}
}
public static Point createPoint(final Coordinate coord) {
return GEOMETRY_FACTORY.createPoint(coord);
}
public static MultiPoint createMultiPoint(final List<? extends Geometry> geoms) {
List<Point> lst = new ArrayList<Point>();
for (Geometry go : geoms) {
if (go instanceof Point) {
lst.add((Point) go);
}
}
return GEOMETRY_FACTORY.createMultiPoint(lst.toArray(new Point[lst.size()]));
}
public static LineString createLine(final Coordinate ... coords) {
return GEOMETRY_FACTORY.createLineString(coords);
}
public static LineString createLine(final List<Coordinate> coords) {
return GEOMETRY_FACTORY.createLineString(coords.toArray(EMPTY_COORDINATE_ARRAY));
}
public static LinearRing createLinearRing(List<Coordinate> coords) {
coords = new ArrayList<Coordinate>(coords);
if (!(coords.get(0).equals2D(coords.get(coords.size() - 1)))) {
Coordinate coo = new Coordinate(coords.get(0));
coords.add(coo);
}
if(coords.size() == 3){
Coordinate coo = new Coordinate(coords.get(0));
coords.add(coo);
}
return GEOMETRY_FACTORY.createLinearRing(coords.toArray(EMPTY_COORDINATE_ARRAY));
}
public static Polygon createPolygon(final List<Coordinate> coords, LinearRing ... holes) {
LinearRing ring = createLinearRing(coords);
return GEOMETRY_FACTORY.createPolygon(ring, holes);
}
public static MultiPolygon createMultiPolygon(final List<? extends Geometry> geoms) {
List<Polygon> lst = new ArrayList<Polygon>();
for (Geometry go : geoms) {
if (go instanceof Polygon) {
lst.add((Polygon) go);
}else{
throw new IllegalArgumentException("Found an unexpected geometry type while building multipolygon : " + go.getClass());
}
}
return GEOMETRY_FACTORY.createMultiPolygon(lst.toArray(new Polygon[lst.size()]));
}
public static MultiLineString createMultiLine(final List<? extends Geometry> geoms) {
List<LineString> lst = new ArrayList<LineString>();
for (Geometry go : geoms) {
if (go instanceof LineString) {
lst.add((LineString) go);
}
}
return GEOMETRY_FACTORY.createMultiLineString(lst.toArray(new LineString[lst.size()]));
}
}