[FEATURE] allow editing of invalid geometry in node tool

git-svn-id: http://svn.osgeo.org/qgis/trunk@12749 c8812cc2-4d05-0410-92ff-de0c093fc19c
This commit is contained in:
jef 2010-01-12 18:17:34 +00:00
parent 41654d59ff
commit 59b0d23cdd
6 changed files with 473 additions and 41 deletions

View File

@ -24,6 +24,10 @@
#include <math.h>
#include <QMouseEvent>
#include <QMessageBox>
#include "qgslogger.h"
#include "qgisapp.h"
#include <QStatusBar>
QgsRubberBand* QgsMapToolNodeTool::createRubberBandMarker( QgsPoint center, QgsVectorLayer* vlayer )
@ -128,7 +132,7 @@ void QgsMapToolNodeTool::layerModified( bool onlyGeometry )
catch ( ... )
{
//GEOS is throwing exception when polygon is not valid to be able to edit it
//Only possibility to fix this operation sice node tool doesn't allow to strore invalid geometry after editing
//Only possibility to fix this operation since node tool doesn't allow to store invalid geometry after editing
mSelectionFeature->updateFromFeature();
//if it does update markers just to be sure of correctness
}
@ -148,7 +152,7 @@ void QgsMapToolNodeTool::createMovingRubberBands()
for ( int i = 0; i < vertexMap.size(); i++ )
{
//create rubber band
if ( vertexMap[i].selected && !( vertexMap[i].inRubberBand ) )
if ( vertexMap[i].selected && !vertexMap[i].inRubberBand )
{
geometry->adjacentVertices( i, beforeVertex, afterVertex );
vertex = i;
@ -774,7 +778,7 @@ void QgsMapToolNodeTool::removeRubberBands()
mTopologyMovingVertexes.clear();
mTopologyRubberBandVertexes.clear();
//remove all data from selected feature (no change to rubber bands itself
//remove all data from selected feature (no change to rubber bands itself)
if ( mSelectionFeature != NULL )
mSelectionFeature->cleanRubberBandsData();
}
@ -936,8 +940,46 @@ void SelectionFeature::setSelectedFeature( int featureId, QgsVectorLayer* vlaye
{
mFeature = feature;
}
//createvertexmap
createVertexMap();
validateGeometry();
}
void SelectionFeature::validateGeometry( QgsGeometry *g )
{
QgsDebugMsg( "validating geometry" );
if ( g == NULL )
g = mFeature->geometry();
g->validateGeometry( mGeomErrors );
while ( !mGeomErrorMarkers.isEmpty() )
{
delete mGeomErrorMarkers.takeFirst();
}
QString tip;
for ( int i = 0; i < mGeomErrors.size(); i++ )
{
tip += mGeomErrors[i].what() + "\n";
if ( !mGeomErrors[i].hasWhere() )
continue;
QgsVertexMarker *vm = createVertexMarker( mGeomErrors[i].where(), QgsVertexMarker::ICON_X );
vm->setToolTip( mGeomErrors[i].what() );
vm->setColor( Qt::green );
vm->setZValue( vm->zValue() + 1 );
mGeomErrorMarkers << vm;
}
QStatusBar *sb = QgisApp::instance()->statusBar();
sb->showMessage( QObject::tr( "%n geometry error(s) found.", "number of geometry errors", mGeomErrors.size() ) );
if ( !tip.isEmpty() )
sb->setToolTip( tip );
}
void SelectionFeature::deleteSelectedVertexes()
@ -983,7 +1025,10 @@ void SelectionFeature::deleteSelectedVertexes()
}
QgsFeature f;
mVlayer->featureAtId( mFeatureId, f, true, false );
if ( !GEOSisValid( f.geometry()->asGeos() ) )
bool wasValid = false; // mGeomErrors.isEmpty();
bool isValid = GEOSisValid( f.geometry()->asGeos() );
if ( wasValid && !isValid )
{
QMessageBox::warning( NULL,
tr( "Node tool" ),
@ -992,7 +1037,7 @@ void SelectionFeature::deleteSelectedVertexes()
QMessageBox::Ok );
}
if ( count != 0 && GEOSisValid( f.geometry()->asGeos() ) )
if ( count != 0 && ( !wasValid || isValid ) )
{
mVlayer->endEditCommand();
}
@ -1054,7 +1099,9 @@ void SelectionFeature::moveSelectedVertexes( double changeX, double changeY )
}
QgsFeature f;
mVlayer->featureAtId( mFeatureId, f, true, false );
if ( !GEOSisValid( f.geometry()->asGeos() ) )
bool wasValid = false; // mGeomErrors.isEmpty();
bool isValid = GEOSisValid( f.geometry()->asGeos() );
if ( wasValid && !isValid )
{
QMessageBox::warning( NULL,
tr( "Node tool" ),
@ -1068,17 +1115,18 @@ void SelectionFeature::moveSelectedVertexes( double changeX, double changeY )
else
{
mVlayer->endEditCommand();
validateGeometry( f.geometry() );
}
updateFeature();
}
QgsVertexMarker* SelectionFeature::createVertexMarker( QgsPoint center )
QgsVertexMarker *SelectionFeature::createVertexMarker( QgsPoint center, QgsVertexMarker::IconType type )
{
QgsVertexMarker* marker = new QgsVertexMarker( mCanvas );
QgsVertexMarker *marker = new QgsVertexMarker( mCanvas );
QgsPoint newCenter = mCanvas->mapRenderer()->layerToMapCoordinates( mVlayer, center );
marker->setCenter( newCenter );
marker->setIconType( QgsVertexMarker::ICON_BOX );
marker->setIconType( type );
marker->setColor( Qt::red );
@ -1103,6 +1151,11 @@ void SelectionFeature::deleteVertexMap()
VertexEntry entry = mVertexMap.takeLast();
delete entry.vertexMarker;
}
while ( !mGeomErrorMarkers.isEmpty() )
{
delete mGeomErrorMarkers.takeFirst();
}
}
bool SelectionFeature::isSelected( int vertexNr )
@ -1134,6 +1187,7 @@ void SelectionFeature::createVertexMapPolygon()
entry.originalIndex = i;
entry.inRubberBand = false;
QgsVertexMarker* marker = createVertexMarker( poly[i] );
marker->setToolTip( tr( "ring %1, vertex %2" ).arg( i2 ).arg( i ) );
entry.vertexMarker = marker;
mVertexMap.insert( y + i, entry );
}
@ -1161,7 +1215,7 @@ void SelectionFeature::createVertexMapPolygon()
entry.originalIndex = y + i - 1;
entry.inRubberBand = false;
QgsVertexMarker* marker = createVertexMarker( poly[i] );
marker->setToolTip( tr( "polygon %1, ring %2, vertex %3" ).arg( i2 ).arg( i3 ).arg( i ) );
entry.vertexMarker = marker;
mVertexMap.insert( y + i, entry );
}
@ -1194,6 +1248,7 @@ void SelectionFeature::createVertexMapLine()
entry.originalIndex = i;
entry.inRubberBand = false;
QgsVertexMarker* marker = createVertexMarker( poly[i] );
marker->setToolTip( tr( "polyline %1, vertex %2" ).arg( i2 ).arg( i ) );
entry.vertexMarker = marker;
mVertexMap.insert( y + i, entry );
}
@ -1214,6 +1269,7 @@ void SelectionFeature::createVertexMapLine()
entry.originalIndex = i;
entry.inRubberBand = false;
QgsVertexMarker* marker = createVertexMarker( poly[i] );
marker->setToolTip( tr( "vertex %1" ).arg( i ) );
entry.vertexMarker = marker;
mVertexMap.insert( i, entry );
}
@ -1236,6 +1292,7 @@ void SelectionFeature::createVertexMapPoint()
entry.originalIndex = 1;
entry.inRubberBand = false;
QgsVertexMarker* marker = createVertexMarker( poly[i] );
marker->setToolTip( tr( "point %1" ).arg( i ) );
entry.vertexMarker = marker;
mVertexMap.insert( i, entry );
}
@ -1251,6 +1308,7 @@ void SelectionFeature::createVertexMapPoint()
entry.originalIndex = 1;
entry.inRubberBand = false;
QgsVertexMarker* marker = createVertexMarker( poly );
marker->setToolTip( tr( "single point" ) );
entry.vertexMarker = marker;
mVertexMap.insert( 1, entry );
}
@ -1372,4 +1430,3 @@ QgsVectorLayer* SelectionFeature::vlayer()
{
return mVlayer;
}

View File

@ -161,7 +161,7 @@ class SelectionFeature: public QObject
* @param center center of marker
* @return created vertex marker
*/
QgsVertexMarker* createVertexMarker( QgsPoint center );
QgsVertexMarker* createVertexMarker( QgsPoint center, QgsVertexMarker::IconType type = QgsVertexMarker::ICON_BOX );
/**
* Getter for getting vector layer which selection is working
@ -171,7 +171,6 @@ class SelectionFeature: public QObject
private:
/**
* Deletes whole vertex map.
*/
@ -198,10 +197,15 @@ class SelectionFeature: public QObject
void createVertexMapPoint();
/**
* Updates stored feauture to actual one loaded from layer
* Updates stored feature to actual one loaded from layer
*/
void updateFeature();
/**
* Validates the geometry
*/
void validateGeometry( QgsGeometry *g = NULL );
QgsFeature* mFeature;
int mFeatureId;
bool mFeatureSelected;
@ -209,6 +213,9 @@ class SelectionFeature: public QObject
QgsRubberBand* mRubberBand;
QList<VertexEntry> mVertexMap;
QgsMapCanvas* mCanvas;
QList< QgsGeometry::Error > mGeomErrors;
QList< QgsVertexMarker * > mGeomErrorMarkers;
};
/**A maptool to move/deletes/adds vertices of line or polygon features*/
@ -367,7 +374,6 @@ class QgsMapToolNodeTool: public QgsMapToolVertexEdit
/** flag to tell if edition points */
bool mIsPoint;
};

View File

@ -2360,7 +2360,7 @@ double QgsGeometry::closestVertexWithContext( const QgsPoint& point, int& atVert
return -1;
const GEOSGeometry *g = GEOSGetExteriorRing( mGeos );
if ( g == NULL )
if ( !g )
return -1;
const GEOSCoordSequence *sequence = GEOSGeom_getCoordSeq( g );
@ -5442,7 +5442,7 @@ int QgsGeometry::topologicalTestPointsSplit( const GEOSGeometry* splitLine, QLis
testPoints.clear();
GEOSGeometry* intersectionGeom = GEOSIntersection( mGeos, splitLine );
if ( intersectionGeom == NULL )
if ( !intersectionGeom )
{
return 1;
}
@ -5853,17 +5853,17 @@ QgsMultiPolygon QgsGeometry::asMultiPolygon()
double QgsGeometry::distance( QgsGeometry& geom )
{
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
if ( geom.mGeos == NULL )
if ( !geom.mGeos )
{
geom.exportWkbToGeos();
}
if ( mGeos == NULL || geom.mGeos == NULL )
if ( !mGeos || !geom.mGeos )
return -1.0;
double dist = -1.0;
@ -5880,7 +5880,7 @@ double QgsGeometry::distance( QgsGeometry& geom )
QgsGeometry* QgsGeometry::buffer( double distance, int segments )
{
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
@ -5898,7 +5898,7 @@ QgsGeometry* QgsGeometry::buffer( double distance, int segments )
QgsGeometry* QgsGeometry::simplify( double tolerance )
{
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
@ -5915,7 +5915,7 @@ QgsGeometry* QgsGeometry::simplify( double tolerance )
QgsGeometry* QgsGeometry::centroid()
{
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
@ -5932,7 +5932,7 @@ QgsGeometry* QgsGeometry::centroid()
QgsGeometry* QgsGeometry::convexHull()
{
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
@ -5950,15 +5950,15 @@ QgsGeometry* QgsGeometry::convexHull()
QgsGeometry* QgsGeometry::intersection( QgsGeometry* geometry )
{
if ( geometry == NULL )
if ( !geometry )
{
return NULL;
}
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
if ( geometry->mGeos == NULL )
if ( !geometry->mGeos )
{
geometry->exportWkbToGeos();
}
@ -5976,15 +5976,15 @@ QgsGeometry* QgsGeometry::intersection( QgsGeometry* geometry )
QgsGeometry* QgsGeometry::combine( QgsGeometry* geometry )
{
if ( geometry == NULL )
if ( !geometry )
{
return NULL;
}
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
if ( geometry->mGeos == NULL )
if ( !geometry->mGeos )
{
geometry->exportWkbToGeos();
}
@ -6015,15 +6015,15 @@ QgsGeometry* QgsGeometry::combine( QgsGeometry* geometry )
QgsGeometry* QgsGeometry::difference( QgsGeometry* geometry )
{
if ( geometry == NULL )
if ( !geometry )
{
return NULL;
}
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
if ( geometry->mGeos == NULL )
if ( !geometry->mGeos )
{
geometry->exportWkbToGeos();
}
@ -6041,15 +6041,15 @@ QgsGeometry* QgsGeometry::difference( QgsGeometry* geometry )
QgsGeometry* QgsGeometry::symDifference( QgsGeometry* geometry )
{
if ( geometry == NULL )
if ( !geometry )
{
return NULL;
}
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
}
if ( geometry->mGeos == NULL )
if ( !geometry->mGeos )
{
geometry->exportWkbToGeos();
}
@ -6067,10 +6067,10 @@ QgsGeometry* QgsGeometry::symDifference( QgsGeometry* geometry )
QList<QgsGeometry*> QgsGeometry::asGeometryCollection()
{
if ( mGeos == NULL )
if ( !mGeos )
{
exportWkbToGeos();
if ( mGeos == NULL )
if ( !mGeos )
return QList<QgsGeometry*>();
}
@ -6258,3 +6258,228 @@ int QgsGeometry::avoidIntersections()
return returnValue;
}
//
// distance of point q from line through p in direction v
// return >0 => q lies left of the line
// <0 => q lies right of the line
//
static double distLine2Point( QgsPoint p, QgsVector v, QgsPoint q )
{
if ( v.length() == 0 )
{
throw QgsException( QObject::tr( "invalid line" ) );
}
return ( v.x()*( q.y() - p.y() ) - v.y()*( q.x() - p.x() ) ) / v.length();
}
static bool intersectLines( QgsPoint p, QgsVector v, QgsPoint q, QgsVector w, QgsPoint &s )
{
double d = v.y() * w.x() - v.x() * w.y();
if ( d == 0 )
return false;
double dx = q.x() - p.x();
double dy = q.y() - p.y();
double k = ( dy * w.x() - dx * w.y() ) / d;
s = p + v * k;
return true;
}
bool pointInRing( const QgsPolyline &ring, const QgsPoint &p )
{
bool inside = false;
int j = ring.size() - 1;
for ( int i = 0; i < ring.size(); i++ )
{
if ( ring[i].x() == p.x() && ring[i].y() == p.y() )
return true;
if (( ring[i].y() < p.y() && ring[j].y() >= p.y() ) ||
( ring[j].y() < p.y() && ring[i].y() >= p.y() ) )
{
if ( ring[i].x() + ( p.y() - ring[i].y() ) / ( ring[j].y() - ring[i].y() )*( ring[j].x() - ring[i].x() ) <= p.x() )
inside = !inside;
}
j = i;
}
return inside;
}
static bool ringInRing( const QgsPolyline &inside, const QgsPolyline &outside )
{
for ( int i = 0; i < inside.size(); i++ )
{
if ( !pointInRing( outside, inside[i] ) )
return false;
}
return true;
}
bool ringIntersectsRing( const QgsPolyline &ring0, const QgsPolyline &ring1 )
{
bool inside = false;
bool outside = false;
for ( int i = 0; i < ring0.size(); i++ )
{
if ( pointInRing( ring1, ring0[i] ) )
{
inside = true;
}
else
{
outside = true;
}
if ( outside && inside )
return true;
}
return false;
}
void QgsGeometry::validatePolyline( QList<Error> &errors, int i, const QgsPolyline &line )
{
if ( line.size() < 2 )
{
QString msg = QObject::tr( "line %1 with less than two points" ).arg( i );
QgsDebugMsg( msg );
errors << Error( msg );
return;
}
bool closed = line[0] == line[ line.size()-1 ];
if ( closed && line.size() < 3 )
{
QString msg = QObject::tr( "ring %1 with less than three points" ).arg( i );
QgsDebugMsg( msg );
errors << Error( msg );
return;
}
for ( int j = 0; j < line.size() - 3; j++ )
{
QgsVector v = line[j+1] - line[j];
int n = j == 0 && closed ? line.size() - 2 : line.size() - 1;
for ( int k = j + 2; k < n; k++ )
{
QgsVector w = line[k+1] - line[k];
QgsPoint s;
if ( intersectLines( line[j], v, line[k], w, s ) )
{
double d = -distLine2Point( line[j], v.perpVector(), s );
if ( d >= 0 && d <= v.length() )
{
d = -distLine2Point( line[k], w.perpVector(), s );
if ( d >= 0 && d <= w.length() )
{
QString msg = QObject::tr( "segments %1 and %2 of line %3 intersect at %4" ).arg( j ).arg( k ).arg( i ).arg( s.toString() );
QgsDebugMsg( msg );
errors << Error( msg, s );
if ( errors.size() > 100 )
{
QString msg = QObject::tr( "stopping validation after more than 100 errors" );
QgsDebugMsg( msg );
errors << Error( msg );
return;
}
}
}
}
}
}
}
void QgsGeometry::validatePolygon( QList<Error> &errors, int idx, const QgsPolygon &polygon )
{
for ( int i = 1; i < polygon.size(); i++ )
{
if ( !ringInRing( polygon[i], polygon[0] ) )
{
QString msg = QObject::tr( "ring %1 of polygon %2 not in exterior ring" ).arg( i ).arg( idx );
QgsDebugMsg( msg );
errors << Error( msg );
}
}
for ( int i = 1; i < polygon.size(); i++ )
{
for ( int j = i + 1; j < polygon.size(); j++ )
{
if ( ringIntersectsRing( polygon[i], polygon[j] ) )
{
QString msg = QObject::tr( "interior rings %1 and %2 of polygon %3 intersect" ).arg( i ).arg( j ).arg( idx );
QgsDebugMsg( msg );
errors << Error( msg );
}
}
}
for ( int i = 0; i < polygon.size(); i++ )
{
validatePolyline( errors, i, polygon[i] );
}
}
void QgsGeometry::validateGeometry( QList<Error> &errors )
{
errors.clear();
switch ( wkbType() )
{
case QGis::WKBPoint:
case QGis::WKBPoint25D:
case QGis::WKBMultiPoint:
case QGis::WKBMultiPoint25D:
break;
case QGis::WKBLineString:
case QGis::WKBLineString25D:
validatePolyline( errors, 0, asPolyline() );
break;
case QGis::WKBMultiLineString:
case QGis::WKBMultiLineString25D:
{
QgsMultiPolyline mp = asMultiPolyline();
for ( int i = 0; i < mp.size(); i++ )
validatePolyline( errors, i, mp[i] );
}
break;
case QGis::WKBPolygon:
case QGis::WKBPolygon25D:
{
validatePolygon( errors, 0, asPolygon() );
}
break;
case QGis::WKBMultiPolygon:
case QGis::WKBMultiPolygon25D:
{
QgsMultiPolygon mp = asMultiPolygon();
for ( int i = 0; i < mp.size(); i++ )
validatePolygon( errors, i, mp[i] );
}
break;
case QGis::WKBUnknown:
QgsDebugMsg( QObject::tr( "Unknown geometry type" ) );
errors << Error( QObject::tr( "Unknown geometry type" ) );
break;
}
}

View File

@ -362,6 +362,23 @@ class CORE_EXPORT QgsGeometry
*/
int avoidIntersections();
class Error
{
QString message;
QgsPoint location;
bool hasLocation;
public:
Error( QString m ) : message( m ), hasLocation( false ) {}
Error( QString m, QgsPoint p ) : message( m ), location( p ), hasLocation( true ) {}
QString what() { return message; };
QgsPoint where() { return location; }
bool hasWhere() { return hasLocation; }
};
void validateGeometry( QList<Error> &errors );
private:
// Private variables
@ -494,6 +511,9 @@ class CORE_EXPORT QgsGeometry
/** return polygon from wkb */
QgsPolygon asPolygon( unsigned char*& ptr, bool hasZValue );
void validatePolyline( QList<Error> &errors, int i, const QgsPolyline &polygon );
void validatePolygon( QList<Error> &errors, int i, const QgsPolygon &polygon );
static int refcount;
}; // class QgsGeometry

View File

@ -22,6 +22,95 @@
#include <QTextStream>
#include <QObject> // for tr()
#include "qgsexception.h"
//
// QgsVector
//
QgsVector::QgsVector() : m_x( 0.0 ), m_y( 0.0 )
{
}
QgsVector::QgsVector( double x, double y ) : m_x( x ), m_y( y )
{
}
QgsVector QgsVector::operator-( void ) const
{
return QgsVector( -m_x, -m_y );
}
QgsVector QgsVector::operator*( double scalar ) const
{
return QgsVector( m_x*scalar, m_y*scalar );
}
QgsVector QgsVector::operator/( double scalar ) const
{
return *this * ( 1.0 / scalar );
}
double QgsVector::operator*( QgsVector v ) const
{
return m_x*v.m_x + m_y*v.m_y;
}
double QgsVector::length() const
{
return sqrt( m_x*m_x + m_y*m_y );
}
double QgsVector::x() const
{
return m_x;
}
double QgsVector::y() const
{
return m_y;
}
// perpendicular vector (rotated 90° counter-clockwise)
QgsVector QgsVector::perpVector() const
{
return QgsVector( -m_y, m_x );
}
double QgsVector::angle( void ) const
{
double ang = atan2( m_y, m_x );
return ang < 0.0 ? ang + 2.0*M_PI : ang;
}
double QgsVector::angle( QgsVector v ) const
{
return v.angle() - angle();
}
QgsVector QgsVector::rotateBy( double rot ) const
{
double ang = atan2( m_y, m_x ) + rot;
double len = length();
return QgsVector( len*cos( ang ), len*sin( ang ) );
}
QgsVector QgsVector::normal() const
{
double len = length();
if ( len == 0.0 )
{
throw QgsException( "normal vector of null vector undefined" );
}
return *this / len;
}
//
// QgsPoint
//
QgsPoint::QgsPoint( const QgsPoint& p )
{

View File

@ -20,9 +20,40 @@
#define QGSPOINT_H
#include <iostream>
#include <QString>
/** \ingroup core
* A class to represent a vector.
* Currently no Z axis / 2.5D support is implemented.
*/
class CORE_EXPORT QgsVector
{
double m_x, m_y;
public:
QgsVector();
QgsVector( double x, double y );
QgsVector operator-( void ) const;
QgsVector operator*( double scalar ) const;
QgsVector operator/( double scalar ) const;
double operator*( QgsVector v ) const;
double length() const;
double x() const;
double y() const;
// perpendicular vector (rotated 90° counter-clockwise)
QgsVector perpVector() const;
double angle( void ) const;
double angle( QgsVector v ) const;
QgsVector rotateBy( double rot ) const;
QgsVector normal() const;
};
/** \ingroup core
* A class to represent a point geometry.
* Currently no Z axis / 2.5D support is implemented.
@ -131,6 +162,12 @@ class CORE_EXPORT QgsPoint
//! 3 if point is on open ray b.
int onSegment( const QgsPoint& a, const QgsPoint& b ) const;
QgsVector operator-( QgsPoint p ) const { return QgsVector( m_x - p.m_x, m_y - p.m_y ); }
QgsPoint &operator+=( const QgsVector &v ) { *this = *this + v; return *this; }
QgsPoint &operator-=( const QgsVector &v ) { *this = *this - v; return *this; }
QgsPoint operator+( const QgsVector &v ) const { return QgsPoint( m_x + v.x(), m_y + v.y() ); }
QgsPoint operator-( const QgsVector &v ) const { return QgsPoint( m_x - v.x(), m_y - v.y() ); }
private:
//! x coordinate
@ -158,6 +195,4 @@ inline std::ostream& operator << ( std::ostream& os, const QgsPoint &p )
return os;
}
#endif //QGSPOINT_H