QGIS API Documentation  2.8.2-Wien
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
layer.cpp
Go to the documentation of this file.
1 /*
2  * libpal - Automated Placement of Labels Library
3  *
4  * Copyright (C) 2008 Maxence Laurent, MIS-TIC, HEIG-VD
5  * University of Applied Sciences, Western Switzerland
6  * http://www.hes-so.ch
7  *
8  * Contact:
9  * maxence.laurent <at> heig-vd <dot> ch
10  * or
11  * eric.taillard <at> heig-vd <dot> ch
12  *
13  * This file is part of libpal.
14  *
15  * libpal is free software: you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation, either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * libpal is distributed in the hope that it will be useful,
21  * but WITHOUT ANY WARRANTY; without even the implied warranty of
22  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23  * GNU General Public License for more details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with libpal. If not, see <http://www.gnu.org/licenses/>.
27  *
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #define _CRT_SECURE_NO_DEPRECATE
35 
36 #include <stddef.h>
37 #include <geos_c.h>
38 
39 #include <iostream>
40 #include <cstring>
41 #include <cmath>
42 #include <vector>
43 
44 #include <pal/pal.h>
45 #include <pal/layer.h>
46 #include <pal/palexception.h>
47 #include <pal/internalexception.h>
48 
49 #include "linkedlist.hpp"
50 #include "hashtable.hpp"
51 
52 #include "feature.h"
53 #include "geomfunction.h"
54 #include "util.h"
55 
56 #include "simplemutex.h"
57 
58 namespace pal
59 {
60 
61  Layer::Layer( const char *lyrName, double min_scale, double max_scale, Arrangement arrangement, Units label_unit, double defaultPriority, bool obstacle, bool active, bool toLabel, Pal *pal, bool displayAll )
62  : pal( pal ), obstacle( obstacle ), active( active ),
63  toLabel( toLabel ), displayAll( displayAll ), centroidInside( false ), label_unit( label_unit ),
64  min_scale( min_scale ), max_scale( max_scale ),
65  arrangement( arrangement ), arrangementFlags( 0 ), mode( LabelPerFeature ), mergeLines( false ),
66  upsidedownLabels( Upright )
67  {
68 
69  this->name = new char[strlen( lyrName ) +1];
70  strcpy( this->name, lyrName );
71 
72  modMutex = new SimpleMutex();
73 
74  rtree = new RTree<FeaturePart*, double, 2, double>();
75  hashtable = new HashTable<Feature*> ( 5281 );
76 
77  connectedHashtable = new HashTable< LinkedList<FeaturePart*>* > ( 5391 );
78  connectedTexts = new LinkedList< char* >( strCompare );
79 
80  if ( defaultPriority < 0.0001 )
81  this->defaultPriority = 0.0001;
82  else if ( defaultPriority > 1.0 )
83  this->defaultPriority = 1.0;
84  else
85  this->defaultPriority = defaultPriority;
86 
87  featureParts = new LinkedList<FeaturePart*> ( ptrFeaturePartCompare );
88  features = new LinkedList<Feature*> ( ptrFeatureCompare );
89  }
90 
92  {
93  modMutex->lock();
94 
95  if ( featureParts )
96  {
97  while ( featureParts->size() )
98  {
99  delete featureParts->pop_front();
100  }
101  delete featureParts;
102 
103  }
104 
105  // this hashtable and list should be empty if they still exist
106  delete connectedHashtable;
107 
108  // features in the hashtable
109  if ( features )
110  {
111  while ( features->size() )
112  {
113  delete features->pop_front();
114  }
115  delete features;
116  }
117 
118  if ( name )
119  delete[] name;
120 
121  delete rtree;
122 
123  delete hashtable;
124  delete modMutex;
125  delete connectedTexts;
126  }
127 
128  Feature* Layer::getFeature( const char* geom_id )
129  {
130  Feature** fptr = hashtable->find( geom_id );
131  return ( fptr ? *fptr : NULL );
132  }
133 
134 
135  bool Layer::isScaleValid( double scale )
136  {
137  return ( scale >= min_scale || min_scale == -1 )
138  && ( scale <= max_scale || max_scale == -1 );
139  }
140 
141 
143  {
144  return features->size();
145  }
146 
147  const char *Layer::getName()
148  {
149  return name;
150  }
151 
153  {
154  return arrangement;
155  }
156 
157  void Layer::setArrangement( Arrangement arrangement )
158  {
159  this->arrangement = arrangement;
160  }
161 
162 
164  {
165  return obstacle;
166  }
167 
169  {
170  return toLabel;
171  }
172 
174  {
175  return active;
176  }
177 
178 
180  {
181  return min_scale;
182  }
183 
185  {
186  return max_scale;
187  }
188 
190  {
191  return defaultPriority;
192  }
193 
194  void Layer::setObstacle( bool obstacle )
195  {
196  this->obstacle = obstacle;
197  }
198 
199  void Layer::setActive( bool active )
200  {
201  this->active = active;
202  }
203 
204  void Layer::setToLabel( bool toLabel )
205  {
206  this->toLabel = toLabel;
207  }
208 
209  void Layer::setMinScale( double min_scale )
210  {
211  this->min_scale = min_scale;
212  }
213 
214  void Layer::setMaxScale( double max_scale )
215  {
216  this->max_scale = max_scale;
217  }
218 
219  void Layer::setPriority( double priority )
220  {
221  if ( priority >= 1.0 ) // low priority
222  defaultPriority = 1.0;
223  else if ( priority <= 0.0001 )
224  defaultPriority = 0.0001; // high priority
225  else
226  defaultPriority = priority;
227  }
228 
229 
230 
231  bool Layer::registerFeature( const char *geom_id, PalGeometry *userGeom, double label_x, double label_y, const char* labelText,
232  double labelPosX, double labelPosY, bool fixedPos, double angle, bool fixedAngle,
233  int xQuadOffset, int yQuadOffset, double xOffset, double yOffset, bool alwaysShow, double repeatDistance )
234  {
235  if ( !geom_id || label_x < 0 || label_y < 0 )
236  return false;
237 
238  modMutex->lock();
239 
240  if ( hashtable->find( geom_id ) )
241  {
242  modMutex->unlock();
243  //A feature with this id already exists. Don't throw an exception as sometimes,
244  //the same feature is added twice (dateline split with otf-reprojection)
245  return false;
246  }
247 
248  // Split MULTI GEOM and Collection in simple geometries
249  const GEOSGeometry *the_geom = userGeom->getGeosGeometry();
250 
251  Feature* f = new Feature( this, geom_id, userGeom, label_x, label_y );
252  if ( fixedPos )
253  {
254  f->setFixedPosition( labelPosX, labelPosY );
255  }
256  if ( xQuadOffset != 0 || yQuadOffset != 0 )
257  {
258  f->setQuadOffset( xQuadOffset, yQuadOffset );
259  }
260  if ( xOffset != 0.0 || yOffset != 0.0 )
261  {
262  f->setPosOffset( xOffset, yOffset );
263  }
264  if ( fixedAngle )
265  {
266  f->setFixedAngle( angle );
267  }
268  // use layer-level defined rotation, but not if position fixed
269  if ( !fixedPos && angle != 0.0 )
270  {
271  f->setFixedAngle( angle );
272  }
273  f->setRepeatDistance( repeatDistance );
274 
275  f->setAlwaysShow( alwaysShow );
276 
277  bool first_feat = true;
278 
279  double geom_size = -1, biggest_size = -1;
280  FeaturePart* biggest_part = NULL;
281 
282  // break the (possibly multi-part) geometry into simple geometries
283  LinkedList <const GEOSGeometry*> *simpleGeometries = unmulti( the_geom );
284  if ( simpleGeometries == NULL ) // unmulti() failed?
285  {
286  modMutex->unlock();
288  }
289 
290  GEOSContextHandle_t geosctxt = geosContext();
291 
292  while ( simpleGeometries->size() > 0 )
293  {
294  const GEOSGeometry* geom = simpleGeometries->pop_front();
295 
296  // ignore invalid geometries (e.g. polygons with self-intersecting rings)
297  if ( GEOSisValid_r( geosctxt, geom ) != 1 ) // 0=invalid, 1=valid, 2=exception
298  {
299  std::cerr << "ignoring invalid feature " << geom_id << std::endl;
300  continue;
301  }
302 
303  int type = GEOSGeomTypeId_r( geosctxt, geom );
304 
305  if ( type != GEOS_POINT && type != GEOS_LINESTRING && type != GEOS_POLYGON )
306  {
307  modMutex->unlock();
309  }
310 
311  FeaturePart* fpart = new FeaturePart( f, geom );
312 
313  // ignore invalid geometries
314  if (( type == GEOS_LINESTRING && fpart->nbPoints < 2 ) ||
315  ( type == GEOS_POLYGON && fpart->nbPoints < 3 ) )
316  {
317  delete fpart;
318  continue;
319  }
320 
321  // polygons: reorder coordinates
322  if ( type == GEOS_POLYGON && reorderPolygon( fpart->nbPoints, fpart->x, fpart->y ) != 0 )
323  {
324  delete fpart;
325  continue;
326  }
327 
328  if ( mode == LabelPerFeature && ( type == GEOS_POLYGON || type == GEOS_LINESTRING ) )
329  {
330  if ( type == GEOS_LINESTRING )
331  GEOSLength_r( geosctxt, geom, &geom_size );
332  else if ( type == GEOS_POLYGON )
333  GEOSArea_r( geosctxt, geom, &geom_size );
334 
335  if ( geom_size > biggest_size )
336  {
337  biggest_size = geom_size;
338  delete biggest_part; // safe with NULL part
339  biggest_part = fpart;
340  }
341  continue; // don't add the feature part now, do it later
342  // TODO: we should probably add also other parts to act just as obstacles
343  }
344 
345  // feature part is ready!
346  addFeaturePart( fpart, labelText );
347 
348  first_feat = false;
349  }
350  delete simpleGeometries;
351 
352  userGeom->releaseGeosGeometry( the_geom );
353 
354  modMutex->unlock();
355 
356  // if using only biggest parts...
357  if (( mode == LabelPerFeature || f->fixedPosition() ) && biggest_part != NULL )
358  {
359  addFeaturePart( biggest_part, labelText );
360  first_feat = false;
361  }
362 
363  // add feature to layer if we have added something
364  if ( !first_feat )
365  {
366  features->push_back( f );
367  hashtable->insertItem( geom_id, f );
368  }
369  else
370  {
371  delete f;
372  }
373 
374  return !first_feat; // true if we've added something
375  }
376 
377  void Layer::addFeaturePart( FeaturePart* fpart, const char* labelText )
378  {
379  double bmin[2];
380  double bmax[2];
381  fpart->getBoundingBox( bmin, bmax );
382 
383  // add to list of layer's feature parts
384  featureParts->push_back( fpart );
385 
386  // add to r-tree for fast spatial access
387  rtree->Insert( bmin, bmax, fpart );
388 
389  // add to hashtable with equally named feature parts
390  if ( mergeLines && labelText )
391  {
392  LinkedList< FeaturePart*>** lstPtr = connectedHashtable->find( labelText );
393  LinkedList< FeaturePart*>* lst;
394  if ( lstPtr == NULL )
395  {
396  // entry doesn't exist yet
397  lst = new LinkedList<FeaturePart*>( ptrFeaturePartCompare );
398  connectedHashtable->insertItem( labelText, lst );
399 
400  char* txt = new char[strlen( labelText ) +1];
401  strcpy( txt, labelText );
402  connectedTexts->push_back( txt );
403  }
404  else
405  {
406  lst = *lstPtr;
407  }
408  lst->push_back( fpart ); // add to the list
409  }
410  }
411 
412 
413  void Layer::setLabelUnit( Units label_unit )
414  {
415  if ( label_unit == PIXEL || label_unit == METER )
416  this->label_unit = label_unit;
417  }
418 
420  {
421  return label_unit;
422  }
423 
424 
425  static FeaturePart* _findConnectedPart( FeaturePart* partCheck, LinkedList<FeaturePart*>* otherParts )
426  {
427  // iterate in the rest of the parts with the same label
428  Cell<FeaturePart*>* p = otherParts->getFirst();
429  while ( p )
430  {
431  if ( partCheck->isConnected( p->item ) )
432  {
433  // stop checking for other connected parts
434  return p->item;
435  }
436  p = p->next;
437  }
438 
439  return NULL; // no connected part found...
440  }
441 
443  {
444  // go through all label texts
445  char* labelText;
446  while (( labelText = connectedTexts->pop_front() ) )
447  {
448  //std::cerr << "JOIN: " << labelText << std::endl;
449  LinkedList<FeaturePart*>** partsPtr = connectedHashtable->find( labelText );
450  if ( !partsPtr )
451  continue; // shouldn't happen
452  LinkedList<FeaturePart*>* parts = *partsPtr;
453 
454  // go one-by-one part, try to merge
455  while ( parts->size() )
456  {
457  // part we'll be checking against other in this round
458  FeaturePart* partCheck = parts->pop_front();
459 
460  FeaturePart* otherPart = _findConnectedPart( partCheck, parts );
461  if ( otherPart )
462  {
463  //std::cerr << "- connected " << partCheck << " with " << otherPart << std::endl;
464 
465  // remove partCheck from r-tree
466  double bmin[2], bmax[2];
467  partCheck->getBoundingBox( bmin, bmax );
468  rtree->Remove( bmin, bmax, partCheck );
469  featureParts->remove( partCheck );
470 
471  otherPart->getBoundingBox( bmin, bmax );
472 
473  // merge points from partCheck to p->item
474  if ( otherPart->mergeWithFeaturePart( partCheck ) )
475  {
476  // reinsert p->item to r-tree (probably not needed)
477  rtree->Remove( bmin, bmax, otherPart );
478  otherPart->getBoundingBox( bmin, bmax );
479  rtree->Insert( bmin, bmax, otherPart );
480  }
481  }
482  }
483 
484  // we're done processing feature parts with this particular label text
485  delete parts;
486  *partsPtr = NULL;
487  delete labelText;
488  }
489 
490  // we're done processing connected fetures
491  delete connectedHashtable;
492  connectedHashtable = NULL;
493  delete connectedTexts;
494  connectedTexts = NULL;
495  }
496 
498  {
499  GEOSContextHandle_t geosctxt = geosContext();
500  LinkedList<FeaturePart*> * newFeatureParts = new LinkedList<FeaturePart*>( ptrFeaturePartCompare );
501  while ( FeaturePart* fpart = featureParts->pop_front() )
502  {
503  const GEOSGeometry* geom = fpart->getGeometry();
504  double chopInterval = fpart->getFeature()->repeatDistance();
505  if ( chopInterval != 0. && GEOSGeomTypeId_r( geosctxt, geom ) == GEOS_LINESTRING )
506  {
507 
508  double bmin[2], bmax[2];
509  fpart->getBoundingBox( bmin, bmax );
510  rtree->Remove( bmin, bmax, fpart );
511 
512  const GEOSCoordSequence *cs = GEOSGeom_getCoordSeq_r( geosctxt, geom );
513 
514  // get number of points
515  unsigned int n;
516  GEOSCoordSeq_getSize_r( geosctxt, cs, &n );
517 
518  // Read points
519  std::vector<Point> points( n );
520  for ( unsigned int i = 0; i < n; ++i )
521  {
522  GEOSCoordSeq_getX_r( geosctxt, cs, i, &points[i].x );
523  GEOSCoordSeq_getY_r( geosctxt, cs, i, &points[i].y );
524  }
525 
526  // Cumulative length vector
527  std::vector<double> len( n, 0 );
528  for ( unsigned int i = 1; i < n; ++i )
529  {
530  double dx = points[i].x - points[i - 1].x;
531  double dy = points[i].y - points[i - 1].y;
532  len[i] = len[i - 1] + std::sqrt( dx * dx + dy * dy );
533  }
534 
535  // Walk along line
536  unsigned int cur = 0;
537  double lambda = 0;
538  std::vector<Point> part;
539  for ( ;; )
540  {
541  lambda += chopInterval;
542  for ( ; cur < n && lambda > len[cur]; ++cur )
543  {
544  part.push_back( points[cur] );
545  }
546  if ( cur >= n )
547  {
548  break;
549  }
550  double c = ( lambda - len[cur - 1] ) / ( len[cur] - len[cur - 1] );
551  Point p;
552  p.x = points[cur - 1].x + c * ( points[cur].x - points[cur - 1].x );
553  p.y = points[cur - 1].y + c * ( points[cur].y - points[cur - 1].y );
554  part.push_back( p );
555  GEOSCoordSequence* cooSeq = GEOSCoordSeq_create_r( geosctxt, part.size(), 2 );
556  for ( std::size_t i = 0; i < part.size(); ++i )
557  {
558  GEOSCoordSeq_setX_r( geosctxt, cooSeq, i, part[i].x );
559  GEOSCoordSeq_setY_r( geosctxt, cooSeq, i, part[i].y );
560  }
561 
562  GEOSGeometry* newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
563  FeaturePart* newfpart = new FeaturePart( fpart->getFeature(), newgeom );
564  newFeatureParts->push_back( newfpart );
565  newfpart->getBoundingBox( bmin, bmax );
566  rtree->Insert( bmin, bmax, newfpart );
567  part.clear();
568  part.push_back( p );
569  }
570  // Create final part
571  part.push_back( points[n - 1] );
572  GEOSCoordSequence* cooSeq = GEOSCoordSeq_create_r( geosctxt, part.size(), 2 );
573  for ( std::size_t i = 0; i < part.size(); ++i )
574  {
575  GEOSCoordSeq_setX_r( geosctxt, cooSeq, i, part[i].x );
576  GEOSCoordSeq_setY_r( geosctxt, cooSeq, i, part[i].y );
577  }
578 
579  GEOSGeometry* newgeom = GEOSGeom_createLineString_r( geosctxt, cooSeq );
580  FeaturePart* newfpart = new FeaturePart( fpart->getFeature(), newgeom );
581  newFeatureParts->push_back( newfpart );
582  newfpart->getBoundingBox( bmin, bmax );
583  rtree->Insert( bmin, bmax, newfpart );
584  }
585  else
586  {
587  newFeatureParts->push_back( fpart );
588  }
589  }
590 
591  delete featureParts;
592  featureParts = newFeatureParts;
593  }
594 
595 
596 
597 } // end namespace
598