Ejemplo n.º 1
0
bool QgsGeorefTransform::transformWorldToRaster( const QgsPointXY &world, QgsPointXY &raster )
{
  bool success = gdal_transform( world, raster, 1 );
  // flip y coordinate due to different CS orientation
  raster.setY( -raster.y() );
  return success;
}
Ejemplo n.º 2
0
void Qgs3DUtils::clampAltitudes( QgsLineString *lineString, Qgs3DTypes::AltitudeClamping altClamp, Qgs3DTypes::AltitudeBinding altBind, const QgsPoint &centroid, 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 );
  }
}
Ejemplo n.º 3
0
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;
}
Ejemplo n.º 4
0
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;
}
Ejemplo n.º 5
0
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;
}
Ejemplo n.º 6
0
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;
}
Ejemplo n.º 7
0
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();
  }
}
Ejemplo n.º 8
0
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;
}
Ejemplo n.º 9
0
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();
}