void Qgs3DUtils::clampAltitudes( QgsLineString *lineString, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, const QgsPoint ¢roid, float height, const Qgs3DMapSettings &map ) { for ( int i = 0; i < lineString->nCoordinates(); ++i ) { float terrainZ = 0; if ( altClamp == Qgs3DTypes::AltClampRelative || altClamp == Qgs3DTypes::AltClampTerrain ) { QgsPointXY pt; if ( altBind == Qgs3DTypes::AltBindVertex ) { pt.setX( lineString->xAt( i ) ); pt.setY( lineString->yAt( i ) ); } else { pt.set( centroid.x(), centroid.y() ); } terrainZ = map.terrainGenerator()->heightAt( pt.x(), pt.y(), map ); } float geomZ = 0; if ( altClamp == Qgs3DTypes::AltClampAbsolute || altClamp == Qgs3DTypes::AltClampRelative ) geomZ = lineString->zAt( i ); float z = ( terrainZ + geomZ ) * map.terrainVerticalScale() + height; lineString->setZAt( i, z ); } }
bool QgsDelimitedTextProvider::pointFromXY( QString &sX, QString &sY, QgsPointXY &pt, const QString &decimalPoint, bool xyDms ) { if ( ! decimalPoint.isEmpty() ) { sX.replace( decimalPoint, QLatin1String( "." ) ); sY.replace( decimalPoint, QLatin1String( "." ) ); } bool xOk, yOk; double x, y; if ( xyDms ) { x = dmsStringToDouble( sX, &xOk ); y = dmsStringToDouble( sY, &yOk ); } else { x = sX.toDouble( &xOk ); y = sY.toDouble( &yOk ); } if ( xOk && yOk ) { pt.setX( x ); pt.setY( y ); return true; } return false; }
bool QgsGeorefTransform::gdal_transform( const QgsPointXY &src, QgsPointXY &dst, int dstToSrc ) const { GDALTransformerFunc t = GDALTransformer(); // Fail if no transformer function was returned if ( !t ) return false; // Copy the source coordinate for inplace transform double x = src.x(); double y = src.y(); double z = 0.0; int success; // Call GDAL transform function ( *t )( GDALTransformerArgs(), dstToSrc, 1, &x, &y, &z, &success ); if ( !success ) return false; dst.setX( x ); dst.setY( y ); return true; }
bool QgsMapToolLabel::currentLabelRotationPoint( QgsPointXY &pos, bool ignoreUpsideDown, bool rotatingUnpinned ) { QVector<QgsPointXY> cornerPoints = mCurrentLabel.pos.cornerPoints; if ( cornerPoints.size() < 4 ) { return false; } if ( mCurrentLabel.pos.upsideDown && !ignoreUpsideDown ) { pos = cornerPoints.at( 2 ); } else { pos = cornerPoints.at( 0 ); } //alignment always center/center and rotation 0 for diagrams if ( mCurrentLabel.pos.isDiagram ) { pos.setX( pos.x() + mCurrentLabel.pos.labelRect.width() / 2.0 ); pos.setY( pos.y() + mCurrentLabel.pos.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 = QStringLiteral( "Center" ); valiString = QStringLiteral( "Half" ); } // QFont labelFont = labelFontCurrentFeature(); QFontMetricsF labelFontMetrics( mCurrentLabel.pos.labelFont ); // NOTE: this assumes the label corner points comprise a rectangle and that the // CRS supports equidistant measurements to accurately determine hypotenuse QgsPointXY cp_0 = cornerPoints.at( 0 ); QgsPointXY cp_1 = cornerPoints.at( 1 ); QgsPointXY 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 = std::sqrt( cp_0.sqrDist( cp_1 ) ); double labelSizeY = std::sqrt( cp_0.sqrDist( cp_3 ) ); double xdiff = 0; double ydiff = 0; if ( haliString.compare( QLatin1String( "Center" ), Qt::CaseInsensitive ) == 0 ) { xdiff = labelSizeX / 2.0; } else if ( haliString.compare( QLatin1String( "Right" ), Qt::CaseInsensitive ) == 0 ) { xdiff = labelSizeX; } if ( valiString.compare( QLatin1String( "Top" ), Qt::CaseInsensitive ) == 0 || valiString.compare( QLatin1String( "Cap" ), Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY; } else { double descentRatio = 1 / labelFontMetrics.ascent() / labelFontMetrics.height(); if ( valiString.compare( QLatin1String( "Base" ), Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY * descentRatio; } else if ( valiString.compare( QLatin1String( "Half" ), Qt::CaseInsensitive ) == 0 ) { ydiff = labelSizeY * 0.5 * ( 1 - descentRatio ); } } double angle = mCurrentLabel.pos.rotation; double xd = xdiff * std::cos( angle ) - ydiff * std::sin( angle ); double yd = xdiff * std::sin( angle ) + ydiff * std::cos( angle ); if ( mCurrentLabel.pos.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 QgsMapToolPinLabels::pinUnpinCurrentLabel( bool pin ) { QgsVectorLayer *vlayer = mCurrentLabel.layer; const QgsLabelPosition &labelpos = mCurrentLabel.pos; // skip diagrams if ( labelpos.isDiagram ) { QgsDebugMsg( QStringLiteral( "Label is diagram, skipping" ) ); return false; } // verify attribute table has x, y fields mapped int xCol, yCol; double xPosOrig, yPosOrig; bool xSuccess, ySuccess; if ( !currentLabelDataDefinedPosition( xPosOrig, xSuccess, yPosOrig, ySuccess, xCol, yCol ) ) { QgsDebugMsg( QStringLiteral( "Label X or Y column not mapped, skipping" ) ); return false; } // rotation field is optional, but will be used if available, unless data exists int rCol; bool rSuccess = false; double defRot; bool hasRCol = currentLabelDataDefinedRotation( defRot, rSuccess, rCol, true ); // get whether to preserve predefined rotation data during label pin/unpin operations bool preserveRot = currentLabelPreserveRotation(); // edit attribute table int fid = labelpos.featureId; bool writeFailed = false; QString labelText = currentLabelText( 24 ); if ( pin ) { // QgsPointXY labelpoint = labelpos.cornerPoints.at( 0 ); QgsPointXY referencePoint; if ( !currentLabelRotationPoint( referencePoint, !preserveRot, false ) ) { referencePoint.setX( labelpos.labelRect.xMinimum() ); referencePoint.setY( labelpos.labelRect.yMinimum() ); } double labelX = referencePoint.x(); double labelY = referencePoint.y(); double labelR = labelpos.rotation * 180 / M_PI; // transform back to layer crs QgsPointXY transformedPoint = mCanvas->mapSettings().mapToLayerCoordinates( vlayer, referencePoint ); labelX = transformedPoint.x(); labelY = transformedPoint.y(); vlayer->beginEditCommand( tr( "Pinned label" ) + QStringLiteral( " '%1'" ).arg( labelText ) ); writeFailed = !vlayer->changeAttributeValue( fid, xCol, labelX ); if ( !vlayer->changeAttributeValue( fid, yCol, labelY ) ) writeFailed = true; if ( hasRCol && !preserveRot ) { if ( !vlayer->changeAttributeValue( fid, rCol, labelR ) ) writeFailed = true; } vlayer->endEditCommand(); } else { vlayer->beginEditCommand( tr( "Unpinned label" ) + QStringLiteral( " '%1'" ).arg( labelText ) ); writeFailed = !vlayer->changeAttributeValue( fid, xCol, QVariant( QString() ) ); if ( !vlayer->changeAttributeValue( fid, yCol, QVariant( QString() ) ) ) writeFailed = true; if ( hasRCol && !preserveRot ) { if ( !vlayer->changeAttributeValue( fid, rCol, QVariant( QString() ) ) ) writeFailed = true; } vlayer->endEditCommand(); } if ( writeFailed ) { QgsDebugMsg( QStringLiteral( "Write to attribute table failed" ) ); #if 0 QgsDebugMsg( QStringLiteral( "Undoing and removing failed command from layer's undo stack" ) ); int lastCmdIndx = vlayer->undoStack()->count(); const QgsUndoCommand *lastCmd = qobject_cast<const QgsUndoCommand *>( vlayer->undoStack()->command( lastCmdIndx ) ); if ( lastCmd ) { vlayer->undoEditCommand( lastCmd ); delete vlayer->undoStack()->command( lastCmdIndx ); } #endif return false; } return true; }
void QgsMapToolMoveLabel::canvasPressEvent( QgsMapMouseEvent *e ) { deleteRubberBands(); QgsLabelPosition labelPos; if ( !labelAtPosition( e, labelPos ) ) { mCurrentLabel = LabelDetails(); return; } mCurrentLabel = LabelDetails( labelPos ); QgsVectorLayer *vlayer = mCurrentLabel.layer; if ( !vlayer ) { return; } int xCol = -1, yCol = -1; if ( !mCurrentLabel.pos.isDiagram && !labelMoveable( vlayer, mCurrentLabel.settings, xCol, yCol ) ) { QgsPalIndexes indexes; if ( createAuxiliaryFields( indexes ) ) return; if ( !labelMoveable( vlayer, mCurrentLabel.settings, xCol, yCol ) ) return; xCol = indexes[ QgsPalLayerSettings::PositionX ]; yCol = indexes[ QgsPalLayerSettings::PositionY ]; } else if ( mCurrentLabel.pos.isDiagram && !diagramMoveable( vlayer, xCol, yCol ) ) { QgsDiagramIndexes indexes; if ( createAuxiliaryFields( indexes ) ) return; if ( !diagramMoveable( vlayer, xCol, yCol ) ) return; xCol = indexes[ QgsDiagramLayerSettings::PositionX ]; yCol = indexes[ QgsDiagramLayerSettings::PositionY ]; } if ( xCol >= 0 && yCol >= 0 ) { mStartPointMapCoords = toMapCoordinates( e->pos() ); QgsPointXY referencePoint; if ( !currentLabelRotationPoint( referencePoint, !currentLabelPreserveRotation(), false ) ) { referencePoint.setX( mCurrentLabel.pos.labelRect.xMinimum() ); referencePoint.setY( mCurrentLabel.pos.labelRect.yMinimum() ); } mClickOffsetX = mStartPointMapCoords.x() - referencePoint.x(); mClickOffsetY = mStartPointMapCoords.y() - referencePoint.y(); createRubberBands(); } }
QgsCadUtils::AlignMapPointOutput QgsCadUtils::alignMapPoint( const QgsPointXY &originalMapPoint, const QgsCadUtils::AlignMapPointContext &ctx ) { QgsCadUtils::AlignMapPointOutput res; res.valid = true; res.softLockCommonAngle = -1; // try to snap to anything QgsPointLocator::Match snapMatch = ctx.snappingUtils->snapToMap( originalMapPoint ); QgsPointXY point = snapMatch.isValid() ? snapMatch.point() : originalMapPoint; // try to snap explicitly to a segment - useful for some constraints QgsPointXY edgePt0, edgePt1; EdgesOnlyFilter edgesOnlyFilter; QgsPointLocator::Match edgeMatch = ctx.snappingUtils->snapToMap( originalMapPoint, &edgesOnlyFilter ); if ( edgeMatch.hasEdge() ) edgeMatch.edgePoints( edgePt0, edgePt1 ); res.edgeMatch = edgeMatch; QgsPointXY previousPt, penultimatePt; if ( ctx.cadPointList.count() >= 2 ) previousPt = ctx.cadPointList.at( 1 ); if ( ctx.cadPointList.count() >= 3 ) penultimatePt = ctx.cadPointList.at( 2 ); // ***************************** // ---- X constraint if ( ctx.xConstraint.locked ) { if ( !ctx.xConstraint.relative ) { point.setX( ctx.xConstraint.value ); } else if ( ctx.cadPointList.count() >= 2 ) { point.setX( previousPt.x() + ctx.xConstraint.value ); } if ( edgeMatch.hasEdge() && !ctx.yConstraint.locked ) { // intersect with snapped segment line at X ccordinate const double dx = edgePt1.x() - edgePt0.x(); if ( dx == 0 ) { point.setY( edgePt0.y() ); } else { const double dy = edgePt1.y() - edgePt0.y(); point.setY( edgePt0.y() + ( dy * ( point.x() - edgePt0.x() ) ) / dx ); } } } // ***************************** // ---- Y constraint if ( ctx.yConstraint.locked ) { if ( !ctx.yConstraint.relative ) { point.setY( ctx.yConstraint.value ); } else if ( ctx.cadPointList.count() >= 2 ) { point.setY( previousPt.y() + ctx.yConstraint.value ); } if ( edgeMatch.hasEdge() && !ctx.xConstraint.locked ) { // intersect with snapped segment line at Y ccordinate const double dy = edgePt1.y() - edgePt0.y(); if ( dy == 0 ) { point.setX( edgePt0.x() ); } else { const double dx = edgePt1.x() - edgePt0.x(); point.setX( edgePt0.x() + ( dx * ( point.y() - edgePt0.y() ) ) / dy ); } } } // ***************************** // ---- Common Angle constraint if ( !ctx.angleConstraint.locked && ctx.cadPointList.count() >= 2 && ctx.commonAngleConstraint.locked && ctx.commonAngleConstraint.value != 0 ) { double commonAngle = ctx.commonAngleConstraint.value * M_PI / 180; // see if soft common angle constraint should be performed // only if not in HardLock mode double softAngle = std::atan2( point.y() - previousPt.y(), point.x() - previousPt.x() ); double deltaAngle = 0; if ( ctx.commonAngleConstraint.relative && ctx.cadPointList.count() >= 3 ) { // compute the angle relative to the last segment (0° is aligned with last segment) deltaAngle = std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() ); softAngle -= deltaAngle; } int quo = std::round( softAngle / commonAngle ); if ( std::fabs( softAngle - quo * commonAngle ) * 180.0 * M_1_PI <= SOFT_CONSTRAINT_TOLERANCE_DEGREES ) { // also check the distance in pixel to the line, otherwise it's too sticky at long ranges softAngle = quo * commonAngle; // http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html // use the direction vector (cos(a),sin(a)) from previous point. |x2-x1|=1 since sin2+cos2=1 const double dist = std::fabs( std::cos( softAngle + deltaAngle ) * ( previousPt.y() - point.y() ) - std::sin( softAngle + deltaAngle ) * ( previousPt.x() - point.x() ) ); if ( dist / ctx.mapUnitsPerPixel < SOFT_CONSTRAINT_TOLERANCE_PIXEL ) { res.softLockCommonAngle = 180.0 / M_PI * softAngle; } } } // angle can be locked in one of the two ways: // 1. "hard" lock defined by the user // 2. "soft" lock from common angle (e.g. 45 degrees) bool angleLocked = false, angleRelative = false; int angleValueDeg = 0; if ( ctx.angleConstraint.locked ) { angleLocked = true; angleRelative = ctx.angleConstraint.relative; angleValueDeg = ctx.angleConstraint.value; } else if ( res.softLockCommonAngle != -1 ) { angleLocked = true; angleRelative = ctx.commonAngleConstraint.relative; angleValueDeg = res.softLockCommonAngle; } // ***************************** // ---- Angle constraint // input angles are in degrees if ( angleLocked ) { double angleValue = angleValueDeg * M_PI / 180; if ( angleRelative && ctx.cadPointList.count() >= 3 ) { // compute the angle relative to the last segment (0° is aligned with last segment) angleValue += std::atan2( previousPt.y() - penultimatePt.y(), previousPt.x() - penultimatePt.x() ); } double cosa = std::cos( angleValue ); double sina = std::sin( angleValue ); double v = ( point.x() - previousPt.x() ) * cosa + ( point.y() - previousPt.y() ) * sina; if ( ctx.xConstraint.locked && ctx.yConstraint.locked ) { // do nothing if both X,Y are already locked } else if ( ctx.xConstraint.locked ) { if ( qgsDoubleNear( cosa, 0.0 ) ) { res.valid = false; } else { double x = ctx.xConstraint.value; if ( !ctx.xConstraint.relative ) { x -= previousPt.x(); } point.setY( previousPt.y() + x * sina / cosa ); } } else if ( ctx.yConstraint.locked ) { if ( qgsDoubleNear( sina, 0.0 ) ) { res.valid = false; } else { double y = ctx.yConstraint.value; if ( !ctx.yConstraint.relative ) { y -= previousPt.y(); } point.setX( previousPt.x() + y * cosa / sina ); } } else { point.setX( previousPt.x() + cosa * v ); point.setY( previousPt.y() + sina * v ); } if ( edgeMatch.hasEdge() && !ctx.distanceConstraint.locked ) { // magnetize to the intersection of the snapped segment and the lockedAngle // line of previous point + locked angle const double x1 = previousPt.x(); const double y1 = previousPt.y(); const double x2 = previousPt.x() + cosa; const double y2 = previousPt.y() + sina; // line of snapped segment const double x3 = edgePt0.x(); const double y3 = edgePt0.y(); const double x4 = edgePt1.x(); const double y4 = edgePt1.y(); const double d = ( x1 - x2 ) * ( y3 - y4 ) - ( y1 - y2 ) * ( x3 - x4 ); // do not compute intersection if lines are almost parallel // this threshold might be adapted if ( std::fabs( d ) > 0.01 ) { point.setX( ( ( x3 - x4 ) * ( x1 * y2 - y1 * x2 ) - ( x1 - x2 ) * ( x3 * y4 - y3 * x4 ) ) / d ); point.setY( ( ( y3 - y4 ) * ( x1 * y2 - y1 * x2 ) - ( y1 - y2 ) * ( x3 * y4 - y3 * x4 ) ) / d ); } } } // ***************************** // ---- Distance constraint if ( ctx.distanceConstraint.locked && ctx.cadPointList.count() >= 2 ) { if ( ctx.xConstraint.locked || ctx.yConstraint.locked ) { // perform both to detect errors in constraints if ( ctx.xConstraint.locked ) { QgsPointXY verticalPt0( ctx.xConstraint.value, point.y() ); QgsPointXY verticalPt1( ctx.xConstraint.value, point.y() + 1 ); res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, verticalPt0, verticalPt1, point ); } if ( ctx.yConstraint.locked ) { QgsPointXY horizontalPt0( point.x(), ctx.yConstraint.value ); QgsPointXY horizontalPt1( point.x() + 1, ctx.yConstraint.value ); res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, horizontalPt0, horizontalPt1, point ); } } else { const double dist = std::sqrt( point.sqrDist( previousPt ) ); if ( dist == 0 ) { // handle case where mouse is over origin and distance constraint is enabled // take arbitrary horizontal line point.set( previousPt.x() + ctx.distanceConstraint.value, previousPt.y() ); } else { const double vP = ctx.distanceConstraint.value / dist; point.set( previousPt.x() + ( point.x() - previousPt.x() ) * vP, previousPt.y() + ( point.y() - previousPt.y() ) * vP ); } if ( edgeMatch.hasEdge() && !ctx.angleConstraint.locked ) { // we will magnietize to the intersection of that segment and the lockedDistance ! res.valid &= lineCircleIntersection( previousPt, ctx.distanceConstraint.value, edgePt0, edgePt1, point ); } } } // ***************************** // ---- calculate CAD values QgsDebugMsgLevel( QString( "point: %1 %2" ).arg( point.x() ).arg( point.y() ), 4 ); QgsDebugMsgLevel( QString( "previous point: %1 %2" ).arg( previousPt.x() ).arg( previousPt.y() ), 4 ); QgsDebugMsgLevel( QString( "penultimate point: %1 %2" ).arg( penultimatePt.x() ).arg( penultimatePt.y() ), 4 ); //QgsDebugMsg( QString( "dx: %1 dy: %2" ).arg( point.x() - previousPt.x() ).arg( point.y() - previousPt.y() ) ); //QgsDebugMsg( QString( "ddx: %1 ddy: %2" ).arg( previousPt.x() - penultimatePt.x() ).arg( previousPt.y() - penultimatePt.y() ) ); res.finalMapPoint = point; return res; }
void QgsAnnotation::_readXml( const QDomElement &annotationElem, const QgsReadWriteContext &context ) { if ( annotationElem.isNull() ) { return; } QPointF pos; pos.setX( annotationElem.attribute( QStringLiteral( "canvasPosX" ), QStringLiteral( "0" ) ).toDouble() ); pos.setY( annotationElem.attribute( QStringLiteral( "canvasPosY" ), QStringLiteral( "0" ) ).toDouble() ); if ( pos.x() >= 1 || pos.x() < 0 || pos.y() < 0 || pos.y() >= 1 ) mRelativePosition = QPointF(); else mRelativePosition = pos; QgsPointXY mapPos; mapPos.setX( annotationElem.attribute( QStringLiteral( "mapPosX" ), QStringLiteral( "0" ) ).toDouble() ); mapPos.setY( annotationElem.attribute( QStringLiteral( "mapPosY" ), QStringLiteral( "0" ) ).toDouble() ); mMapPosition = mapPos; if ( !mMapPositionCrs.readXml( annotationElem ) ) { mMapPositionCrs = QgsCoordinateReferenceSystem(); } mContentsMargins = QgsMargins::fromString( annotationElem.attribute( QStringLiteral( "contentsMargin" ) ) ); mFrameSize.setWidth( annotationElem.attribute( QStringLiteral( "frameWidth" ), QStringLiteral( "50" ) ).toDouble() ); mFrameSize.setHeight( annotationElem.attribute( QStringLiteral( "frameHeight" ), QStringLiteral( "50" ) ).toDouble() ); mOffsetFromReferencePoint.setX( annotationElem.attribute( QStringLiteral( "offsetX" ), QStringLiteral( "0" ) ).toDouble() ); mOffsetFromReferencePoint.setY( annotationElem.attribute( QStringLiteral( "offsetY" ), QStringLiteral( "0" ) ).toDouble() ); mHasFixedMapPosition = annotationElem.attribute( QStringLiteral( "mapPositionFixed" ), QStringLiteral( "1" ) ).toInt(); mVisible = annotationElem.attribute( QStringLiteral( "visible" ), QStringLiteral( "1" ) ).toInt(); if ( annotationElem.hasAttribute( QStringLiteral( "mapLayer" ) ) ) { mMapLayer = QgsProject::instance()->mapLayer( annotationElem.attribute( QStringLiteral( "mapLayer" ) ) ); } //marker symbol QDomElement symbolElem = annotationElem.firstChildElement( QStringLiteral( "symbol" ) ); if ( !symbolElem.isNull() ) { QgsMarkerSymbol *symbol = QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( symbolElem, context ); if ( symbol ) { mMarkerSymbol.reset( symbol ); } } mFillSymbol.reset( nullptr ); QDomElement fillElem = annotationElem.firstChildElement( QStringLiteral( "fillSymbol" ) ); if ( !fillElem.isNull() ) { QDomElement symbolElem = fillElem.firstChildElement( QStringLiteral( "symbol" ) ); if ( !symbolElem.isNull() ) { QgsFillSymbol *symbol = QgsSymbolLayerUtils::loadSymbol<QgsFillSymbol>( symbolElem, context ); if ( symbol ) { mFillSymbol.reset( symbol ); } } } if ( !mFillSymbol ) { QColor frameColor; frameColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameColor" ), QStringLiteral( "#000000" ) ) ); frameColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameColorAlpha" ), QStringLiteral( "255" ) ).toInt() ); QColor frameBackgroundColor; frameBackgroundColor.setNamedColor( annotationElem.attribute( QStringLiteral( "frameBackgroundColor" ) ) ); frameBackgroundColor.setAlpha( annotationElem.attribute( QStringLiteral( "frameBackgroundColorAlpha" ), QStringLiteral( "255" ) ).toInt() ); double frameBorderWidth = annotationElem.attribute( QStringLiteral( "frameBorderWidth" ), QStringLiteral( "0.5" ) ).toDouble(); // need to roughly convert border width from pixels to mm - just assume 96 dpi frameBorderWidth = frameBorderWidth * 25.4 / 96.0; QgsStringMap props; props.insert( QStringLiteral( "color" ), frameBackgroundColor.name() ); props.insert( QStringLiteral( "style" ), QStringLiteral( "solid" ) ); props.insert( QStringLiteral( "style_border" ), QStringLiteral( "solid" ) ); props.insert( QStringLiteral( "color_border" ), frameColor.name() ); props.insert( QStringLiteral( "width_border" ), QString::number( frameBorderWidth ) ); props.insert( QStringLiteral( "joinstyle" ), QStringLiteral( "miter" ) ); mFillSymbol.reset( QgsFillSymbol::createSimple( props ) ); } updateBalloon(); emit mapLayerChanged(); }