176 changes: 171 additions & 5 deletions src/core/qgspallabeling.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,11 @@ QgsPalLayerSettings::QgsPalLayerSettings()
{
placement = AroundPoint;
placementFlags = 0;
xQuadOffset = 0;
yQuadOffset = 0;
xOffset = 0;
yOffset = 0;
angleOffset = 0;
//textFont = QFont();
textNamedStyle = QString( "" );
textColor = Qt::black;
Expand Down Expand Up @@ -169,6 +174,7 @@ QgsPalLayerSettings::QgsPalLayerSettings()
addDirectionSymbol = false;
fontSizeInMapUnits = false;
bufferSizeInMapUnits = false;
labelOffsetInMapUnits = true;
distInMapUnits = false;
wrapChar = "";
preserveRotation = true;
Expand All @@ -181,6 +187,11 @@ QgsPalLayerSettings::QgsPalLayerSettings( const QgsPalLayerSettings& s )
isExpression = s.isExpression;
placement = s.placement;
placementFlags = s.placementFlags;
xQuadOffset = s.xQuadOffset;
yQuadOffset = s.yQuadOffset;
xOffset = s.xOffset;
yOffset = s.yOffset;
angleOffset = s.angleOffset;
textFont = s.textFont;
textNamedStyle = s.textNamedStyle;
textColor = s.textColor;
Expand Down Expand Up @@ -209,6 +220,7 @@ QgsPalLayerSettings::QgsPalLayerSettings( const QgsPalLayerSettings& s )
fontSizeInMapUnits = s.fontSizeInMapUnits;
bufferSizeInMapUnits = s.bufferSizeInMapUnits;
distInMapUnits = s.distInMapUnits;
labelOffsetInMapUnits = s.labelOffsetInMapUnits;
wrapChar = s.wrapChar;
preserveRotation = s.preserveRotation;

Expand Down Expand Up @@ -343,6 +355,11 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer )
isExpression = layer->customProperty( "labeling/isExpression" ).toBool();
placement = ( Placement ) layer->customProperty( "labeling/placement" ).toInt();
placementFlags = layer->customProperty( "labeling/placementFlags" ).toUInt();
xQuadOffset = layer->customProperty( "labeling/xQuadOffset", QVariant( 0 ) ).toInt();
yQuadOffset = layer->customProperty( "labeling/yQuadOffset", QVariant( 0 ) ).toInt();
xOffset = layer->customProperty( "labeling/xOffset", QVariant( 0.0 ) ).toDouble();
yOffset = layer->customProperty( "labeling/yOffset", QVariant( 0.0 ) ).toDouble();
angleOffset = layer->customProperty( "labeling/angleOffset", QVariant( 0.0 ) ).toDouble();
QString fontFamily = layer->customProperty( "labeling/fontFamily" ).toString();
double fontSize = layer->customProperty( "labeling/fontSize" ).toDouble();
int fontWeight = layer->customProperty( "labeling/fontWeight" ).toInt();
Expand Down Expand Up @@ -380,6 +397,7 @@ void QgsPalLayerSettings::readFromLayer( QgsVectorLayer* layer )
fontSizeInMapUnits = layer->customProperty( "labeling/fontSizeInMapUnits" ).toBool();
bufferSizeInMapUnits = layer->customProperty( "labeling/bufferSizeInMapUnits" ).toBool();
distInMapUnits = layer->customProperty( "labeling/distInMapUnits" ).toBool();
labelOffsetInMapUnits = layer->customProperty( "labeling/labelOffsetInMapUnits", QVariant( true ) ).toBool();
wrapChar = layer->customProperty( "labeling/wrapChar" ).toString();
preserveRotation = layer->customProperty( "labeling/preserveRotation", QVariant( true ) ).toBool();
_readDataDefinedPropertyMap( layer, dataDefinedProperties );
Expand All @@ -394,6 +412,11 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer )
layer->setCustomProperty( "labeling/isExpression", isExpression );
layer->setCustomProperty( "labeling/placement", placement );
layer->setCustomProperty( "labeling/placementFlags", ( unsigned int )placementFlags );
layer->setCustomProperty( "labeling/xQuadOffset", xQuadOffset );
layer->setCustomProperty( "labeling/yQuadOffset", yQuadOffset );
layer->setCustomProperty( "labeling/xOffset", xOffset );
layer->setCustomProperty( "labeling/yOffset", yOffset );
layer->setCustomProperty( "labeling/angleOffset", angleOffset );

layer->setCustomProperty( "labeling/fontFamily", textFont.family() );
layer->setCustomProperty( "labeling/namedStyle", textNamedStyle );
Expand Down Expand Up @@ -430,6 +453,7 @@ void QgsPalLayerSettings::writeToLayer( QgsVectorLayer* layer )
layer->setCustomProperty( "labeling/fontSizeInMapUnits", fontSizeInMapUnits );
layer->setCustomProperty( "labeling/bufferSizeInMapUnits", bufferSizeInMapUnits );
layer->setCustomProperty( "labeling/distInMapUnits", distInMapUnits );
layer->setCustomProperty( "labeling/labelOffsetInMapUnits", labelOffsetInMapUnits );
layer->setCustomProperty( "labeling/wrapChar", wrapChar );
layer->setCustomProperty( "labeling/preserveRotation", preserveRotation );
_writeDataDefinedPropertyMap( layer, dataDefinedProperties );
Expand Down Expand Up @@ -647,6 +671,27 @@ void QgsPalLayerSettings::registerFeature( QgsVectorLayer* layer, QgsFeature& f
return;
}

// convert centroids to points before processing to use GEOS instead of PAL calculation
if (( placement == QgsPalLayerSettings::AroundPoint
|| placement == QgsPalLayerSettings::OverPoint )
&& geom->type() == QGis::Polygon )
{
QgsGeometry* centroidpt = geom->centroid();
if ( centroidpt->isGeosValid() && extentGeom->contains( centroidpt ) )
{
geom = QgsGeometry::fromPoint( centroidpt->asPoint() );
if ( geom->type() == QGis::Point )
{
QgsDebugMsg( QString( "Feature %1 centroid converted to point: " ).arg( f.id() ) );
}
}
else
{
// invalid geom type, skip registering feature with PAL
return;
}
}

// CLIP the geometry if it is bigger than the extent
QgsGeometry* geomClipped = NULL;
GEOSGeometry* geos_geom;
Expand All @@ -673,6 +718,7 @@ void QgsPalLayerSettings::registerFeature( QgsVectorLayer* layer, QgsFeature& f

//data defined position / alignment / rotation?
bool dataDefinedPosition = false;
bool labelIsPinned = false;
bool dataDefinedRotation = false;
double xPos = 0.0, yPos = 0.0, angle = 0.0;
bool ddXPos, ddYPos;
Expand All @@ -690,6 +736,7 @@ void QgsPalLayerSettings::registerFeature( QgsVectorLayer* layer, QgsFeature& f
if ( ddXPos && ddYPos )
{
dataDefinedPosition = true;
labelIsPinned = true;
//x/y shift in case of alignment
double xdiff = 0;
double ydiff = 0;
Expand Down Expand Up @@ -765,6 +812,49 @@ void QgsPalLayerSettings::registerFeature( QgsVectorLayer* layer, QgsFeature& f
}
}

// treat rotated labels of PAL layer point/centroid features as data defined
// does not flag label as pinned or rotatble
// always set rotation center as if Center/Half were set for data defined
bool overPointCentroid = false;
if ( !dataDefinedPosition
&& placement == QgsPalLayerSettings::OverPoint
&& geom->type() == QGis::Point )
{
overPointCentroid = true;
dataDefinedPosition = true;

QgsPoint fPt = geom->asPoint();
// default reference (feature) point is lower left corner of label bounding box
xPos = fPt.x();
yPos = fPt.y();

double xdiff = 0.0;
double ydiff = 0.0;

// as per Center for data defined
xdiff -= labelX / 2.0;

// as per Half for data defined
QFontMetrics labelFontMetrics( labelFont );
double descentRatio = labelFontMetrics.descent() / labelFontMetrics.height();
ydiff -= labelY * 0.5 * ( 1 - descentRatio );

if ( angleOffset != 0 )
{
angle = angleOffset * M_PI / 180; // convert to radians

dataDefinedRotation = true;
//adjust xdiff and ydiff for Center/Half
double xd = xdiff * cos( angle ) - ydiff * sin( angle );
double yd = xdiff * sin( angle ) + ydiff * cos( angle );
xdiff = xd;
ydiff = yd;
}

xPos += xdiff;
yPos += ydiff;
}

QgsPalGeometry* lbl = new QgsPalGeometry( f.id(), labelText, geos_geom_clone );

// record the created geometry - it will be deleted at the end.
Expand Down Expand Up @@ -811,6 +901,74 @@ void QgsPalLayerSettings::registerFeature( QgsVectorLayer* layer, QgsFeature& f
feat->setDistLabel( qAbs( ptOne.x() - ptZero.x() )* distance );
}

// treat offset labels of PAL layer point/centroid features as data defined
// does not flag label as pinned
// done after feature registration so label W and H are relative to any applied rotation
if ( overPointCentroid )
{
double labelW = labelX;
double labelH = labelY;

if ( angleOffset != 0 )
{
// use LabelPosition construction to calculate new rotated label dimensions
pal::FeaturePart* fpart = new FeaturePart( feat, geom->asGeos() );
pal::LabelPosition* lp = new LabelPosition( 1, xPos, yPos, labelX, labelY,
( angleOffset * M_PI / 180 ), 0.0, fpart );

double amin[2], amax[2];
lp->getBoundingBox( amin, amax );
QgsRectangle lblrect = QgsRectangle( amin[0], amin[1], amax[0], amax[1] );

// labelW = lp->getWidth();
// labelH = lp->getHeight();
labelW = lblrect.width();
labelH = lblrect.height();
delete fpart;
delete lp;
}

// x/y shift in case of alignment other than center
double xdiff = 0.0;
double ydiff = 0.0;

// quadrant offsets are -1, 0, or 1 (positive is up and right)
if ( xQuadOffset != 0 )
{
xdiff += labelW / 2 * xQuadOffset;
}
if ( yQuadOffset != 0 )
{
ydiff += labelH / 2 * yQuadOffset;
}

double mapUntsPerMM = context.mapToPixel().mapUnitsPerPixel() * context.scaleFactor();

if ( xOffset != 0 )
{
double xoff = xOffset;
if ( !labelOffsetInMapUnits ) //convert offset from mm to map units
{
xoff = xOffset * mapUntsPerMM;
}
xdiff += xoff;
}

if ( yOffset != 0 )
{
double yoff = yOffset;
if ( !labelOffsetInMapUnits ) //convert offset from mm to map units
{
yoff = yOffset * mapUntsPerMM;
}
ydiff += yoff;
}

xPos += xdiff;
yPos += ydiff;
feat->setFixedPosition( xPos, yPos );
}

//add parameters for data defined labeling to QgsPalGeometry
QMap< DataDefinedProperties, int >::const_iterator dIt = dataDefinedProperties.constBegin();
for ( ; dIt != dataDefinedProperties.constEnd(); ++dIt )
Expand All @@ -819,7 +977,7 @@ void QgsPalLayerSettings::registerFeature( QgsVectorLayer* layer, QgsFeature& f
}

// set geometry's pinned property
lbl->setIsPinned( dataDefinedPosition );
lbl->setIsPinned( labelIsPinned );
}

int QgsPalLayerSettings::sizeToPixel( double size, const QgsRenderContext& c, bool buffer ) const
Expand Down Expand Up @@ -979,13 +1137,21 @@ int QgsPalLabeling::prepareLayer( QgsVectorLayer* layer, QSet<int>& attrIndices,
lyr.textFont.setPixelSize( pixelFontSize );
}

// scale spacing sizes if using map units
if ( lyr.fontSizeInMapUnits )
{
double spacingPixelSize = lyr.textFont.wordSpacing() / ctx.mapToPixel().mapUnitsPerPixel() * ctx.rasterScaleFactor();
lyr.textFont.setWordSpacing( spacingPixelSize );
double spacingPixelSize;
if ( lyr.textFont.wordSpacing() != 0 )
{
spacingPixelSize = lyr.textFont.wordSpacing() / ctx.mapToPixel().mapUnitsPerPixel() * ctx.rasterScaleFactor();
lyr.textFont.setWordSpacing( spacingPixelSize );
}

spacingPixelSize = lyr.textFont.letterSpacing() / ctx.mapToPixel().mapUnitsPerPixel() * ctx.rasterScaleFactor();
lyr.textFont.setLetterSpacing( QFont::AbsoluteSpacing, spacingPixelSize );
if ( lyr.textFont.letterSpacing() != 0 )
{
spacingPixelSize = lyr.textFont.letterSpacing() / ctx.mapToPixel().mapUnitsPerPixel() * ctx.rasterScaleFactor();
lyr.textFont.setLetterSpacing( QFont::AbsoluteSpacing, spacingPixelSize );
}
}

//raster and vector scale factors
Expand Down
6 changes: 6 additions & 0 deletions src/core/qgspallabeling.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ class CORE_EXPORT QgsPalLayerSettings

Placement placement;
unsigned int placementFlags;
// offset labels of point/centroid features default to center
// move label to quadrant: left/down, don't move, right/up (-1, 0, 1)
int xQuadOffset, yQuadOffset;
double xOffset, yOffset; // offset from point in mm or map units
double angleOffset; // rotation applied to offset labels
QFont textFont;
QString textNamedStyle;
QColor textColor;
Expand Down Expand Up @@ -146,6 +151,7 @@ class CORE_EXPORT QgsPalLayerSettings
bool addDirectionSymbol;
bool fontSizeInMapUnits; //true if font size is in map units (otherwise in points)
bool bufferSizeInMapUnits; //true if buffer is in map units (otherwise in mm)
bool labelOffsetInMapUnits; //true if label offset is in map units (otherwise in mm)
bool distInMapUnits; //true if distance is in map units (otherwise in mm)
QString wrapChar;
// called from register feature hook
Expand Down
223 changes: 209 additions & 14 deletions src/ui/qgslabelingguibase.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1521,6 +1521,9 @@
</item>
<item>
<widget class="QDoubleSpinBox" name="mMinSizeSpinBox">
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="suffix">
<string> mm</string>
</property>
Expand Down Expand Up @@ -1618,6 +1621,12 @@
</item>
<item row="0" column="0">
<widget class="QGroupBox" name="groupBox_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
Expand Down Expand Up @@ -1650,7 +1659,7 @@
<item>
<widget class="QRadioButton" name="radAroundPoint">
<property name="text">
<string>around point</string>
<string>Around point</string>
</property>
<property name="checked">
<bool>true</bool>
Expand All @@ -1660,7 +1669,7 @@
<item>
<widget class="QRadioButton" name="radOverPoint">
<property name="text">
<string>over point</string>
<string>Offset from point</string>
</property>
</widget>
</item>
Expand All @@ -1671,7 +1680,7 @@
<item>
<widget class="QRadioButton" name="radLineParallel">
<property name="text">
<string>parallel</string>
<string>Parallel</string>
</property>
<property name="checked">
<bool>true</bool>
Expand All @@ -1681,14 +1690,14 @@
<item>
<widget class="QRadioButton" name="radLineCurved">
<property name="text">
<string>curved</string>
<string>Curved</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radLineHorizontal">
<property name="text">
<string>horizontal</string>
<string>Horizontal</string>
</property>
</widget>
</item>
Expand All @@ -1699,7 +1708,7 @@
<item>
<widget class="QRadioButton" name="radOverCentroid">
<property name="text">
<string>over centroid</string>
<string>Offset from centroid</string>
</property>
<property name="checked">
<bool>true</bool>
Expand All @@ -1709,28 +1718,28 @@
<item>
<widget class="QRadioButton" name="radAroundCentroid">
<property name="text">
<string>around centroid</string>
<string>Around centroid</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radPolygonHorizontal">
<property name="text">
<string>horizontal (slow)</string>
<string>Horizontal (slow)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radPolygonFree">
<property name="text">
<string>free (slow)</string>
<string>Free (slow)</string>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radPolygonPerimeter">
<property name="text">
<string>using perimeter</string>
<string>Using perimeter</string>
</property>
</widget>
</item>
Expand All @@ -1748,7 +1757,7 @@
<item>
<widget class="QStackedWidget" name="stackedOptions">
<property name="currentIndex">
<number>1</number>
<number>0</number>
</property>
<widget class="QWidget" name="pageOptionsPoint">
<layout class="QGridLayout" name="gridLayout_7">
Expand Down Expand Up @@ -1817,7 +1826,7 @@
<item>
<widget class="QCheckBox" name="chkLineAbove">
<property name="text">
<string>above line</string>
<string>Above line</string>
</property>
<property name="checked">
<bool>true</bool>
Expand All @@ -1827,14 +1836,14 @@
<item>
<widget class="QCheckBox" name="chkLineOn">
<property name="text">
<string>on line</string>
<string>On line</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="chkLineBelow">
<property name="text">
<string>below line</string>
<string>Below line</string>
</property>
</widget>
</item>
Expand Down Expand Up @@ -1895,6 +1904,192 @@
</layout>
</widget>
<widget class="QWidget" name="pageOptionsEmpty"/>
<widget class="QWidget" name="pageOptionsPointOffset">
<layout class="QGridLayout" name="gridLayout_20">
<item row="1" column="0">
<widget class="QDoubleSpinBox" name="mPointOffsetXOffsetSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="prefix">
<string>X </string>
</property>
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-9999999.000000000000000</double>
</property>
<property name="maximum">
<double>9999999.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="label_27">
<property name="text">
<string>degrees</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QComboBox" name="mPointOffsetUnitsComboBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<item>
<property name="text">
<string>mm</string>
</property>
</item>
<item>
<property name="text">
<string>map units</string>
</property>
</item>
</widget>
</item>
<item row="1" column="1">
<widget class="QDoubleSpinBox" name="mPointOffsetYOffsetSpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="prefix">
<string>Y </string>
</property>
<property name="suffix">
<string/>
</property>
<property name="decimals">
<number>4</number>
</property>
<property name="minimum">
<double>-9999999.000000000000000</double>
</property>
<property name="maximum">
<double>9999999.000000000000000</double>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>Rotation</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<layout class="QGridLayout" name="gridLayout_19">
<property name="verticalSpacing">
<number>8</number>
</property>
<item row="1" column="2">
<widget class="QRadioButton" name="mPointOffsetRadioRight">
<property name="text">
<string>Right</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="QRadioButton" name="mPointOffsetRadioAboveRight">
<property name="text">
<string>Above Right</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="mPointOffsetRadioAboveLeft">
<property name="text">
<string>Above Left</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="mPointOffsetRadioOver">
<property name="text">
<string>Over</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QRadioButton" name="mPointOffsetRadioAbove">
<property name="text">
<string>Above</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="mPointOffsetRadioLeft">
<property name="text">
<string>Left</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="mPointOffsetRadioBelowLeft">
<property name="text">
<string>Below Left</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QRadioButton" name="mPointOffsetRadioBelow">
<property name="text">
<string>Below</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QRadioButton" name="mPointOffsetRadioBelowRight">
<property name="text">
<string>Below Right</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QDoubleSpinBox" name="mPointOffsetAngleSpinBox">
<property name="enabled">
<bool>true</bool>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="minimum">
<double>-360.000000000000000</double>
</property>
<property name="maximum">
<double>360.000000000000000</double>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
Expand Down