//$HeadURL$ /*---------------------------------------------------------------------------- This file is part of deegree, http://deegree.org/ Copyright (C) 2001-2009 by: - Department of Geography, University of Bonn - and - lat/lon GmbH - 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 information: lat/lon GmbH Aennchenstr. 19, 53177 Bonn Germany http://lat-lon.de/ Department of Geography, University of Bonn Prof. Dr. Klaus Greve Postfach 1147, 53001 Bonn Germany http://www.geographie.uni-bonn.de/deegree/ e-mail: info@deegree.org ----------------------------------------------------------------------------*/ package org.deegree.igeo.commands.digitize; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.UUID; import javax.vecmath.Vector2d; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.Pair; import org.deegree.igeo.ApplicationContainer; import org.deegree.igeo.commands.model.AddErrorLayerCommand; import org.deegree.igeo.dataadapter.FeatureAdapter; import org.deegree.igeo.i18n.Messages; import org.deegree.igeo.mapmodel.Layer; import org.deegree.igeo.mapmodel.MapModel; import org.deegree.igeo.mapmodel.MapModelEntry; import org.deegree.igeo.mapmodel.MapModelException; import org.deegree.kernel.AbstractCommand; import org.deegree.kernel.Command; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureCollection; import org.deegree.model.feature.FeatureFactory; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.PropertyType; 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.MultiSurface; import org.deegree.model.spatialschema.Point; import org.deegree.model.spatialschema.Position; import org.deegree.model.spatialschema.PositionImpl; import org.deegree.model.spatialschema.Surface; import org.deegree.model.spatialschema.SurfaceInterpolationImpl; /** * {@link Command} implementation for creating polygon finding a closed graph that can be used as border of an empty * area (no geometry already there) * * @author <a href="mailto:name@deegree.org">Andreas Poth</a> * @author last edited by: $Author$ * * @version $Revision$, $Date$ */ public class CreatePolygonFromBordersCommand extends AbstractCommand { private static final ILogger LOG = LoggerFactory.getLogger( CreatePolygonFromBordersCommand.class ); private static final QualifiedName name = new QualifiedName( "Create Polygon from Borders" ); private ApplicationContainer<?> appCont; private MapModel mapModel; private Layer layer; private Point clickPoint; /** * * @param appCont * @param clickPoint */ public CreatePolygonFromBordersCommand( ApplicationContainer<?> appCont, Point clickPoint ) { this.appCont = appCont; this.clickPoint = clickPoint; mapModel = appCont.getMapModel( null ); List<MapModelEntry> list = mapModel.getMapModelEntriesSelectedForAction( MapModel.SELECTION_EDITING ); if ( list.size() == 0 ) { throw new MapModelException( Messages.get( "$DG10105" ) ); } layer = (Layer) list.get( 0 ); } /* * (non-Javadoc) * * @see org.deegree.kernel.Command#execute() */ public void execute() throws Exception { FeatureAdapter adapter = (FeatureAdapter) layer.getDataAccess().get( 0 ); List<Pair<Position, Position>> edges = getEdges( adapter ); for ( Pair<Position, Position> pair : edges ) { // ensure all edges have same direction according to click point if ( ccw( pair.first, pair.second, clickPoint.getPosition() ) < 0 ) { Position p = pair.first; pair.first = pair.second; pair.second = p; } } // find nearest edge an remove it from edges list Pair<Position, Position> edge = findStartEgde( clickPoint, edges ); // create a real copy List<Pair<Position, Position>> polygonEdges = new ArrayList<Pair<Position, Position>>( 1000 ); polygonEdges.add( edge ); do { edge = findNextEdge( edges, polygonEdges.get( polygonEdges.size() - 1 ) ); if ( edge != null ) { polygonEdges.add( edge ); } } while ( edge != null && !polygonEdges.get( 0 ).first.equals( edge.second ) ); if ( edge == null ) { LOG.logError( "ring is not closed" ); createErrorlayer( "ring is not closed", edges.get( 0 ).first, edges.get( edges.size() - 1 ).second ); return; } // create position list to create a new surface // for this deep clone has to made to avoid having several geometries // sharing the same Position instances List<Position> positions = new ArrayList<Position>( polygonEdges.size() + 2 ); positions.add( (Position) ( (PositionImpl) polygonEdges.get( 0 ).first ).clone() ); positions.add( (Position) ( (PositionImpl) polygonEdges.get( 0 ).second ).clone() ); for ( int i = 1; i < polygonEdges.size(); i++ ) { positions.add( (Position) ( (PositionImpl) polygonEdges.get( i ).second ).clone() ); } Position[] tt = positions.toArray( new Position[positions.size()] ); Surface surface = GeometryFactory.createSurface( tt, null, new SurfaceInterpolationImpl(), mapModel.getCoordinateSystem() ); if ( !surface.contains( clickPoint ) ) { LOG.logError( "click point is outside result polygons" ); createErrorlayer( "click point is outside result polygons", tt ); return; } // create new feature for filling polygon FeatureType ft = adapter.getSchema(); PropertyType[] pts = ft.getProperties(); FeatureProperty[] fps = new FeatureProperty[pts.length]; for ( int i = 0; i < pts.length; i++ ) { if ( pts[i].getType() == Types.GEOMETRY ) { fps[i] = FeatureFactory.createFeatureProperty( pts[i].getName(), surface ); } else { fps[i] = FeatureFactory.createFeatureProperty( pts[i].getName(), null ); } } Feature feature = FeatureFactory.createFeature( "UUID_" + UUID.randomUUID().toString(), ft, fps ); adapter.insertFeature( feature ); } private void createErrorlayer( String message, Position... posList ) throws Exception { List<Pair<String, Point>> errorLocations = new ArrayList<Pair<String, Point>>( posList.length ); for ( Position position : posList ) { Pair<String, Point> pa = new Pair<String, Point>(); pa.first = message; pa.second = GeometryFactory.createPoint( position, mapModel.getCoordinateSystem() ); errorLocations.add( pa ); } Command cmd = new AddErrorLayerCommand( appCont, layer, errorLocations ); appCont.getCommandProcessor().executeSychronously( cmd, true ); } /** * @param edges * @param second * @return */ private Pair<Position, Position> findNextEdge( List<Pair<Position, Position>> edges, Pair<Position, Position> lastEdge ) { List<Pair<Position, Position>> list = new ArrayList<Pair<Position, Position>>( 5 ); // find edge where its first position is equal to last position of last edge for ( Pair<Position, Position> pair : edges ) { if ( pair.first.equals( lastEdge.second ) ) { list.add( pair ); } } // if no edge has been found (which should not happen) try finding an edge that // also ends where the last edge ends and invert its order. if ( list.size() == 0 ) { for ( Pair<Position, Position> pair : edges ) { if ( pair.second.equals( lastEdge.second ) ) { Position p = pair.first; pair.first = pair.second; pair.second = p; list.add( pair ); } } } if ( list.size() == 1 ) { edges.remove( list.get( 0 ) ); return list.get( 0 ); } // find edge that don't have an outer side between itself and click point. Pair<Position, Position> tmp = null; double a = -9E99; for ( int i = 0; i < list.size(); i++ ) { Vector2d vec1 = new Vector2d( list.get( i ).second.getX() - list.get( i ).first.getX(), list.get( i ).second.getY() - list.get( i ).first.getY() ); Vector2d vec2 = new Vector2d( lastEdge.first.getX() - lastEdge.second.getX(), lastEdge.first.getY() - lastEdge.second.getY() ); double angle = Math.toDegrees( vec1.angle( vec2 ) ); if ( ccw( lastEdge.first, lastEdge.second, list.get( i ).second ) >= 0 ) { angle = 360 - angle; } if ( angle > a ) { a = angle; tmp = list.get( i ); } } edges.remove( tmp ); return tmp; } /** * @param edges * @return * @throws GeometryException */ private Pair<Position, Position> findStartEgde( Point clickPoint, List<Pair<Position, Position>> edges ) throws GeometryException { double distance = 9E99; Pair<Position, Position> nearest = null; Position[] p = new Position[2]; for ( Pair<Position, Position> pair : edges ) { p[0] = pair.first; p[1] = pair.second; Curve curve = GeometryFactory.createCurve( p, mapModel.getCoordinateSystem() ); double d = clickPoint.distance( curve ); if ( d < distance ) { distance = d; nearest = pair; } } edges.remove( nearest ); return nearest; } private List<Pair<Position, Position>> getEdges( FeatureAdapter adapter ) throws GeometryException { FeatureCollection fc = adapter.getFeatureCollection(); List<Pair<Position, Position>> list = new ArrayList<Pair<Position, Position>>( fc.size() * 10 ); Iterator<Feature> iterator = fc.iterator(); while ( iterator.hasNext() ) { Feature feature = (Feature) iterator.next(); Geometry g = feature.getDefaultGeometryPropertyValue(); if ( g instanceof Curve ) { Position[] pos = ( (Curve) g ).getAsLineString().getPositions(); for ( int i = 0; i < pos.length - 1; i++ ) { Pair<Position, Position> p = new Pair<Position, Position>( pos[i], pos[i + 1] ); list.add( p ); } } else if ( g instanceof MultiCurve ) { Curve[] curves = ( (MultiCurve) g ).getAllCurves(); for ( Curve curve : curves ) { Position[] pos = curve.getAsLineString().getPositions(); for ( int i = 0; i < pos.length - 1; i++ ) { Pair<Position, Position> p = new Pair<Position, Position>( pos[i], pos[i + 1] ); list.add( p ); } } } else if ( g instanceof Surface ) { Position[] pos = ( (Surface) g ).getSurfaceBoundary().getExteriorRing().getPositions(); for ( int i = 0; i < pos.length - 1; i++ ) { Pair<Position, Position> p = new Pair<Position, Position>( pos[i], pos[i + 1] ); list.add( p ); } } else if ( g instanceof MultiSurface ) { Surface[] surfaces = ( (MultiSurface) g ).getAllSurfaces(); for ( Surface surface : surfaces ) { Position[] pos = surface.getSurfaceBoundary().getExteriorRing().getPositions(); for ( int i = 0; i < pos.length - 1; i++ ) { Pair<Position, Position> p = new Pair<Position, Position>( pos[i], pos[i + 1] ); list.add( p ); } } } } return list; } /* * (non-Javadoc) * * @see org.deegree.kernel.Command#getName() */ public QualifiedName getName() { return name; } /* * (non-Javadoc) * * @see org.deegree.kernel.Command#getResult() */ public Object getResult() { // TODO Auto-generated method stub return null; } /** * * @param start * @param end * @param p * @return -1 if the points are counter clock wise, 0 if the points have a direction and 1 if they are clockwise. */ protected static int ccw( Position start, Position end, Position p ) { double X1 = start.getX(); double Y1 = start.getY(); double X2 = end.getX(); double Y2 = end.getY(); double PX = p.getX(); double PY = p.getY(); X2 -= X1; Y2 -= Y1; PX -= X1; PY -= Y1; double ccw = ( PX * Y2 ) - ( PY * X2 ); if ( ccw == 0.0 ) { ccw = ( PX * X2 ) + ( PY * Y2 ); if ( ccw > 0.0 ) { PX -= X2; PY -= Y2; ccw = ( PX * X2 ) + ( PY * Y2 ); if ( ccw < 0.0 ) { ccw = 0.0; } } } return ( ccw < 0.0 ) ? ( -1 ) : ( ( ccw > 0.0 ) ? 1 : 0 ); } }