//$HeadURL$
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2008 by:
Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
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; either
version 2.1 of the License, or (at your option) any later version.
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.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53177 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.igeo.views;
import static java.awt.geom.Line2D.ptSegDist;
import static java.lang.Double.MAX_VALUE;
import java.awt.Point;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.util.GeometryUtils;
import org.deegree.graphics.transformation.GeoTransform;
import org.deegree.igeo.ApplicationContainer;
import org.deegree.igeo.ChangeListener;
import org.deegree.igeo.ValueChangedEvent;
import org.deegree.igeo.dataadapter.DataAccessAdapter;
import org.deegree.igeo.dataadapter.FeatureAdapter;
import org.deegree.igeo.mapmodel.Layer;
import org.deegree.igeo.mapmodel.LayerGroup;
import org.deegree.igeo.mapmodel.MapModel;
import org.deegree.igeo.mapmodel.MapModelVisitor;
import org.deegree.igeo.settings.Settings;
import org.deegree.igeo.settings.SnappingLayersOpts;
import org.deegree.igeo.settings.SnappingToleranceOpt;
import org.deegree.model.feature.Feature;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.filterencoding.FilterEvaluationException;
import org.deegree.model.spatialschema.Curve;
import org.deegree.model.spatialschema.Geometry;
import org.deegree.model.spatialschema.GeometryException;
import org.deegree.model.spatialschema.GeometryFactory;
import org.deegree.model.spatialschema.MultiCurve;
import org.deegree.model.spatialschema.MultiPoint;
import org.deegree.model.spatialschema.MultiSurface;
import org.deegree.model.spatialschema.Position;
import org.deegree.model.spatialschema.Ring;
import org.deegree.model.spatialschema.Surface;
/**
* This class enables snapping a digitizing cursor to existing vertexes and/or lines. It reads snapping settings from
* current configuration document and layers to be used as snapping targets from current map model. A layer to be used
* as target must be selected for snapping.<br>
*
* <pre>
* appCont.getMapModel( null ).getLayersSelectedForAction( "snapping" );
* </pre>
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
public class Snapper implements ChangeListener {
private static final ILogger LOG = LoggerFactory.getLogger( Snapper.class );
public static final String SNAPPING = "snapping";
public enum SNAPTARGET {
Vertex, StartNode, EndNode, Edge, EdgeCenter
};
private GeoTransform gt;
private List<LayerSnapInfo> layers = new ArrayList<LayerSnapInfo>( 50 );
private MapModel mapModel;
private ApplicationContainer<?> appCont;
/**
*
* @param appCont
* container for currently loaded project
*/
public Snapper( ApplicationContainer<?> appCont ) {
this.appCont = appCont;
mapModel = appCont.getMapModel( null );
initSnapInfoList();
// register as change listener to update snapping targets if map model
// (e.g. boundingbox) changes
mapModel.addChangeListener( this );
}
/**
* fills list of layers to be considered for snapping
*/
public void initSnapInfoList() {
Settings settings = appCont.getSettings();
final SnappingLayersOpts slo = settings.getSnappingLayersOptions();
layers.clear();
try {
mapModel.walkLayerTree( new MapModelVisitor() {
public void visit( Layer layer )
throws Exception {
if ( slo.isSelectedForSnapping( layer.getIdentifier() ) ) {
List<SNAPTARGET> snapOrder = slo.getSelectedForSnappingList( layer.getIdentifier() );
layers.add( new LayerSnapInfo( layer, snapOrder ) );
}
}
public void visit( LayerGroup layerGroup )
throws Exception {
}
} );
} catch ( Exception e ) {
LOG.logWarning( "ignore", e );
}
// collect vertices and segments of a map
valueChanged( null );
}
/**
* removes Snapper as listener from current map model. For example will be invoked when finishDrawing() method of a
* DrawingPane will be invoked
*/
public void dispose() {
mapModel.removeChangeListener( this );
}
/**
* this method should just be used for drawing !!!!!!!!
* @see #snap(Position)
* @param point
* @return snapped point
*/
public Point snap( Point point ) {
double xx = gt.getSourceX( point.x );
double yy = gt.getSourceY( point.y );
Position p = snap( xx, yy );
if ( p != null ) {
point.x = (int) Math.round( gt.getDestX( p.getX() ) );
point.y = (int) Math.round( gt.getDestY( p.getY() ) );
}
return point;
}
/**
* @see #snap(Position)
* @param point
* @return snapped point
*/
public Position snapPos( Point point ) {
double xx = gt.getSourceX( point.x );
double yy = gt.getSourceY( point.y );
Position p = snap( xx, yy );
return p;
}
/**
*
* @see #snap(Position)
* @param x
* @param y
* @return snapped point
*/
public Position snap( double x, double y ) {
return snap( GeometryFactory.createPosition( x, y ) );
}
/**
* snaps a passed point (geographic coordinates) to the matching target. If no matching target can be found because
* no geometry of a layer selected for snapping is within current snapping distance, the passed point will be
* returned.
*
* @param position
* @return snapped point
*/
public Position snap( Position position ) {
Position result = position;
double distance = Double.MAX_VALUE;
for ( LayerSnapInfo layerSnapInfo : layers ) {
Position tmp = layerSnapInfo.snap( position );
double td = GeometryUtils.distance( tmp, position );
if ( td < distance && tmp != position ) {
distance = td;
result = tmp;
}
}
return GeometryFactory.createPosition( result.getAsArray() );
}
/*
* (non-Javadoc)
*
* @see org.deegree.igeo.ChangeListener#valueChanged(org.deegree.igeo.ValueChangedEvent)
*/
public void valueChanged( ValueChangedEvent event ) {
gt = mapModel.getToTargetDeviceTransformation();
try {
for ( LayerSnapInfo snapInfo : layers ) {
snapInfo.collectVertexes();
}
} catch ( FilterEvaluationException e ) {
LOG.logError( e.getMessage(), e );
}
}
/**
*
*
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
private class LayerSnapInfo {
private Layer layer;
private List<Position> vertices;
private List<Position[]> edges;
private List<Position> startNodes;
private List<Position> endNodes;
private List<Position> edgeCenters;
private List<SNAPTARGET> snapOrder;
private double tolerance;
/**
*
* @param layer
*/
private LayerSnapInfo( Layer layer, List<SNAPTARGET> snapOrder ) {
this.layer = layer;
this.snapOrder = snapOrder;
vertices = new ArrayList<Position>( 5000 );
startNodes = new ArrayList<Position>( 5000 );
endNodes = new ArrayList<Position>( 5000 );
edges = new ArrayList<Position[]>( 5000 );
edgeCenters = new ArrayList<Position>( 5000 );
}
private void collectVertexes()
throws FilterEvaluationException {
vertices.clear();
startNodes.clear();
endNodes.clear();
edges.clear();
edgeCenters.clear();
SnappingToleranceOpt sto = appCont.getSettings().getSnappingToleranceOptions();
tolerance = sto.getValue();
String snapUnits = sto.getUOM();
if ( snapUnits.equalsIgnoreCase( "Pixel" ) ) {
// if snapping tolerance has been defined as pixels it must be converted
// into map units
GeoTransform gt = mapModel.getToTargetDeviceTransformation();
double x1 = gt.getSourceX( tolerance );
double x2 = gt.getSourceX( 0 );
tolerance = Math.abs( x2 - x1 );
}
List<DataAccessAdapter> tmp = layer.getDataAccess();
for ( DataAccessAdapter adapter : tmp ) {
if ( adapter instanceof FeatureAdapter ) {
FeatureCollection fc = ( (FeatureAdapter) adapter ).getFeatureCollection( mapModel.getEnvelope() );
Iterator<Feature> iterator = fc.iterator();
while ( iterator.hasNext() ) {
Geometry[] geometries = iterator.next().getGeometryPropertyValues();
for ( Geometry geometry : geometries ) {
if ( geometry instanceof org.deegree.model.spatialschema.Point ) {
vertices.add( ( (org.deegree.model.spatialschema.Point) geometry ).getPosition() );
} else if ( geometry instanceof Curve ) {
Curve curve = (Curve) geometry;
collectCurveVertexes( curve );
} else if ( geometry instanceof Surface ) {
Surface surface = (Surface) geometry;
collectSurfaceVertexes( surface );
} else if ( geometry instanceof MultiPoint ) {
org.deegree.model.spatialschema.Point[] points = ( (MultiPoint) geometry ).getAllPoints();
for ( org.deegree.model.spatialschema.Point point : points ) {
vertices.add( point.getPosition() );
}
} else if ( geometry instanceof MultiCurve ) {
Curve[] curves = ( (MultiCurve) geometry ).getAllCurves();
for ( Curve curve : curves ) {
collectCurveVertexes( curve );
}
} else if ( geometry instanceof MultiSurface ) {
Surface[] surfaces = ( (MultiSurface) geometry ).getAllSurfaces();
for ( Surface surface : surfaces ) {
collectSurfaceVertexes( surface );
}
}
}
}
}
}
LOG.logDebug( vertices.size() + " vertices found for snapping for layer: " + layer.getTitle() );
LOG.logDebug( startNodes.size() + " edge start positions found for snapping for layer: " + layer.getTitle() );
LOG.logDebug( endNodes.size() + " edge end positions found for snapping for layer: " + layer.getTitle() );
LOG.logDebug( edges.size() + " edges found for snapping for layer: " + layer.getTitle() );
LOG.logDebug( edgeCenters.size() + " edge centers found for snapping for layer: " + layer.getTitle() );
}
private void collectSurfaceVertexes( Surface surface ) {
Position[] pos = surface.getSurfaceBoundary().getExteriorRing().getPositions();
for ( int i = 0; i < pos.length - 1; i++ ) {
vertices.add( pos[i] );
edges.add( new Position[] { pos[i], pos[i + 1] } );
double x = pos[i].getX() / 2d + pos[i + 1].getX() / 2d;
double y = pos[i].getY() / 2d + pos[i + 1].getY() / 2d;
edgeCenters.add( GeometryFactory.createPosition( x, y ) );
}
vertices.add( pos[pos.length - 1] );
Ring[] rings = surface.getSurfaceBoundary().getInteriorRings();
for ( int k = 0; k < rings.length; k++ ) {
pos = rings[k].getPositions();
for ( int i = 0; i < pos.length - 1; i++ ) {
vertices.add( pos[i] );
edges.add( new Position[] { pos[i], pos[i + 1] } );
double x = pos[i].getX() / 2d + pos[i + 1].getX() / 2d;
double y = pos[i].getY() / 2d + pos[i + 1].getY() / 2d;
edgeCenters.add( GeometryFactory.createPosition( x, y ) );
}
vertices.add( pos[pos.length - 1] );
}
}
private void collectCurveVertexes( Curve curve ) {
startNodes.add( curve.getStartPoint().getPosition() );
endNodes.add( curve.getEndPoint().getPosition() );
try {
Position[] pos = curve.getAsLineString().getPositions();
for ( int i = 0; i < pos.length - 1; i++ ) {
vertices.add( pos[i] );
edges.add( new Position[] { pos[i], pos[i + 1] } );
double x = pos[i].getX() / 2d + pos[i + 1].getX() / 2d;
double y = pos[i].getY() / 2d + pos[i + 1].getY() / 2d;
edgeCenters.add( GeometryFactory.createPosition( x, y ) );
}
vertices.add( pos[pos.length - 1] );
} catch ( GeometryException e ) {
LOG.logWarning( "ignore", e );
}
}
/**
* @see #snap(double, double)
* @param position
* @return snapped point
*/
private Position snap( Position position ) {
Position target = position;
if ( layers.size() != 0 ) {
for ( SNAPTARGET snapTarget : snapOrder ) {
switch ( snapTarget ) {
case Vertex:
target = vertexCheck( position );
break;
case StartNode:
target = startNodeCheck( position );
break;
case EndNode:
target = endNodeCheck( position );
break;
case Edge:
target = edgeCheck( position );
break;
case EdgeCenter:
target = edgeCenterCheck( position );
break;
}
if ( !position.equals( target ) ) {
break;
}
}
}
return target;
}
private Position edgeCheck( Position position ) {
Position target = position;
final double posx = position.getX();
final double posy = position.getY();
double distance = MAX_VALUE;
for ( Position[] segment : edges ) {
double tmp = ptSegDist( segment[0].getX(), segment[0].getY(), segment[1].getX(), segment[1].getY(),
position.getX(), position.getY() );
if ( tmp <= tolerance && tmp < distance ) {
final double dx = segment[1].getX() - segment[0].getX();
final double dy = segment[1].getY() - segment[0].getY();
final double m1 = dy / dx;
final double m2 = -dx / dy;
final double nx = ( posy - m2 * posx - segment[0].getY() + m1 * segment[0].getX() ) / ( m1 - m2 );
final double ny = m2 * ( nx - posx ) + posy;
return GeometryFactory.createPosition( nx, ny );
}
}
return target;
}
private Position endNodeCheck( Position position ) {
Position target = position;
double distance = Double.MAX_VALUE;
for ( Position pos : endNodes ) {
double tmp = GeometryUtils.distance( pos, position );
if ( tmp <= tolerance && tmp < distance ) {
distance = tmp;
target = pos;
}
}
return target;
}
private Position startNodeCheck( Position position ) {
Position target = position;
double distance = Double.MAX_VALUE;
for ( Position pos : startNodes ) {
double tmp = GeometryUtils.distance( pos, position );
if ( tmp <= tolerance && tmp < distance ) {
distance = tmp;
target = pos;
}
}
return target;
}
private Position vertexCheck( Position position ) {
Position target = position;
double distance = Double.MAX_VALUE;
for ( Position pos : vertices ) {
double tmp = GeometryUtils.distance( pos, position );
if ( tmp <= tolerance && tmp < distance ) {
distance = tmp;
target = pos;
}
}
return target;
}
private Position edgeCenterCheck( Position position ) {
Position target = position;
double distance = Double.MAX_VALUE;
for ( Position pos : edgeCenters ) {
double tmp = GeometryUtils.distance( pos, position );
if ( tmp <= tolerance && tmp < distance ) {
distance = tmp;
target = pos;
}
}
return target;
}
}
}