bool QgsGeometryAnalyzer::clipSegmentByRange( double x1, double y1, double m1, double x2, double y2, double m2, double range1, double range2, QgsPoint& pt1, QgsPoint& pt2, bool& secondPointClipped ) { bool reversed = m1 > m2; double tmp; //reverse m1, m2 if necessary (and consequently also x1,x2 / y1, y2) if ( reversed ) { tmp = m1; m1 = m2; m2 = tmp; tmp = x1; x1 = x2; x2 = tmp; tmp = y1; y1 = y2; y2 = tmp; } //reverse range1, range2 if necessary if ( range1 > range2 ) { tmp = range1; range1 = range2; range2 = tmp; } //segment completely outside of range if ( m2 < range1 || m1 > range2 ) { return false; } //segment completely inside of range if ( m2 <= range2 && m1 >= range1 ) { if ( reversed ) { pt1.setX( x2 ); pt1.setY( y2 ); pt2.setX( x1 ); pt2.setY( y1 ); } else { pt1.setX( x1 ); pt1.setY( y1 ); pt2.setX( x2 ); pt2.setY( y2 ); } secondPointClipped = false; return true; } //m1 inside and m2 not if ( m1 >= range1 && m1 <= range2 ) { pt1.setX( x1 ); pt1.setY( y1 ); double dist = ( range2 - m1 ) / ( m2 - m1 ); pt2.setX( x1 + ( x2 - x1 ) * dist ); pt2.setY( y1 + ( y2 - y1 ) * dist ); secondPointClipped = !reversed; } //m2 inside and m1 not if ( m2 >= range1 && m2 <= range2 ) { pt2.setX( x2 ); pt2.setY( y2 ); double dist = ( m2 - range1 ) / ( m2 - m1 ); pt1.setX( x2 - ( x2 - x1 ) * dist ); pt1.setY( y2 - ( y2 - y1 ) * dist ); secondPointClipped = reversed; } //range1 and range 2 both inside the segment if ( range1 >= m1 && range2 <= m2 ) { double dist1 = ( range1 - m1 ) / ( m2 - m1 ); double dist2 = ( range2 - m1 ) / ( m2 - m1 ); pt1.setX( x1 + ( x2 - x1 ) * dist1 ); pt1.setY( y1 + ( y2 - y1 ) * dist1 ); pt2.setX( x1 + ( x2 - x1 ) * dist2 ); pt2.setY( y1 + ( y2 - y1 ) * dist2 ); secondPointClipped = true; } if ( reversed ) //switch p1 and p2 { QgsPoint tmpPt = pt1; pt1 = pt2; pt2 = tmpPt; } return true; }
void QgsGeometryAnalyzer::locateAlongSegment( double x1, double y1, double m1, double x2, double y2, double m2, double measure, bool& pt1Ok, QgsPoint& pt1, bool& pt2Ok, QgsPoint& pt2 ) { bool reversed = false; pt1Ok = false; pt2Ok = false; double tolerance = 0.000001; //work with a small tolerance to catch e.g. locations at endpoints if ( m1 > m2 ) { double tmp = m1; m1 = m2; m2 = tmp; reversed = true; } //segment does not match if (( m1 - measure ) > tolerance || ( measure - m2 ) > tolerance ) { pt1Ok = false; pt2Ok = false; return; } //match with vertex1 if ( qgsDoubleNear( m1, measure, tolerance ) ) { if ( reversed ) { pt2Ok = true; pt2.setX( x2 ); pt2.setY( y2 ); } else { pt1Ok = true; pt1.setX( x1 ); pt1.setY( y1 ); } } //match with vertex2 if ( qgsDoubleNear( m2, measure, tolerance ) ) { if ( reversed ) { pt1Ok = true; pt1.setX( x1 ); pt1.setY( y1 ); } else { pt2Ok = true; pt2.setX( x2 ); pt2.setY( y2 ); } } if ( pt1Ok || pt2Ok ) { return; } //match between the vertices if ( qgsDoubleNear( m1, m2 ) ) { pt1.setX( x1 ); pt1.setY( y1 ); pt1Ok = true; return; } double dist = ( measure - m1 ) / ( m2 - m1 ); if ( reversed ) { dist = 1 - dist; } pt1.setX( x1 + dist * ( x2 - x1 ) ); pt1.setY( y1 + dist * ( y2 - y1 ) ); pt1Ok = true; }
double QgsLineString::closestSegment( const QgsPoint &pt, QgsPoint &segmentPt, QgsVertexId &vertexAfter, int *leftOf, double epsilon ) const { double sqrDist = std::numeric_limits<double>::max(); double leftOfDist = std::numeric_limits<double>::max(); int prevLeftOf = 0; double prevLeftOfX = 0.0; double prevLeftOfY = 0.0; double testDist = 0; double segmentPtX, segmentPtY; if ( leftOf ) *leftOf = 0; int size = mX.size(); if ( size == 0 || size == 1 ) { vertexAfter = QgsVertexId( 0, 0, 0 ); return -1; } for ( int i = 1; i < size; ++i ) { double prevX = mX.at( i - 1 ); double prevY = mY.at( i - 1 ); double currentX = mX.at( i ); double currentY = mY.at( i ); testDist = QgsGeometryUtils::sqrDistToLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY, segmentPtX, segmentPtY, epsilon ); if ( testDist < sqrDist ) { sqrDist = testDist; segmentPt.setX( segmentPtX ); segmentPt.setY( segmentPtY ); vertexAfter.part = 0; vertexAfter.ring = 0; vertexAfter.vertex = i; } if ( leftOf && qgsDoubleNear( testDist, sqrDist ) ) { int left = QgsGeometryUtils::leftOfLine( pt.x(), pt.y(), prevX, prevY, currentX, currentY ); // if left equals 0, the test could not be performed (e.g. point in line with segment or on segment) // so don't set leftOf in this case, and hope that there's another segment that's the same distance // where we can perform the check if ( left != 0 ) { if ( qgsDoubleNear( testDist, leftOfDist ) && left != prevLeftOf && prevLeftOf != 0 ) { // we have two possible segments each with equal distance to point, but they disagree // on whether or not the point is to the left of them. // so we test the segments themselves and flip the result. // see https://stackoverflow.com/questions/10583212/elegant-left-of-test-for-polyline *leftOf = -QgsGeometryUtils::leftOfLine( currentX, currentY, prevLeftOfX, prevLeftOfY, prevX, prevY ); } else { *leftOf = left; } prevLeftOf = *leftOf; leftOfDist = testDist; prevLeftOfX = prevX; prevLeftOfY = prevY; } else if ( testDist < leftOfDist ) { *leftOf = left; leftOfDist = testDist; prevLeftOf = 0; } } } return sqrDist; }
bool QgsTransectSample::closestSegmentPoints( QgsGeometry& g1, QgsGeometry& g2, double& dist, QgsPoint& pt1, QgsPoint& pt2 ) { QGis::WkbType t1 = g1.wkbType(); if ( t1 != QGis::WKBLineString && t1 != QGis::WKBLineString25D ) { return false; } QGis::WkbType t2 = g2.wkbType(); if ( t2 != QGis::WKBLineString && t2 != QGis::WKBLineString25D ) { return false; } QgsPolyline pl1 = g1.asPolyline(); QgsPolyline pl2 = g2.asPolyline(); if ( pl1.size() < 2 || pl2.size() < 2 ) { return false; } QgsPoint p11 = pl1.at( 0 ); QgsPoint p12 = pl1.at( 1 ); QgsPoint p21 = pl2.at( 0 ); QgsPoint p22 = pl2.at( 1 ); double p1x = p11.x(); double p1y = p11.y(); double v1x = p12.x() - p11.x(); double v1y = p12.y() - p11.y(); double p2x = p21.x(); double p2y = p21.y(); double v2x = p22.x() - p21.x(); double v2y = p22.y() - p21.y(); double denominatorU = v2x * v1y - v2y * v1x; double denominatorT = v1x * v2y - v1y * v2x; if ( qgsDoubleNear( denominatorU, 0 ) || qgsDoubleNear( denominatorT, 0 ) ) { //lines are parallel //project all points on the other segment and take the one with the smallest distance QgsPoint minDistPoint1; double d1 = p11.sqrDistToSegment( p21.x(), p21.y(), p22.x(), p22.y(), minDistPoint1 ); QgsPoint minDistPoint2; double d2 = p12.sqrDistToSegment( p21.x(), p21.y(), p22.x(), p22.y(), minDistPoint2 ); QgsPoint minDistPoint3; double d3 = p21.sqrDistToSegment( p11.x(), p11.y(), p12.x(), p12.y(), minDistPoint3 ); QgsPoint minDistPoint4; double d4 = p22.sqrDistToSegment( p11.x(), p11.y(), p12.x(), p12.y(), minDistPoint4 ); if ( d1 <= d2 && d1 <= d3 && d1 <= d4 ) { dist = sqrt( d1 ); pt1 = p11; pt2 = minDistPoint1; return true; } else if ( d2 <= d1 && d2 <= d3 && d2 <= d4 ) { dist = sqrt( d2 ); pt1 = p12; pt2 = minDistPoint2; return true; } else if ( d3 <= d1 && d3 <= d2 && d3 <= d4 ) { dist = sqrt( d3 ); pt1 = p21; pt2 = minDistPoint3; return true; } else { dist = sqrt( d4 ); pt1 = p21; pt2 = minDistPoint4; return true; } } double u = ( p1x * v1y - p1y * v1x - p2x * v1y + p2y * v1x ) / denominatorU; double t = ( p2x * v2y - p2y * v2x - p1x * v2y + p1y * v2x ) / denominatorT; if ( u >= 0 && u <= 1.0 && t >= 0 && t <= 1.0 ) { dist = 0; pt1.setX( p2x + u * v2x ); pt1.setY( p2y + u * v2y ); pt2 = pt1; dist = 0; return true; } if ( t > 1.0 ) { pt1.setX( p12.x() ); pt1.setY( p12.y() ); } else if ( t < 0.0 ) { pt1.setX( p11.x() ); pt1.setY( p11.y() ); } if ( u > 1.0 ) { pt2.setX( p22.x() ); pt2.setY( p22.y() ); } if ( u < 0.0 ) { pt2.setX( p21.x() ); pt2.setY( p21.y() ); } if ( t >= 0.0 && t <= 1.0 ) { //project pt2 onto g1 pt2.sqrDistToSegment( p11.x(), p11.y(), p12.x(), p12.y(), pt1 ); } if ( u >= 0.0 && u <= 1.0 ) { //project pt1 onto g2 pt1.sqrDistToSegment( p21.x(), p21.y(), p22.x(), p22.y(), pt2 ); } dist = sqrt( pt1.sqrDist( pt2 ) ); return true; }
bool QgsMapToolLabel::rotationPoint( QgsPoint& pos, bool ignoreUpsideDown, bool rotatingUnpinned ) { QVector<QgsPoint> cornerPoints = mCurrentLabelPos.cornerPoints; if ( cornerPoints.size() < 4 ) { return false; } if ( mCurrentLabelPos.upsideDown && !ignoreUpsideDown ) { pos = cornerPoints.at( 2 ); } else { pos = cornerPoints.at( 0 ); } //alignment always center/center and rotation 0 for diagrams if ( mCurrentLabelPos.isDiagram ) { pos.setX( pos.x() + mCurrentLabelPos.labelRect.width() / 2.0 ); pos.setY( pos.y() + mCurrentLabelPos.labelRect.height() / 2.0 ); return true; } //adapt pos depending on data defined alignment QString haliString, valiString; currentAlignment( haliString, valiString ); // rotate unpinned labels (i.e. no hali/vali settings) as if hali/vali was Center/Half if ( rotatingUnpinned ) { haliString = "Center"; valiString = "Half"; } // QFont labelFont = labelFontCurrentFeature(); QFontMetricsF labelFontMetrics( mCurrentLabelPos.labelFont ); // NOTE: this assumes the label corner points comprise a rectangle and that the // CRS supports equidistant measurements to accurately determine hypotenuse QgsPoint cp_0 = cornerPoints.at( 0 ); QgsPoint cp_1 = cornerPoints.at( 1 ); QgsPoint cp_3 = cornerPoints.at( 3 ); // QgsDebugMsg( QString( "cp_0: x=%1, y=%2" ).arg( cp_0.x() ).arg( cp_0.y() ) ); // QgsDebugMsg( QString( "cp_1: x=%1, y=%2" ).arg( cp_1.x() ).arg( cp_1.y() ) ); // QgsDebugMsg( QString( "cp_3: x=%1, y=%2" ).arg( cp_3.x() ).arg( cp_3.y() ) ); double labelSizeX = qSqrt( cp_0.sqrDist( cp_1 ) ); double labelSizeY = qSqrt( cp_0.sqrDist( cp_3 ) ); double xdiff = 0; double ydiff = 0; if ( haliString.compare( "Center", Qt::CaseInsensitive ) == 0 ) { xdiff = labelSizeX / 2.0; } else if ( haliString.compare( "Right", Qt::CaseInsensitive ) == 0 ) { xdiff = labelSizeX; } if ( valiString.compare( "Top", Qt::CaseInsensitive ) == 0 || valiString.compare( "Cap", Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY; } else { double descentRatio = 1 / labelFontMetrics.ascent() / labelFontMetrics.height(); if ( valiString.compare( "Base", Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY * descentRatio; } else if ( valiString.compare( "Half", Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY * 0.5 * ( 1 - descentRatio ); } } double angle = mCurrentLabelPos.rotation; double xd = xdiff * cos( angle ) - ydiff * sin( angle ); double yd = xdiff * sin( angle ) + ydiff * cos( angle ); if ( mCurrentLabelPos.upsideDown && !ignoreUpsideDown ) { pos.setX( pos.x() - xd ); pos.setY( pos.y() - yd ); } else { pos.setX( pos.x() + xd ); pos.setY( pos.y() + yd ); } return true; }
bool QgsMapToolLabel::rotationPoint( QgsPoint& pos, bool ignoreUpsideDown ) { QVector<QgsPoint> cornerPoints = mCurrentLabelPos.cornerPoints; if ( cornerPoints.size() < 4 ) { return false; } if ( mCurrentLabelPos.upsideDown && !ignoreUpsideDown ) { pos = mCurrentLabelPos.cornerPoints.at( 2 ); } else { pos = mCurrentLabelPos.cornerPoints.at( 0 ); } //alignment always center/center and rotation 0 for diagrams if ( mCurrentLabelPos.isDiagram ) { pos.setX( pos.x() + mCurrentLabelPos.labelRect.width() / 2.0 ); pos.setY( pos.y() + mCurrentLabelPos.labelRect.height() / 2.0 ); return true; } //adapt pos depending on data defined alignment QString haliString, valiString; currentAlignment( haliString, valiString ); QFont labelFont = labelFontCurrentFeature(); QFontMetricsF labelFontMetrics( labelFont ); //label text? QString labelText = currentLabelText(); bool labelSettingsOk; QgsPalLayerSettings& labelSettings = currentLabelSettings( &labelSettingsOk ); if ( !labelSettingsOk ) { return false; } double labelSizeX, labelSizeY; labelSettings.calculateLabelSize( &labelFontMetrics, labelText, labelSizeX, labelSizeY ); double xdiff = 0; double ydiff = 0; if ( haliString.compare( "Center", Qt::CaseInsensitive ) == 0 ) { xdiff = labelSizeX / 2.0; } else if ( haliString.compare( "Right", Qt::CaseInsensitive ) == 0 ) { xdiff = labelSizeX; } if ( valiString.compare( "Top", Qt::CaseInsensitive ) == 0 || valiString.compare( "Cap", Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY; } else { double descentRatio = 1 / labelFontMetrics.ascent() / labelFontMetrics.height(); if ( valiString.compare( "Base", Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY * descentRatio; } else if ( valiString.compare( "Half", Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY * descentRatio; ydiff = labelSizeY * 0.5 * ( 1 - descentRatio ); } } double angle = mCurrentLabelPos.rotation; double xd = xdiff * cos( angle ) - ydiff * sin( angle ); double yd = xdiff * sin( angle ) + ydiff * cos( angle ); if ( mCurrentLabelPos.upsideDown && !ignoreUpsideDown ) { pos.setX( pos.x() - xd ); pos.setY( pos.y() - yd ); } else { pos.setX( pos.x() + xd ); pos.setY( pos.y() + yd ); } return true; }
void QgsLabel::renderLabel( QPainter * painter, const QgsRectangle& viewExtent, const QgsCoordinateTransform* coordinateTransform, const QgsMapToPixel *transform, QgsFeature &feature, bool selected, QgsLabelAttributes *classAttributes, double sizeScale, double rasterScaleFactor ) { QPen pen; QFont font; QString value; QString text; /* Calc scale (not nice) */ QgsPoint point; point = transform->transform( 0, 0 ); double x1 = point.x(); point = transform->transform( 1000, 0 ); double x2 = point.x(); double scale = ( x2 - x1 ) * 0.001; /* Text */ value = fieldValue( Text, feature ); if ( value.isEmpty() ) { text = mLabelAttributes->text(); } else { text = value; } /* Font */ value = fieldValue( Family, feature ); if ( value.isEmpty() ) { font.setFamily( mLabelAttributes->family() ); } else { font.setFamily( value ); } double size; value = fieldValue( Size, feature ); if ( value.isEmpty() ) { size = mLabelAttributes->size(); } else { size = value.toDouble(); } int sizeType; value = fieldValue( SizeType, feature ); if ( value.isEmpty() ) sizeType = mLabelAttributes->sizeType(); else { value = value.toLower(); if ( value.compare( "mapunits" ) == 0 ) sizeType = QgsLabelAttributes::MapUnits; else sizeType = QgsLabelAttributes::PointUnits; } if ( sizeType == QgsLabelAttributes::MapUnits ) { size *= scale; } else //point units { double sizeMM = size * 0.3527; size = sizeMM * sizeScale; } //Request font larger (multiplied by rasterScaleFactor) as a workaround for the Qt font bug //and scale the painter down by rasterScaleFactor when drawing the label size *= rasterScaleFactor; if (( int )size <= 0 ) // skip too small labels return; font.setPixelSize( size ); value = fieldValue( Color, feature ); if ( value.isEmpty() ) { pen.setColor( mLabelAttributes->color() ); } else { pen.setColor( QColor( value ) ); } value = fieldValue( Bold, feature ); if ( value.isEmpty() ) { font.setBold( mLabelAttributes->bold() ); } else { font.setBold(( bool ) value.toInt() ); } value = fieldValue( Italic, feature ); if ( value.isEmpty() ) { font.setItalic( mLabelAttributes->italic() ); } else { font.setItalic(( bool ) value.toInt() ); } value = fieldValue( Underline, feature ); if ( value.isEmpty() ) { font.setUnderline( mLabelAttributes->underline() ); } else { font.setUnderline(( bool ) value.toInt() ); } // QgsPoint overridePoint; bool useOverridePoint = false; value = fieldValue( XCoordinate, feature ); if ( !value.isEmpty() ) { overridePoint.setX( value.toDouble() ); useOverridePoint = true; } value = fieldValue( YCoordinate, feature ); if ( !value.isEmpty() ) { overridePoint.setY( value.toDouble() ); useOverridePoint = true; } /* Alignment */ int alignment; QFontMetrics fm( font ); int width, height; if ( mLabelAttributes->multilineEnabled() ) { QStringList texts = text.split( "\n" ); width = 0; for ( int i = 0; i < texts.size(); i++ ) { int w = fm.width( texts[i] ); if ( w > width ) width = w; } height = fm.height() * texts.size(); } else { width = fm.width( text ); height = fm.height(); } int dx = 0; int dy = 0; value = fieldValue( Alignment, feature ); if ( value.isEmpty() ) { alignment = mLabelAttributes->alignment(); } else { value = value.toLower(); alignment = 0; if ( value.contains( "left" ) ) alignment |= Qt::AlignLeft; else if ( value.contains( "right" ) ) alignment |= Qt::AlignRight; else alignment |= Qt::AlignHCenter; if ( value.contains( "bottom" ) ) alignment |= Qt::AlignBottom; else if ( value.contains( "top" ) ) alignment |= Qt::AlignTop; else alignment |= Qt::AlignVCenter; } if ( alignment & Qt::AlignLeft ) { dx = 0; } else if ( alignment & Qt::AlignHCenter ) { dx = -width / 2; } else if ( alignment & Qt::AlignRight ) { dx = -width; } if ( alignment & Qt::AlignBottom ) { dy = 0; } else if ( alignment & Qt::AlignVCenter ) { dy = height / 2; } else if ( alignment & Qt::AlignTop ) { dy = height; } // Offset double xoffset, yoffset; value = fieldValue( XOffset, feature ); if ( value.isEmpty() ) { xoffset = mLabelAttributes->xOffset(); } else { xoffset = value.toDouble(); } value = fieldValue( YOffset, feature ); if ( value.isEmpty() ) { yoffset = mLabelAttributes->yOffset(); } else { yoffset = value.toDouble(); } // recalc offset to pixels if ( mLabelAttributes->offsetType() == QgsLabelAttributes::MapUnits ) { xoffset *= scale; yoffset *= scale; } else { xoffset = xoffset * 0.3527 * sizeScale; yoffset = yoffset * 0.3527 * sizeScale; } // Angle double ang; value = fieldValue( Angle, feature ); if ( value.isEmpty() ) { ang = mLabelAttributes->angle(); } else { ang = value.toDouble(); } // Work out a suitable position to put the label for the // feature. For multi-geometries, put the same label on each // part. if ( useOverridePoint ) { renderLabel( painter, overridePoint, coordinateTransform, transform, text, font, pen, dx, dy, xoffset, yoffset, ang, width, height, alignment, sizeScale, rasterScaleFactor ); } else { std::vector<labelpoint> points; labelPoint( points, feature ); for ( uint i = 0; i < points.size(); ++i ) { renderLabel( painter, points[i].p, coordinateTransform, transform, text, font, pen, dx, dy, xoffset, yoffset, mLabelAttributes->angleIsAuto() ? points[i].angle : ang, width, height, alignment, sizeScale, rasterScaleFactor ); } } }