mirror of
https://github.com/qgis/QGIS.git
synced 2025-12-08 00:06:51 -05:00
512 lines
14 KiB
C++
512 lines
14 KiB
C++
/***************************************************************************
|
|
qgspoint.cpp - description
|
|
-------------------
|
|
begin : Sat Jun 22 2002
|
|
copyright : (C) 2002 by Gary E.Sherman
|
|
email : sherman at mrcc.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
|
|
#include "qgspoint.h"
|
|
#include "qgis.h"
|
|
#include <cmath>
|
|
#include <QTextStream>
|
|
#include <QObject> // for tr()
|
|
|
|
#include "qgsexception.h"
|
|
|
|
//
|
|
// QgsVector
|
|
//
|
|
|
|
QgsVector::QgsVector()
|
|
: mX( 0.0 )
|
|
, mY( 0.0 )
|
|
{
|
|
}
|
|
|
|
QgsVector::QgsVector( double x, double y )
|
|
: mX( x )
|
|
, mY( y )
|
|
{
|
|
}
|
|
|
|
QgsVector QgsVector::operator-() const
|
|
{
|
|
return QgsVector( -mX, -mY );
|
|
}
|
|
|
|
QgsVector QgsVector::operator*( double scalar ) const
|
|
{
|
|
return QgsVector( mX * scalar, mY * scalar );
|
|
}
|
|
|
|
QgsVector QgsVector::operator/( double scalar ) const
|
|
{
|
|
return *this * ( 1.0 / scalar );
|
|
}
|
|
|
|
double QgsVector::operator*( QgsVector v ) const
|
|
{
|
|
return mX * v.mX + mY * v.mY;
|
|
}
|
|
|
|
QgsVector QgsVector::operator+( QgsVector other ) const
|
|
{
|
|
return QgsVector( mX + other.mX, mY + other.mY );
|
|
}
|
|
|
|
QgsVector& QgsVector::operator+=( QgsVector other )
|
|
{
|
|
mX += other.mX;
|
|
mY += other.mY;
|
|
return *this;
|
|
}
|
|
|
|
QgsVector QgsVector::operator-( QgsVector other ) const
|
|
{
|
|
return QgsVector( mX - other.mX, mY - other.mY );
|
|
}
|
|
|
|
QgsVector& QgsVector::operator-=( QgsVector other )
|
|
{
|
|
mX -= other.mX;
|
|
mY -= other.mY;
|
|
return *this;
|
|
}
|
|
|
|
double QgsVector::length() const
|
|
{
|
|
return sqrt( mX * mX + mY * mY );
|
|
}
|
|
|
|
double QgsVector::x() const
|
|
{
|
|
return mX;
|
|
}
|
|
|
|
double QgsVector::y() const
|
|
{
|
|
return mY;
|
|
}
|
|
|
|
QgsVector QgsVector::perpVector() const
|
|
{
|
|
return QgsVector( -mY, mX );
|
|
}
|
|
|
|
double QgsVector::angle() const
|
|
{
|
|
double ang = atan2( mY, mX );
|
|
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( mY, mX ) + rot;
|
|
double len = length();
|
|
return QgsVector( len * cos( ang ), len * sin( ang ) );
|
|
}
|
|
|
|
QgsVector QgsVector::normalized() const
|
|
{
|
|
double len = length();
|
|
|
|
if ( len == 0.0 )
|
|
{
|
|
throw QgsException( QStringLiteral( "normalized vector of null vector undefined" ) );
|
|
}
|
|
|
|
return *this / len;
|
|
}
|
|
|
|
|
|
//
|
|
// QgsPoint
|
|
//
|
|
|
|
QgsPoint::QgsPoint( const QgsPoint& p )
|
|
{
|
|
m_x = p.x();
|
|
m_y = p.y();
|
|
}
|
|
|
|
QPointF QgsPoint::toQPointF() const
|
|
{
|
|
return QPointF( m_x, m_y );
|
|
}
|
|
|
|
QString QgsPoint::toString() const
|
|
{
|
|
QString rep;
|
|
QTextStream ot( &rep );
|
|
ot.setRealNumberPrecision( 12 );
|
|
ot << m_x << ", " << m_y;
|
|
return rep;
|
|
}
|
|
|
|
QString QgsPoint::toString( int thePrecision ) const
|
|
{
|
|
QString x = qIsFinite( m_x ) ? QString::number( m_x, 'f', thePrecision ) : QObject::tr( "infinite" );
|
|
QString y = qIsFinite( m_y ) ? QString::number( m_y, 'f', thePrecision ) : QObject::tr( "infinite" );
|
|
return QStringLiteral( "%1,%2" ).arg( x, y );
|
|
}
|
|
|
|
QString QgsPoint::toDegreesMinutesSeconds( int thePrecision, const bool useSuffix, const bool padded ) const
|
|
{
|
|
//first, limit longitude to -360 to 360 degree range
|
|
double myWrappedX = fmod( m_x, 360.0 );
|
|
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
|
|
if ( myWrappedX > 180.0 )
|
|
{
|
|
myWrappedX = myWrappedX - 360.0;
|
|
}
|
|
else if ( myWrappedX < -180.0 )
|
|
{
|
|
myWrappedX = myWrappedX + 360.0;
|
|
}
|
|
|
|
//first, limit latitude to -180 to 180 degree range
|
|
double myWrappedY = fmod( m_y, 180.0 );
|
|
//next, wrap around latitudes > 90 or < -90 degrees, so that eg "110S" -> "70N"
|
|
if ( myWrappedY > 90.0 )
|
|
{
|
|
myWrappedY = myWrappedY - 180.0;
|
|
}
|
|
else if ( myWrappedY < -90.0 )
|
|
{
|
|
myWrappedY = myWrappedY + 180.0;
|
|
}
|
|
|
|
int myDegreesX = int( qAbs( myWrappedX ) );
|
|
double myFloatMinutesX = double(( qAbs( myWrappedX ) - myDegreesX ) * 60 );
|
|
int myIntMinutesX = int( myFloatMinutesX );
|
|
double mySecondsX = double( myFloatMinutesX - myIntMinutesX ) * 60;
|
|
|
|
int myDegreesY = int( qAbs( myWrappedY ) );
|
|
double myFloatMinutesY = double(( qAbs( myWrappedY ) - myDegreesY ) * 60 );
|
|
int myIntMinutesY = int( myFloatMinutesY );
|
|
double mySecondsY = double( myFloatMinutesY - myIntMinutesY ) * 60;
|
|
|
|
//make sure rounding to specified precision doesn't create seconds >= 60
|
|
if ( qRound( mySecondsX * pow( 10.0, thePrecision ) ) >= 60 * pow( 10.0, thePrecision ) )
|
|
{
|
|
mySecondsX = qMax( mySecondsX - 60, 0.0 );
|
|
myIntMinutesX++;
|
|
if ( myIntMinutesX >= 60 )
|
|
{
|
|
myIntMinutesX -= 60;
|
|
myDegreesX++;
|
|
}
|
|
}
|
|
if ( qRound( mySecondsY * pow( 10.0, thePrecision ) ) >= 60 * pow( 10.0, thePrecision ) )
|
|
{
|
|
mySecondsY = qMax( mySecondsY - 60, 0.0 );
|
|
myIntMinutesY++;
|
|
if ( myIntMinutesY >= 60 )
|
|
{
|
|
myIntMinutesY -= 60;
|
|
myDegreesY++;
|
|
}
|
|
}
|
|
|
|
QString myXHemisphere;
|
|
QString myYHemisphere;
|
|
QString myXSign;
|
|
QString myYSign;
|
|
if ( useSuffix )
|
|
{
|
|
myXHemisphere = myWrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
|
|
myYHemisphere = myWrappedY < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
|
|
}
|
|
else
|
|
{
|
|
if ( myWrappedX < 0 )
|
|
{
|
|
myXSign = QObject::tr( "-" );
|
|
}
|
|
if ( myWrappedY < 0 )
|
|
{
|
|
myYSign = QObject::tr( "-" );
|
|
}
|
|
}
|
|
//check if coordinate is all zeros for the specified precision, and if so,
|
|
//remove the sign and hemisphere strings
|
|
if ( myDegreesX == 0 && myIntMinutesX == 0 && qRound( mySecondsX * pow( 10.0, thePrecision ) ) == 0 )
|
|
{
|
|
myXSign = QString();
|
|
myXHemisphere = QString();
|
|
}
|
|
if ( myDegreesY == 0 && myIntMinutesY == 0 && qRound( mySecondsY * pow( 10.0, thePrecision ) ) == 0 )
|
|
{
|
|
myYSign = QString();
|
|
myYHemisphere = QString();
|
|
}
|
|
//also remove directional prefix from 180 degree longitudes
|
|
if ( myDegreesX == 180 && myIntMinutesX == 0 && qRound( mySecondsX * pow( 10.0, thePrecision ) ) == 0 )
|
|
{
|
|
myXHemisphere = QString();
|
|
}
|
|
//pad minutes with leading digits if required
|
|
QString myMinutesX = padded ? QStringLiteral( "%1" ).arg( myIntMinutesX, 2, 10, QChar( '0' ) ) : QString::number( myIntMinutesX );
|
|
QString myMinutesY = padded ? QStringLiteral( "%1" ).arg( myIntMinutesY, 2, 10, QChar( '0' ) ) : QString::number( myIntMinutesY );
|
|
//pad seconds with leading digits if required
|
|
int digits = 2 + ( thePrecision == 0 ? 0 : 1 + thePrecision ); //1 for decimal place if required
|
|
QString myStrSecondsX = padded ? QStringLiteral( "%1" ).arg( mySecondsX, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( mySecondsX, 'f', thePrecision );
|
|
QString myStrSecondsY = padded ? QStringLiteral( "%1" ).arg( mySecondsY, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( mySecondsY, 'f', thePrecision );
|
|
|
|
QString rep = myXSign + QString::number( myDegreesX ) + QChar( 176 ) +
|
|
myMinutesX + QChar( 0x2032 ) +
|
|
myStrSecondsX + QChar( 0x2033 ) +
|
|
myXHemisphere + ',' +
|
|
myYSign + QString::number( myDegreesY ) + QChar( 176 ) +
|
|
myMinutesY + QChar( 0x2032 ) +
|
|
myStrSecondsY + QChar( 0x2033 ) +
|
|
myYHemisphere;
|
|
return rep;
|
|
}
|
|
|
|
QString QgsPoint::toDegreesMinutes( int thePrecision, const bool useSuffix, const bool padded ) const
|
|
{
|
|
//first, limit longitude to -360 to 360 degree range
|
|
double myWrappedX = fmod( m_x, 360.0 );
|
|
//next, wrap around longitudes > 180 or < -180 degrees, so that eg "190E" -> "170W"
|
|
if ( myWrappedX > 180.0 )
|
|
{
|
|
myWrappedX = myWrappedX - 360.0;
|
|
}
|
|
else if ( myWrappedX < -180.0 )
|
|
{
|
|
myWrappedX = myWrappedX + 360.0;
|
|
}
|
|
|
|
int myDegreesX = int( qAbs( myWrappedX ) );
|
|
double myFloatMinutesX = double(( qAbs( myWrappedX ) - myDegreesX ) * 60 );
|
|
|
|
int myDegreesY = int( qAbs( m_y ) );
|
|
double myFloatMinutesY = double(( qAbs( m_y ) - myDegreesY ) * 60 );
|
|
|
|
//make sure rounding to specified precision doesn't create minutes >= 60
|
|
if ( qRound( myFloatMinutesX * pow( 10.0, thePrecision ) ) >= 60 * pow( 10.0, thePrecision ) )
|
|
{
|
|
myFloatMinutesX = qMax( myFloatMinutesX - 60, 0.0 );
|
|
myDegreesX++;
|
|
}
|
|
if ( qRound( myFloatMinutesY * pow( 10.0, thePrecision ) ) >= 60 * pow( 10.0, thePrecision ) )
|
|
{
|
|
myFloatMinutesY = qMax( myFloatMinutesY - 60, 0.0 );
|
|
myDegreesY++;
|
|
}
|
|
|
|
QString myXHemisphere;
|
|
QString myYHemisphere;
|
|
QString myXSign;
|
|
QString myYSign;
|
|
if ( useSuffix )
|
|
{
|
|
myXHemisphere = myWrappedX < 0 ? QObject::tr( "W" ) : QObject::tr( "E" );
|
|
myYHemisphere = m_y < 0 ? QObject::tr( "S" ) : QObject::tr( "N" );
|
|
}
|
|
else
|
|
{
|
|
if ( myWrappedX < 0 )
|
|
{
|
|
myXSign = QObject::tr( "-" );
|
|
}
|
|
if ( m_y < 0 )
|
|
{
|
|
myYSign = QObject::tr( "-" );
|
|
}
|
|
}
|
|
//check if coordinate is all zeros for the specified precision, and if so,
|
|
//remove the sign and hemisphere strings
|
|
if ( myDegreesX == 0 && qRound( myFloatMinutesX * pow( 10.0, thePrecision ) ) == 0 )
|
|
{
|
|
myXSign = QString();
|
|
myXHemisphere = QString();
|
|
}
|
|
if ( myDegreesY == 0 && qRound( myFloatMinutesY * pow( 10.0, thePrecision ) ) == 0 )
|
|
{
|
|
myYSign = QString();
|
|
myYHemisphere = QString();
|
|
}
|
|
//also remove directional prefix from 180 degree longitudes
|
|
if ( myDegreesX == 180 && qRound( myFloatMinutesX * pow( 10.0, thePrecision ) ) == 0 )
|
|
{
|
|
myXHemisphere = QString();
|
|
}
|
|
|
|
//pad minutes with leading digits if required
|
|
int digits = 2 + ( thePrecision == 0 ? 0 : 1 + thePrecision ); //1 for decimal place if required
|
|
QString myStrMinutesX = padded ? QStringLiteral( "%1" ).arg( myFloatMinutesX, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( myFloatMinutesX, 'f', thePrecision );
|
|
QString myStrMinutesY = padded ? QStringLiteral( "%1" ).arg( myFloatMinutesY, digits, 'f', thePrecision, QChar( '0' ) ) : QString::number( myFloatMinutesY, 'f', thePrecision );
|
|
|
|
QString rep = myXSign + QString::number( myDegreesX ) + QChar( 176 ) +
|
|
myStrMinutesX + QChar( 0x2032 ) +
|
|
myXHemisphere + ',' +
|
|
myYSign + QString::number( myDegreesY ) + QChar( 176 ) +
|
|
myStrMinutesY + QChar( 0x2032 ) +
|
|
myYHemisphere;
|
|
return rep;
|
|
}
|
|
|
|
QString QgsPoint::wellKnownText() const
|
|
{
|
|
return QStringLiteral( "POINT(%1 %2)" ).arg( qgsDoubleToString( m_x ), qgsDoubleToString( m_y ) );
|
|
}
|
|
|
|
double QgsPoint::sqrDist( double x, double y ) const
|
|
{
|
|
return ( m_x - x ) * ( m_x - x ) + ( m_y - y ) * ( m_y - y );
|
|
}
|
|
|
|
double QgsPoint::sqrDist( const QgsPoint& other ) const
|
|
{
|
|
return sqrDist( other.x(), other.y() );
|
|
}
|
|
|
|
double QgsPoint::distance( double x, double y ) const
|
|
{
|
|
return sqrt( sqrDist( x, y ) );
|
|
}
|
|
|
|
double QgsPoint::distance( const QgsPoint& other ) const
|
|
{
|
|
return sqrt( sqrDist( other ) );
|
|
}
|
|
|
|
double QgsPoint::azimuth( const QgsPoint& other ) const
|
|
{
|
|
double dx = other.x() - m_x;
|
|
double dy = other.y() - m_y;
|
|
return ( atan2( dx, dy ) * 180.0 / M_PI );
|
|
}
|
|
|
|
QgsPoint QgsPoint::project( double distance, double bearing ) const
|
|
{
|
|
double rads = bearing * M_PI / 180.0;
|
|
double dx = distance * sin( rads );
|
|
double dy = distance * cos( rads );
|
|
return QgsPoint( m_x + dx, m_y + dy );
|
|
}
|
|
|
|
bool QgsPoint::compare( const QgsPoint &other, double epsilon ) const
|
|
{
|
|
return ( qgsDoubleNear( m_x, other.x(), epsilon ) && qgsDoubleNear( m_y, other.y(), epsilon ) );
|
|
}
|
|
|
|
// operators
|
|
bool QgsPoint::operator==( const QgsPoint & other )
|
|
{
|
|
if ( qgsDoubleNear( m_x, other.x() ) && qgsDoubleNear( m_y, other.y() ) )
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool QgsPoint::operator!=( const QgsPoint & other ) const
|
|
{
|
|
if ( qgsDoubleNear( m_x, other.x() ) && qgsDoubleNear( m_y, other.y() ) )
|
|
return false;
|
|
else
|
|
return true;
|
|
}
|
|
|
|
QgsPoint & QgsPoint::operator=( const QgsPoint & other )
|
|
{
|
|
if ( &other != this )
|
|
{
|
|
m_x = other.x();
|
|
m_y = other.y();
|
|
}
|
|
|
|
return *this;
|
|
}
|
|
|
|
void QgsPoint::multiply( double scalar )
|
|
{
|
|
m_x *= scalar;
|
|
m_y *= scalar;
|
|
}
|
|
|
|
int QgsPoint::onSegment( const QgsPoint& a, const QgsPoint& b ) const
|
|
{
|
|
//algorithm from 'graphics GEMS', A. Paeth: 'A Fast 2D Point-on-line test'
|
|
if (
|
|
qAbs(( b.y() - a.y() ) *( m_x - a.x() ) - ( m_y - a.y() ) *( b.x() - a.x() ) )
|
|
>= qMax( qAbs( b.x() - a.x() ), qAbs( b.y() - a.y() ) )
|
|
)
|
|
{
|
|
return 0;
|
|
}
|
|
if (( b.x() < a.x() && a.x() < m_x ) || ( b.y() < a.y() && a.y() < m_y ) )
|
|
{
|
|
return 1;
|
|
}
|
|
if (( m_x < a.x() && a.x() < b.x() ) || ( m_y < a.y() && a.y() < b.y() ) )
|
|
{
|
|
return 1;
|
|
}
|
|
if (( a.x() < b.x() && b.x() < m_x ) || ( a.y() < b.y() && b.y() < m_y ) )
|
|
{
|
|
return 3;
|
|
}
|
|
if (( m_x < b.x() && b.x() < a.x() ) || ( m_y < b.y() && b.y() < a.y() ) )
|
|
{
|
|
return 3;
|
|
}
|
|
|
|
return 2;
|
|
}
|
|
|
|
double QgsPoint::sqrDistToSegment( double x1, double y1, double x2, double y2, QgsPoint& minDistPoint, double epsilon ) const
|
|
{
|
|
double nx, ny; //normal vector
|
|
|
|
nx = y2 - y1;
|
|
ny = -( x2 - x1 );
|
|
|
|
double t;
|
|
t = ( m_x * ny - m_y * nx - x1 * ny + y1 * nx ) / (( x2 - x1 ) * ny - ( y2 - y1 ) * nx );
|
|
|
|
if ( t < 0.0 )
|
|
{
|
|
minDistPoint.setX( x1 );
|
|
minDistPoint.setY( y1 );
|
|
}
|
|
else if ( t > 1.0 )
|
|
{
|
|
minDistPoint.setX( x2 );
|
|
minDistPoint.setY( y2 );
|
|
}
|
|
else
|
|
{
|
|
minDistPoint.setX( x1 + t *( x2 - x1 ) );
|
|
minDistPoint.setY( y1 + t *( y2 - y1 ) );
|
|
}
|
|
|
|
double dist = sqrDist( minDistPoint );
|
|
//prevent rounding errors if the point is directly on the segment
|
|
if ( qgsDoubleNear( dist, 0.0, epsilon ) )
|
|
{
|
|
minDistPoint.setX( m_x );
|
|
minDistPoint.setY( m_y );
|
|
return 0.0;
|
|
}
|
|
return dist;
|
|
}
|