[labeling] Allow different obstacle geometry to feature geometry

This change makes it possible to have a different geometry used
for labeling obstacle detection to the geometry used for generating
label position candidates.

Also fixes parts of multipolygon features were not treated as
obstacles when "label only largest part" of polygon was checked.
Some inefficiencies in pal were also fixed (eg avoiding adding
features/obstacles to pal rtree indexes when they will never
be used).

Sponsored by City of Uster
This commit is contained in:
Nyall Dawson 2015-11-21 21:18:30 +11:00
parent 47e6d3031b
commit 9cc10e2a21
13 changed files with 456 additions and 55 deletions

View File

@ -74,6 +74,16 @@ namespace pal
}
FeaturePart::FeaturePart( const FeaturePart& other )
: PointSet( other )
, mLF( other.mLF )
{
Q_FOREACH ( FeaturePart* part, other.mHoles )
{
mHoles << new FeaturePart( *part );
}
}
FeaturePart::~FeaturePart()
{

View File

@ -94,6 +94,8 @@ namespace pal
*/
FeaturePart( QgsLabelFeature* lf, const GEOSGeometry* geom );
FeaturePart( const FeaturePart& other );
/** Delete the feature
*/
virtual ~FeaturePart();

View File

@ -236,7 +236,7 @@ namespace pal
} PruneCtx;
/** Check whether the candidate in ctx overlap with obstacle feat */
static bool pruneCallback( LabelPosition *lp, void *ctx );
static bool pruneCallback( LabelPosition *candidatePosition, void *ctx );
// for sorting
static bool costShrink( void *l, void *r );

View File

@ -61,7 +61,8 @@ namespace pal
, mMergeLines( false )
, mUpsidedownLabels( Upright )
{
rtree = new RTree<FeaturePart*, double, 2, double>();
mFeatureIndex = new RTree<FeaturePart*, double, 2, double>();
mObstacleIndex = new RTree<FeaturePart*, double, 2, double>();
if ( defaultPriority < 0.0001 )
mDefaultPriority = 0.0001;
@ -76,13 +77,13 @@ namespace pal
mMutex.lock();
qDeleteAll( mFeatureParts );
mFeatureParts.clear();
qDeleteAll( mObstacleParts );
//should already be empty
qDeleteAll( mConnectedHashtable );
mConnectedHashtable.clear();
delete rtree;
delete mFeatureIndex;
delete mObstacleIndex;
mMutex.unlock();
}
@ -132,6 +133,8 @@ namespace pal
GEOSContextHandle_t geosctxt = geosContext();
bool featureGeomIsObstacleGeom = !lf->obstacleGeometry();
while ( simpleGeometries->size() > 0 )
{
const GEOSGeometry* geom = simpleGeometries->takeFirst();
@ -168,6 +171,32 @@ namespace pal
continue;
}
// is the feature well defined? TODO Check epsilon
bool labelWellDefined = ( lf->size().width() > 0.0000001 && lf->size().height() > 0.0000001 );
if ( lf->isObstacle() && featureGeomIsObstacleGeom )
{
//if we are not labelling the layer, only insert it into the obstacle list and avoid an
//unnecessary copy
if ( mLabelLayer && labelWellDefined )
{
addObstaclePart( new FeaturePart( *fpart ) );
}
else
{
addObstaclePart( fpart );
fpart = 0;
}
}
// feature has to be labeled?
if ( !mLabelLayer || !labelWellDefined )
{
//nothing more to do for this part
delete fpart;
continue;
}
if ( mMode == LabelPerFeature && ( type == GEOS_POLYGON || type == GEOS_LINESTRING ) )
{
if ( type == GEOS_LINESTRING )
@ -186,7 +215,6 @@ namespace pal
delete fpart;
}
continue; // don't add the feature part now, do it later
// TODO: we should probably add also other parts to act just as obstacles
}
// feature part is ready!
@ -195,6 +223,57 @@ namespace pal
}
delete simpleGeometries;
if ( !featureGeomIsObstacleGeom )
{
//do the same for the obstacle geometry
simpleGeometries = unmulti( lf->obstacleGeometry() );
if ( simpleGeometries == NULL ) // unmulti() failed?
{
mMutex.unlock();
throw InternalException::UnknownGeometry();
}
while ( simpleGeometries->size() > 0 )
{
const GEOSGeometry* geom = simpleGeometries->takeFirst();
// ignore invalid geometries (e.g. polygons with self-intersecting rings)
if ( GEOSisValid_r( geosctxt, geom ) != 1 ) // 0=invalid, 1=valid, 2=exception
{
continue;
}
int type = GEOSGeomTypeId_r( geosctxt, geom );
if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
{
mMutex.unlock();
throw InternalException::UnknownGeometry();
}
FeaturePart* fpart = new FeaturePart( lf, geom );
// ignore invalid geometries
if (( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
{
delete fpart;
continue;
}
// polygons: reorder coordinates
if ( type == GEOS_POLYGON && reorderPolygon( fpart->nbPoints, fpart->x, fpart->y ) != 0 )
{
delete fpart;
continue;
}
// feature part is ready!
addObstaclePart( fpart );
}
delete simpleGeometries;
}
mMutex.unlock();
// if using only biggest parts...
@ -224,7 +303,7 @@ namespace pal
mFeatureParts << fpart;
// add to r-tree for fast spatial access
rtree->Insert( bmin, bmax, fpart );
mFeatureIndex->Insert( bmin, bmax, fpart );
// add to hashtable with equally named feature parts
if ( mMergeLines && !labelText.isEmpty() )
@ -245,6 +324,19 @@ namespace pal
}
}
void Layer::addObstaclePart( FeaturePart* fpart )
{
double bmin[2];
double bmax[2];
fpart->getBoundingBox( bmin, bmax );
// add to list of layer's feature parts
mObstacleParts.append( fpart );
// add to obstacle r-tree
mObstacleIndex->Insert( bmin, bmax, fpart );
}
static FeaturePart* _findConnectedPart( FeaturePart* partCheck, QLinkedList<FeaturePart*>* otherParts )
{
// iterate in the rest of the parts with the same label
@ -286,7 +378,7 @@ namespace pal
// remove partCheck from r-tree
double bmin[2], bmax[2];
partCheck->getBoundingBox( bmin, bmax );
rtree->Remove( bmin, bmax, partCheck );
mFeatureIndex->Remove( bmin, bmax, partCheck );
mFeatureParts.removeOne( partCheck );
otherPart->getBoundingBox( bmin, bmax );
@ -295,9 +387,9 @@ namespace pal
if ( otherPart->mergeWithFeaturePart( partCheck ) )
{
// reinsert p->item to r-tree (probably not needed)
rtree->Remove( bmin, bmax, otherPart );
mFeatureIndex->Remove( bmin, bmax, otherPart );
otherPart->getBoundingBox( bmin, bmax );
rtree->Insert( bmin, bmax, otherPart );
mFeatureIndex->Insert( bmin, bmax, otherPart );
}
delete partCheck;
}
@ -331,7 +423,7 @@ namespace pal
double bmin[2], bmax[2];
fpart->getBoundingBox( bmin, bmax );
rtree->Remove( bmin, bmax, fpart );
mFeatureIndex->Remove( bmin, bmax, fpart );
const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom );
@ -387,7 +479,7 @@ namespace pal
FeaturePart* newfpart = new FeaturePart( fpart->feature(), newgeom );
newFeatureParts.append( newfpart );
newfpart->getBoundingBox( bmin, bmax );
rtree->Insert( bmin, bmax, newfpart );
mFeatureIndex->Insert( bmin, bmax, newfpart );
part.clear();
part.push_back( p );
}
@ -404,7 +496,7 @@ namespace pal
FeaturePart* newfpart = new FeaturePart( fpart->feature(), newgeom );
newFeatureParts.append( newfpart );
newfpart->getBoundingBox( bmin, bmax );
rtree->Insert( bmin, bmax, newfpart );
mFeatureIndex->Insert( bmin, bmax, newfpart );
delete fpart;
}
else

View File

@ -249,6 +249,9 @@ namespace pal
/** List of feature parts */
QLinkedList<FeaturePart*> mFeatureParts;
/** List of obstacle parts */
QList<FeaturePart*> mObstacleParts;
Pal *pal;
double mDefaultPriority;
@ -269,10 +272,13 @@ namespace pal
UpsideDownLabels mUpsidedownLabels;
// indexes (spatial and id)
RTree<FeaturePart*, double, 2, double, 8, 4> *rtree;
RTree<FeaturePart*, double, 2, double, 8, 4> *mFeatureIndex;
//! Lookup table of label features (owned by the label feature provider that created them)
QHash< QgsFeatureId, QgsLabelFeature*> mHashtable;
//obstacle r-tree
RTree<FeaturePart*, double, 2, double, 8, 4> *mObstacleIndex;
QHash< QString, QLinkedList<FeaturePart*>* > mConnectedHashtable;
QStringList mConnectedTexts;
@ -296,6 +302,9 @@ namespace pal
/** Add newly created feature part into r tree and to the list */
void addFeaturePart( FeaturePart* fpart, const QString &labelText = QString() );
/** Add newly created obstacle part into r tree and to the list */
void addObstaclePart( FeaturePart* fpart );
};
} // end namespace pal

View File

@ -151,32 +151,8 @@ namespace pal
bool extractFeatCallback( FeaturePart *ft_ptr, void *ctx )
{
double amin[2], amax[2];
FeatCallBackCtx *context = ( FeatCallBackCtx* ) ctx;
#ifdef _DEBUG_FULL_
std::cout << "extract feat : " << ft_ptr->getLayer()->getName() << "/" << ft_ptr->getUID() << std::endl;
#endif
// all feature which are obstacle will be inserted into obstacles
if ( ft_ptr->isObstacle() )
{
ft_ptr->getBoundingBox( amin, amax );
context->obstacles->Insert( amin, amax, ft_ptr );
}
// first do some checks whether to extract candidates or not
// feature has to be labeled?
if ( !context->layer->labelLayer() )
return true;
// is the feature well defined? TODO Check epsilon
if ( ft_ptr->getLabelWidth() < 0.0000001 || ft_ptr->getLabelHeight() < 0.0000001 )
return true;
// OK, everything's fine, let's process the feature part
// Holes of the feature are obstacles
for ( int i = 0; i < ft_ptr->getNumSelfObstacles(); i++ )
{
@ -210,8 +186,28 @@ namespace pal
return true;
}
typedef struct _obstaclebackCtx
{
RTree<FeaturePart*, double, 2, double> *obstacles;
int obstacleCount;
} ObstacleCallBackCtx;
/*
* Callback function
*
* Extract obstacles from indexes
*/
bool extractObstaclesCallback( FeaturePart *ft_ptr, void *ctx )
{
double amin[2], amax[2];
ObstacleCallBackCtx *context = ( ObstacleCallBackCtx* ) ctx;
// insert into obstacles
ft_ptr->getBoundingBox( amin, amax );
context->obstacles->Insert( amin, amax, ft_ptr );
context->obstacleCount++;
return true;
}
typedef struct _filterContext
{
@ -267,20 +263,23 @@ namespace pal
QLinkedList<Feats*> *fFeats = new QLinkedList<Feats*>;
FeatCallBackCtx *context = new FeatCallBackCtx();
context->fFeats = fFeats;
context->obstacles = obstacles;
context->candidates = prob->candidates;
FeatCallBackCtx context;
context.fFeats = fFeats;
context.obstacles = obstacles;
context.candidates = prob->candidates;
context.bbox_min[0] = amin[0];
context.bbox_min[1] = amin[1];
context.bbox_max[0] = amax[0];
context.bbox_max[1] = amax[1];
context->bbox_min[0] = amin[0];
context->bbox_min[1] = amin[1];
context->bbox_max[0] = amax[0];
context->bbox_max[1] = amax[1];
ObstacleCallBackCtx obstacleContext;
obstacleContext.obstacles = obstacles;
obstacleContext.obstacleCount = 0;
// first step : extract features from layers
int previousFeatureCount = 0;
int previousObstacleCount = 0;
QStringList layersWithFeaturesInBBox;
@ -303,19 +302,23 @@ namespace pal
layer->chopFeaturesAtRepeatDistance();
// find features within bounding box and generate candidates list
context->layer = layer;
context->layer->mMutex.lock();
context->layer->rtree->Search( amin, amax, extractFeatCallback, ( void* ) context );
context->layer->mMutex.unlock();
layer->mMutex.lock();
if ( context->fFeats->size() - previousFeatureCount > 0 )
// find features within bounding box and generate candidates list
context.layer = layer;
layer->mFeatureIndex->Search( amin, amax, extractFeatCallback, ( void* ) &context );
// find obstacles within bounding box
layer->mObstacleIndex->Search( amin, amax, extractObstaclesCallback, ( void* ) &obstacleContext );
layer->mMutex.unlock();
if ( context.fFeats->size() - previousFeatureCount > 0 || obstacleContext.obstacleCount > previousObstacleCount )
{
layersWithFeaturesInBBox << layer->name();
}
previousFeatureCount = context->fFeats->size();
previousFeatureCount = context.fFeats->size();
previousObstacleCount = obstacleContext.obstacleCount;
}
delete context;
mMutex.unlock();
prob->nbLabelledLayers = layersWithFeaturesInBBox.size();

View File

@ -349,6 +349,7 @@ QgsLabelFeature::QgsLabelFeature( QgsFeatureId id, GEOSGeometry* geometry, const
: mLayer( 0 )
, mId( id )
, mGeometry( geometry )
, mObstacleGeometry( 0 )
, mSize( size )
, mPriority( -1 )
, mHasFixedPosition( false )
@ -369,9 +370,20 @@ QgsLabelFeature::~QgsLabelFeature()
if ( mGeometry )
GEOSGeom_destroy_r( QgsGeometry::getGEOSHandler(), mGeometry );
if ( mObstacleGeometry )
GEOSGeom_destroy_r( QgsGeometry::getGEOSHandler(), mObstacleGeometry );
delete mInfo;
}
void QgsLabelFeature::setObstacleGeometry( GEOSGeometry* obstacleGeom )
{
if ( mObstacleGeometry )
GEOSGeom_destroy_r( QgsGeometry::getGEOSHandler(), mObstacleGeometry );
mObstacleGeometry = obstacleGeom;
}
QgsAbstractLabelProvider*QgsLabelFeature::provider() const
{
return mLayer ? mLayer->provider() : 0;

View File

@ -62,6 +62,22 @@ class CORE_EXPORT QgsLabelFeature
//! Get access to the associated geometry
GEOSGeometry* geometry() const { return mGeometry; }
/** Sets the label's obstacle geometry, if different to the feature geometry.
* This can be used to override the shape of the feature for obstacle detection, eg to
* buffer around a point geometry to prevent labels being placed too close to the
* point itself. It not set, the feature's geometry is used for obstacle detection.
* Ownership of obstacle geometry is transferred.
* @note added in QGIS 2.14
* @see obstacleGeometry()
*/
void setObstacleGeometry( GEOSGeometry* obstacleGeom );
/** Returns the label's obstacle geometry, if different to the feature geometry.
* @note added in QGIS 2.14
* @see setObstacleGeometry()
*/
GEOSGeometry* obstacleGeometry() const { return mObstacleGeometry; }
//! Size of the label (in map units)
QSizeF size() const { return mSize; }
@ -196,6 +212,8 @@ class CORE_EXPORT QgsLabelFeature
QgsFeatureId mId;
//! Geometry of the feature to be labelled
GEOSGeometry* mGeometry;
//! Optional geometry to use for label obstacles, if different to mGeometry
GEOSGeometry* mObstacleGeometry;
//! Width and height of the label
QSizeF mSize;
//! Priority of the label

View File

@ -176,6 +176,16 @@ class TestPointPlacement(TestPlacementBase):
self.removeMapLayer(self.layer)
self.layer = None
def test_multipolygon_obstacle(self):
# Test that all parts of multipolygon are used as an obstacle
self.layer = TestQgsPalLabeling.loadFeatureLayer('point')
polyLayer = TestQgsPalLabeling.loadFeatureLayer('multi_polygon')
self._TestMapSettings = self.cloneMapSettings(self._MapSettings)
self.checkTest()
self.removeMapLayer(self.layer)
self.removeMapLayer(polyLayer)
self.layer = None
if __name__ == '__main__':
# NOTE: unless PAL_SUITE env var is set all test class methods will be run
# SEE: test_qgspallabeling_tests.suiteTests() to define suite

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

View File

@ -0,0 +1,245 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="2.13.0-Master" minimumScale="-4.65661e-10" maximumScale="1e+08" simplifyDrawingHints="1" minLabelScale="0" maxLabelScale="1e+08" simplifyDrawingTol="1" simplifyMaxScale="1" hasScaleBasedVisibilityFlag="0" simplifyLocal="1" scaleBasedLabelVisibilityFlag="0">
<edittypes>
<edittype widgetv2type="TextEdit" name="pkuid">
<widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
</edittype>
<edittype widgetv2type="TextEdit" name="ftype">
<widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
</edittype>
<edittype widgetv2type="TextEdit" name="text">
<widgetv2config IsMultiline="0" fieldEditable="1" UseHtml="0" labelOnTop="0"/>
</edittype>
</edittypes>
<renderer-v2 forceraster="0" symbollevels="0" type="singleSymbol">
<symbols>
<symbol alpha="1" clip_to_extent="1" type="fill" name="0">
<layer pass="0" class="SimpleFill" locked="0">
<prop k="border_width_map_unit_scale" v="0,0,0,0,0,0"/>
<prop k="color" v="133,149,161,255"/>
<prop k="joinstyle" v="bevel"/>
<prop k="offset" v="0,0"/>
<prop k="offset_map_unit_scale" v="0,0,0,0,0,0"/>
<prop k="offset_unit" v="MM"/>
<prop k="outline_color" v="0,0,0,255"/>
<prop k="outline_style" v="no"/>
<prop k="outline_width" v="0.26"/>
<prop k="outline_width_unit" v="MM"/>
<prop k="style" v="solid"/>
</layer>
</symbol>
</symbols>
<rotation/>
<sizescale scalemethod="diameter"/>
</renderer-v2>
<labeling type="simple"/>
<customproperties>
<property key="labeling" value="pal"/>
<property key="labeling/addDirectionSymbol" value="false"/>
<property key="labeling/angleOffset" value="0"/>
<property key="labeling/blendMode" value="0"/>
<property key="labeling/bufferBlendMode" value="0"/>
<property key="labeling/bufferColorA" value="255"/>
<property key="labeling/bufferColorB" value="255"/>
<property key="labeling/bufferColorG" value="255"/>
<property key="labeling/bufferColorR" value="255"/>
<property key="labeling/bufferDraw" value="false"/>
<property key="labeling/bufferJoinStyle" value="64"/>
<property key="labeling/bufferNoFill" value="false"/>
<property key="labeling/bufferSize" value="1"/>
<property key="labeling/bufferSizeInMapUnits" value="false"/>
<property key="labeling/bufferSizeMapUnitMaxScale" value="0"/>
<property key="labeling/bufferSizeMapUnitMinScale" value="0"/>
<property key="labeling/bufferTransp" value="0"/>
<property key="labeling/centroidInside" value="false"/>
<property key="labeling/centroidWhole" value="false"/>
<property key="labeling/decimals" value="3"/>
<property key="labeling/displayAll" value="false"/>
<property key="labeling/dist" value="0"/>
<property key="labeling/distInMapUnits" value="false"/>
<property key="labeling/distMapUnitMaxScale" value="0"/>
<property key="labeling/distMapUnitMinScale" value="0"/>
<property key="labeling/drawLabels" value="true"/>
<property key="labeling/enabled" value="true"/>
<property key="labeling/fieldName" value="text"/>
<property key="labeling/fitInPolygonOnly" value="false"/>
<property key="labeling/fontCapitals" value="0"/>
<property key="labeling/fontFamily" value="Ubuntu"/>
<property key="labeling/fontItalic" value="true"/>
<property key="labeling/fontLetterSpacing" value="0"/>
<property key="labeling/fontLimitPixelSize" value="false"/>
<property key="labeling/fontMaxPixelSize" value="10000"/>
<property key="labeling/fontMinPixelSize" value="3"/>
<property key="labeling/fontSize" value="11"/>
<property key="labeling/fontSizeInMapUnits" value="false"/>
<property key="labeling/fontSizeMapUnitMaxScale" value="0"/>
<property key="labeling/fontSizeMapUnitMinScale" value="0"/>
<property key="labeling/fontStrikeout" value="false"/>
<property key="labeling/fontUnderline" value="false"/>
<property key="labeling/fontWeight" value="63"/>
<property key="labeling/fontWordSpacing" value="0"/>
<property key="labeling/formatNumbers" value="false"/>
<property key="labeling/isExpression" value="false"/>
<property key="labeling/labelOffsetInMapUnits" value="true"/>
<property key="labeling/labelOffsetMapUnitMaxScale" value="0"/>
<property key="labeling/labelOffsetMapUnitMinScale" value="0"/>
<property key="labeling/labelPerPart" value="false"/>
<property key="labeling/leftDirectionSymbol" value="&lt;"/>
<property key="labeling/limitNumLabels" value="false"/>
<property key="labeling/maxCurvedCharAngleIn" value="20"/>
<property key="labeling/maxCurvedCharAngleOut" value="-20"/>
<property key="labeling/maxNumLabels" value="2000"/>
<property key="labeling/mergeLines" value="false"/>
<property key="labeling/minFeatureSize" value="0"/>
<property key="labeling/multilineAlign" value="0"/>
<property key="labeling/multilineHeight" value="1"/>
<property key="labeling/namedStyle" value="Medium Italic"/>
<property key="labeling/obstacle" value="true"/>
<property key="labeling/obstacleFactor" value="1"/>
<property key="labeling/obstacleType" value="0"/>
<property key="labeling/placeDirectionSymbol" value="0"/>
<property key="labeling/placement" value="1"/>
<property key="labeling/placementFlags" value="10"/>
<property key="labeling/plussign" value="false"/>
<property key="labeling/preserveRotation" value="true"/>
<property key="labeling/previewBkgrdColor" value="#ffffff"/>
<property key="labeling/priority" value="5"/>
<property key="labeling/quadOffset" value="4"/>
<property key="labeling/repeatDistance" value="0"/>
<property key="labeling/repeatDistanceMapUnitMaxScale" value="0"/>
<property key="labeling/repeatDistanceMapUnitMinScale" value="0"/>
<property key="labeling/repeatDistanceUnit" value="1"/>
<property key="labeling/reverseDirectionSymbol" value="false"/>
<property key="labeling/rightDirectionSymbol" value=">"/>
<property key="labeling/scaleMax" value="10000000"/>
<property key="labeling/scaleMin" value="1"/>
<property key="labeling/scaleVisibility" value="false"/>
<property key="labeling/shadowBlendMode" value="6"/>
<property key="labeling/shadowColorB" value="0"/>
<property key="labeling/shadowColorG" value="0"/>
<property key="labeling/shadowColorR" value="0"/>
<property key="labeling/shadowDraw" value="false"/>
<property key="labeling/shadowOffsetAngle" value="135"/>
<property key="labeling/shadowOffsetDist" value="1"/>
<property key="labeling/shadowOffsetGlobal" value="true"/>
<property key="labeling/shadowOffsetMapUnitMaxScale" value="0"/>
<property key="labeling/shadowOffsetMapUnitMinScale" value="0"/>
<property key="labeling/shadowOffsetUnits" value="1"/>
<property key="labeling/shadowRadius" value="1.5"/>
<property key="labeling/shadowRadiusAlphaOnly" value="false"/>
<property key="labeling/shadowRadiusMapUnitMaxScale" value="0"/>
<property key="labeling/shadowRadiusMapUnitMinScale" value="0"/>
<property key="labeling/shadowRadiusUnits" value="1"/>
<property key="labeling/shadowScale" value="100"/>
<property key="labeling/shadowTransparency" value="30"/>
<property key="labeling/shadowUnder" value="0"/>
<property key="labeling/shapeBlendMode" value="0"/>
<property key="labeling/shapeBorderColorA" value="255"/>
<property key="labeling/shapeBorderColorB" value="128"/>
<property key="labeling/shapeBorderColorG" value="128"/>
<property key="labeling/shapeBorderColorR" value="128"/>
<property key="labeling/shapeBorderWidth" value="0"/>
<property key="labeling/shapeBorderWidthMapUnitMaxScale" value="0"/>
<property key="labeling/shapeBorderWidthMapUnitMinScale" value="0"/>
<property key="labeling/shapeBorderWidthUnits" value="1"/>
<property key="labeling/shapeDraw" value="false"/>
<property key="labeling/shapeFillColorA" value="255"/>
<property key="labeling/shapeFillColorB" value="255"/>
<property key="labeling/shapeFillColorG" value="255"/>
<property key="labeling/shapeFillColorR" value="255"/>
<property key="labeling/shapeJoinStyle" value="64"/>
<property key="labeling/shapeOffsetMapUnitMaxScale" value="0"/>
<property key="labeling/shapeOffsetMapUnitMinScale" value="0"/>
<property key="labeling/shapeOffsetUnits" value="1"/>
<property key="labeling/shapeOffsetX" value="0"/>
<property key="labeling/shapeOffsetY" value="0"/>
<property key="labeling/shapeRadiiMapUnitMaxScale" value="0"/>
<property key="labeling/shapeRadiiMapUnitMinScale" value="0"/>
<property key="labeling/shapeRadiiUnits" value="1"/>
<property key="labeling/shapeRadiiX" value="0"/>
<property key="labeling/shapeRadiiY" value="0"/>
<property key="labeling/shapeRotation" value="0"/>
<property key="labeling/shapeRotationType" value="0"/>
<property key="labeling/shapeSVGFile" value=""/>
<property key="labeling/shapeSizeMapUnitMaxScale" value="0"/>
<property key="labeling/shapeSizeMapUnitMinScale" value="0"/>
<property key="labeling/shapeSizeType" value="0"/>
<property key="labeling/shapeSizeUnits" value="1"/>
<property key="labeling/shapeSizeX" value="0"/>
<property key="labeling/shapeSizeY" value="0"/>
<property key="labeling/shapeTransparency" value="0"/>
<property key="labeling/shapeType" value="0"/>
<property key="labeling/textColorA" value="255"/>
<property key="labeling/textColorB" value="0"/>
<property key="labeling/textColorG" value="0"/>
<property key="labeling/textColorR" value="0"/>
<property key="labeling/textTransp" value="0"/>
<property key="labeling/upsidedownLabels" value="0"/>
<property key="labeling/wrapChar" value=""/>
<property key="labeling/xOffset" value="0"/>
<property key="labeling/yOffset" value="0"/>
<property key="variableNames" value="_fields_"/>
<property key="variableValues" value=""/>
</customproperties>
<blendMode>0</blendMode>
<featureBlendMode>0</featureBlendMode>
<layerTransparency>0</layerTransparency>
<displayfield>pkuid</displayfield>
<label>0</label>
<labelattributes>
<label fieldname="" text="Label"/>
<family fieldname="" name="Ubuntu"/>
<size fieldname="" units="pt" value="12"/>
<bold fieldname="" on="0"/>
<italic fieldname="" on="0"/>
<underline fieldname="" on="0"/>
<strikeout fieldname="" on="0"/>
<color fieldname="" red="0" blue="0" green="0"/>
<x fieldname=""/>
<y fieldname=""/>
<offset x="0" y="0" units="pt" yfieldname="" xfieldname=""/>
<angle fieldname="" value="0" auto="0"/>
<alignment fieldname="" value="center"/>
<buffercolor fieldname="" red="255" blue="255" green="255"/>
<buffersize fieldname="" units="pt" value="1"/>
<bufferenabled fieldname="" on=""/>
<multilineenabled fieldname="" on=""/>
<selectedonly on=""/>
</labelattributes>
<SingleCategoryDiagramRenderer diagramType="Pie">
<DiagramCategory penColor="#000000" labelPlacementMethod="XHeight" penWidth="0" diagramOrientation="Up" minimumSize="0" barWidth="5" penAlpha="255" maxScaleDenominator="1e+08" backgroundColor="#ffffff" transparency="0" width="15" scaleDependency="Area" backgroundAlpha="255" angleOffset="1440" scaleBasedVisibility="0" enabled="0" height="15" sizeType="MM" minScaleDenominator="-4.65661e-10">
<fontProperties description="Ubuntu,11,-1,5,50,0,0,0,0,0" style=""/>
</DiagramCategory>
</SingleCategoryDiagramRenderer>
<DiagramLayerSettings yPosColumn="-1" linePlacementFlags="10" placement="0" dist="0" xPosColumn="-1" priority="0" obstacle="0" showAll="1"/>
<editform></editform>
<editforminit/>
<editforminitusecode>0</editforminitusecode>
<editforminitcode><![CDATA[# -*- coding: utf-8 -*-
"""
QGIS forms can have a Python function that is called when the form is
opened.
Use this function to add extra logic to your forms.
Enter the name of the function in the "Python Init function"
field.
An example follows:
"""
from PyQt4.QtGui import QWidget
def my_form_open(dialog, layer, feature):
geom = feature.geometry()
control = dialog.findChild(QWidget, "MyLineEdit")
]]></editforminitcode>
<featformsuppress>0</featformsuppress>
<annotationform></annotationform>
<editorlayout>generatedlayout</editorlayout>
<excludeAttributesWMS/>
<excludeAttributesWFS/>
<attributeactions/>
<conditionalstyles>
<rowstyles/>
<fieldstyles/>
</conditionalstyles>
</qgis>

Binary file not shown.