[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() FeaturePart::~FeaturePart()
{ {

View File

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

View File

@ -236,7 +236,7 @@ namespace pal
} PruneCtx; } PruneCtx;
/** Check whether the candidate in ctx overlap with obstacle feat */ /** 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 // for sorting
static bool costShrink( void *l, void *r ); static bool costShrink( void *l, void *r );

View File

@ -61,7 +61,8 @@ namespace pal
, mMergeLines( false ) , mMergeLines( false )
, mUpsidedownLabels( Upright ) , 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 ) if ( defaultPriority < 0.0001 )
mDefaultPriority = 0.0001; mDefaultPriority = 0.0001;
@ -76,13 +77,13 @@ namespace pal
mMutex.lock(); mMutex.lock();
qDeleteAll( mFeatureParts ); qDeleteAll( mFeatureParts );
mFeatureParts.clear(); qDeleteAll( mObstacleParts );
//should already be empty //should already be empty
qDeleteAll( mConnectedHashtable ); qDeleteAll( mConnectedHashtable );
mConnectedHashtable.clear();
delete rtree; delete mFeatureIndex;
delete mObstacleIndex;
mMutex.unlock(); mMutex.unlock();
} }
@ -132,6 +133,8 @@ namespace pal
GEOSContextHandle_t geosctxt = geosContext(); GEOSContextHandle_t geosctxt = geosContext();
bool featureGeomIsObstacleGeom = !lf->obstacleGeometry();
while ( simpleGeometries->size() > 0 ) while ( simpleGeometries->size() > 0 )
{ {
const GEOSGeometry* geom = simpleGeometries->takeFirst(); const GEOSGeometry* geom = simpleGeometries->takeFirst();
@ -168,6 +171,32 @@ namespace pal
continue; 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 ( mMode == LabelPerFeature && ( type == GEOS_POLYGON || type == GEOS_LINESTRING ) )
{ {
if ( type == GEOS_LINESTRING ) if ( type == GEOS_LINESTRING )
@ -186,7 +215,6 @@ namespace pal
delete fpart; delete fpart;
} }
continue; // don't add the feature part now, do it later 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! // feature part is ready!
@ -195,6 +223,57 @@ namespace pal
} }
delete simpleGeometries; 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(); mMutex.unlock();
// if using only biggest parts... // if using only biggest parts...
@ -224,7 +303,7 @@ namespace pal
mFeatureParts << fpart; mFeatureParts << fpart;
// add to r-tree for fast spatial access // 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 // add to hashtable with equally named feature parts
if ( mMergeLines && !labelText.isEmpty() ) 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 ) static FeaturePart* _findConnectedPart( FeaturePart* partCheck, QLinkedList<FeaturePart*>* otherParts )
{ {
// iterate in the rest of the parts with the same label // iterate in the rest of the parts with the same label
@ -286,7 +378,7 @@ namespace pal
// remove partCheck from r-tree // remove partCheck from r-tree
double bmin[2], bmax[2]; double bmin[2], bmax[2];
partCheck->getBoundingBox( bmin, bmax ); partCheck->getBoundingBox( bmin, bmax );
rtree->Remove( bmin, bmax, partCheck ); mFeatureIndex->Remove( bmin, bmax, partCheck );
mFeatureParts.removeOne( partCheck ); mFeatureParts.removeOne( partCheck );
otherPart->getBoundingBox( bmin, bmax ); otherPart->getBoundingBox( bmin, bmax );
@ -295,9 +387,9 @@ namespace pal
if ( otherPart->mergeWithFeaturePart( partCheck ) ) if ( otherPart->mergeWithFeaturePart( partCheck ) )
{ {
// reinsert p->item to r-tree (probably not needed) // reinsert p->item to r-tree (probably not needed)
rtree->Remove( bmin, bmax, otherPart ); mFeatureIndex->Remove( bmin, bmax, otherPart );
otherPart->getBoundingBox( bmin, bmax ); otherPart->getBoundingBox( bmin, bmax );
rtree->Insert( bmin, bmax, otherPart ); mFeatureIndex->Insert( bmin, bmax, otherPart );
} }
delete partCheck; delete partCheck;
} }
@ -331,7 +423,7 @@ namespace pal
double bmin[2], bmax[2]; double bmin[2], bmax[2];
fpart->getBoundingBox( bmin, bmax ); fpart->getBoundingBox( bmin, bmax );
rtree->Remove( bmin, bmax, fpart ); mFeatureIndex->Remove( bmin, bmax, fpart );
const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom ); const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom );
@ -387,7 +479,7 @@ namespace pal
FeaturePart* newfpart = new FeaturePart( fpart->feature(), newgeom ); FeaturePart* newfpart = new FeaturePart( fpart->feature(), newgeom );
newFeatureParts.append( newfpart ); newFeatureParts.append( newfpart );
newfpart->getBoundingBox( bmin, bmax ); newfpart->getBoundingBox( bmin, bmax );
rtree->Insert( bmin, bmax, newfpart ); mFeatureIndex->Insert( bmin, bmax, newfpart );
part.clear(); part.clear();
part.push_back( p ); part.push_back( p );
} }
@ -404,7 +496,7 @@ namespace pal
FeaturePart* newfpart = new FeaturePart( fpart->feature(), newgeom ); FeaturePart* newfpart = new FeaturePart( fpart->feature(), newgeom );
newFeatureParts.append( newfpart ); newFeatureParts.append( newfpart );
newfpart->getBoundingBox( bmin, bmax ); newfpart->getBoundingBox( bmin, bmax );
rtree->Insert( bmin, bmax, newfpart ); mFeatureIndex->Insert( bmin, bmax, newfpart );
delete fpart; delete fpart;
} }
else else

View File

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

View File

@ -151,32 +151,8 @@ namespace pal
bool extractFeatCallback( FeaturePart *ft_ptr, void *ctx ) bool extractFeatCallback( FeaturePart *ft_ptr, void *ctx )
{ {
double amin[2], amax[2]; double amin[2], amax[2];
FeatCallBackCtx *context = ( FeatCallBackCtx* ) ctx; 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 // Holes of the feature are obstacles
for ( int i = 0; i < ft_ptr->getNumSelfObstacles(); i++ ) for ( int i = 0; i < ft_ptr->getNumSelfObstacles(); i++ )
{ {
@ -210,8 +186,28 @@ namespace pal
return true; 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 typedef struct _filterContext
{ {
@ -267,20 +263,23 @@ namespace pal
QLinkedList<Feats*> *fFeats = new QLinkedList<Feats*>; QLinkedList<Feats*> *fFeats = new QLinkedList<Feats*>;
FeatCallBackCtx *context = new FeatCallBackCtx(); FeatCallBackCtx context;
context->fFeats = fFeats; context.fFeats = fFeats;
context->obstacles = obstacles; context.obstacles = obstacles;
context->candidates = prob->candidates; 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]; ObstacleCallBackCtx obstacleContext;
context->bbox_min[1] = amin[1]; obstacleContext.obstacles = obstacles;
obstacleContext.obstacleCount = 0;
context->bbox_max[0] = amax[0];
context->bbox_max[1] = amax[1];
// first step : extract features from layers // first step : extract features from layers
int previousFeatureCount = 0; int previousFeatureCount = 0;
int previousObstacleCount = 0;
QStringList layersWithFeaturesInBBox; QStringList layersWithFeaturesInBBox;
@ -303,19 +302,23 @@ namespace pal
layer->chopFeaturesAtRepeatDistance(); layer->chopFeaturesAtRepeatDistance();
// find features within bounding box and generate candidates list layer->mMutex.lock();
context->layer = layer;
context->layer->mMutex.lock();
context->layer->rtree->Search( amin, amax, extractFeatCallback, ( void* ) context );
context->layer->mMutex.unlock();
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(); layersWithFeaturesInBBox << layer->name();
} }
previousFeatureCount = context->fFeats->size(); previousFeatureCount = context.fFeats->size();
previousObstacleCount = obstacleContext.obstacleCount;
} }
delete context;
mMutex.unlock(); mMutex.unlock();
prob->nbLabelledLayers = layersWithFeaturesInBBox.size(); prob->nbLabelledLayers = layersWithFeaturesInBBox.size();

View File

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

View File

@ -62,6 +62,22 @@ class CORE_EXPORT QgsLabelFeature
//! Get access to the associated geometry //! Get access to the associated geometry
GEOSGeometry* geometry() const { return mGeometry; } 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) //! Size of the label (in map units)
QSizeF size() const { return mSize; } QSizeF size() const { return mSize; }
@ -196,6 +212,8 @@ class CORE_EXPORT QgsLabelFeature
QgsFeatureId mId; QgsFeatureId mId;
//! Geometry of the feature to be labelled //! Geometry of the feature to be labelled
GEOSGeometry* mGeometry; GEOSGeometry* mGeometry;
//! Optional geometry to use for label obstacles, if different to mGeometry
GEOSGeometry* mObstacleGeometry;
//! Width and height of the label //! Width and height of the label
QSizeF mSize; QSizeF mSize;
//! Priority of the label //! Priority of the label

View File

@ -176,6 +176,16 @@ class TestPointPlacement(TestPlacementBase):
self.removeMapLayer(self.layer) self.removeMapLayer(self.layer)
self.layer = None 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__': if __name__ == '__main__':
# NOTE: unless PAL_SUITE env var is set all test class methods will be run # NOTE: unless PAL_SUITE env var is set all test class methods will be run
# SEE: test_qgspallabeling_tests.suiteTests() to define suite # 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.