Skip to content

Commit 5a4ef13

Browse files
author
wonder
committed
Multipart labels, advanced label info, added basic support for curved labels (some code taken from Mapnik: placement_finder.cpp)
git-svn-id: http://svn.osgeo.org/qgis/branches/symbology-ng-branch@11176 c8812cc2-4d05-0410-92ff-de0c093fc19c
1 parent 53ace62 commit 5a4ef13

13 files changed

+512
-47
lines changed

src/core/pal/feature.cpp

Lines changed: 241 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ namespace pal
7070

7171
label_x = -1;
7272
label_y = -1;
73+
labelInfo = NULL;
7374

7475
xmin = feat->minmax[0];
7576
xmax = feat->minmax[2];
@@ -532,6 +533,242 @@ namespace pal
532533
}
533534

534535

536+
StraightLabelPosition* Feature::curvedPlacementAtOffset( PointSet* path_positions, double* path_distances, int orientation, int index, double distance )
537+
{
538+
// Check that the given distance is on the given index and find the correct index and distance if not
539+
while (distance < 0 && index > 1)
540+
{
541+
index--;
542+
distance += path_distances[index];
543+
}
544+
545+
if (index <= 1 && distance < 0) // We've gone off the start, fail out
546+
{
547+
std::cerr << "err1" << std::endl;
548+
return NULL;
549+
}
550+
551+
// Same thing, checking if we go off the end
552+
while (index < path_positions->nbPoints && distance > path_distances[index])
553+
{
554+
distance -= path_distances[index];
555+
index += 1;
556+
}
557+
if (index >= path_positions->nbPoints)
558+
{
559+
std::cerr << "err2" << std::endl;
560+
return NULL;
561+
}
562+
563+
// Keep track of the initial index,distance incase we need to re-call get_placement_offset
564+
int initial_index = index;
565+
double initial_distance = distance;
566+
567+
double string_height = labelInfo->label_height;
568+
double old_x = path_positions->x[index-1];
569+
double old_y = path_positions->y[index-1];
570+
571+
double new_x = path_positions->x[index];
572+
double new_y = path_positions->y[index];
573+
574+
double dx = new_x - old_x;
575+
double dy = new_y - old_y;
576+
577+
double segment_length = path_distances[index];
578+
if (segment_length == 0)
579+
{
580+
// Not allowed to place across on 0 length segments or discontinuities
581+
std::cerr << "err3" << std::endl;
582+
return NULL;
583+
}
584+
585+
StraightLabelPosition* slp = NULL;
586+
StraightLabelPosition* slp_tmp = NULL;
587+
// current_placement = placement_result()
588+
double xBase = old_x + dx*distance/segment_length;
589+
double yBase = old_y + dy*distance/segment_length;
590+
double angle = atan2(-dy, dx);
591+
592+
bool orientation_forced = (orientation != 0); // Whether the orientation was set by the caller
593+
if (!orientation_forced)
594+
orientation = (angle > 0.55*M_PI || angle < -0.45*M_PI ? -1 : 1);
595+
596+
int upside_down_char_count = 0; // Count of characters that are placed upside down.
597+
598+
for (int i = 0; i < labelInfo->char_num; i++)
599+
{
600+
double last_character_angle = angle;
601+
602+
// grab the next character according to the orientation
603+
LabelInfo::CharacterInfo& ci = (orientation > 0 ? labelInfo->char_info[i] : labelInfo->char_info[labelInfo->char_num-i-1]);
604+
605+
// Coordinates this character will start at
606+
if (segment_length == 0)
607+
{
608+
// Not allowed to place across on 0 length segments or discontinuities
609+
std::cerr << "err4" << std::endl;
610+
return NULL;
611+
}
612+
613+
double start_x = old_x + dx*distance/segment_length;
614+
double start_y = old_y + dy*distance/segment_length;
615+
// Coordinates this character ends at, calculated below
616+
double end_x = 0;
617+
double end_y = 0;
618+
619+
std::cerr << "segment len " << segment_length << " distance " << distance << std::endl;
620+
if (segment_length - distance >= ci.width)
621+
{
622+
// if the distance remaining in this segment is enough, we just go further along the segment
623+
distance += ci.width;
624+
end_x = old_x + dx*distance/segment_length;
625+
end_y = old_y + dy*distance/segment_length;
626+
}
627+
else
628+
{
629+
// If there isn't enough distance left on this segment
630+
// then we need to search until we find the line segment that ends further than ci.width away
631+
do
632+
{
633+
old_x = new_x;
634+
old_y = new_y;
635+
index++;
636+
if (index >= path_positions->nbPoints) // Bail out if we run off the end of the shape
637+
{
638+
std::cerr << "err5" << std::endl;
639+
return NULL;
640+
}
641+
new_x = path_positions->x[index];
642+
new_y = path_positions->y[index];
643+
dx = new_x - old_x;
644+
dy = new_y - old_y;
645+
segment_length = path_distances[index];
646+
647+
std::cerr << "-> " << sqrt(pow(start_x - new_x,2) + pow(start_y - new_y,2)) << " vs " << ci.width << std::endl;
648+
649+
} while (sqrt(pow(start_x - new_x,2) + pow(start_y - new_y,2)) < ci.width); // Distance from start_ to new_
650+
651+
// Calculate the position to place the end of the character on
652+
findLineCircleIntersection( start_x, start_y, ci.width, old_x, old_y, new_x, new_y, end_x, end_y);
653+
654+
// Need to calculate distance on the new segment
655+
distance = sqrt(pow(old_x - end_x,2) + pow(old_y - end_y,2));
656+
}
657+
658+
// Calculate angle from the start of the character to the end based on start_/end_ position
659+
angle = atan2(start_y-end_y, end_x-start_x);
660+
//angle = atan2(end_y-start_y, end_x-start_x);
661+
662+
// Test last_character_angle vs angle
663+
// since our rendering angle has changed then check against our
664+
// max allowable angle change.
665+
double angle_delta = last_character_angle - angle;
666+
// normalise between -180 and 180
667+
while (angle_delta > M_PI) angle_delta -= 2*M_PI;
668+
while (angle_delta < -M_PI) angle_delta += 2*M_PI;
669+
if (labelInfo->max_char_angle_delta > 0 && fabs(angle_delta) > labelInfo->max_char_angle_delta*(M_PI/180))
670+
{
671+
std::cerr << "err6" << std::endl;
672+
return NULL;
673+
}
674+
675+
double render_angle = angle;
676+
677+
double render_x = start_x;
678+
double render_y = start_y;
679+
680+
// Center the text on the line
681+
//render_x -= ((string_height/2.0) - 1.0)*math.cos(render_angle+math.pi/2)
682+
//render_y += ((string_height/2.0) - 1.0)*math.sin(render_angle+math.pi/2)
683+
684+
if (orientation < 0)
685+
{
686+
// rotate in place
687+
render_x += ci.width*cos(render_angle); //- (string_height-2)*sin(render_angle);
688+
render_y -= ci.width*sin(render_angle); //+ (string_height-2)*cos(render_angle);
689+
render_angle += M_PI;
690+
}
691+
692+
std::cerr << "adding part: " << render_x << " " << render_y << std::endl;
693+
StraightLabelPosition* tmp = new StraightLabelPosition(0, render_x /*- xBase*/, render_y /*- yBase*/, ci.width, string_height, -render_angle, 0.0001, this);
694+
tmp->setPartId( orientation > 0 ? i : labelInfo->char_num-i-1 );
695+
if (slp == NULL)
696+
slp = tmp;
697+
else
698+
slp_tmp->setNextPart(tmp);
699+
slp_tmp = tmp;
700+
701+
//current_placement.add_node(ci.character,render_x, -render_y, render_angle);
702+
//current_placement.add_node(ci.character,render_x - current_placement.starting_x, render_y - current_placement.starting_y, render_angle)
703+
704+
// Normalise to 0 <= angle < 2PI
705+
while (render_angle >= 2*M_PI) render_angle -= 2*M_PI;
706+
while (render_angle < 0) render_angle += 2*M_PI;
707+
708+
if (render_angle > M_PI/2 && render_angle < 1.5*M_PI)
709+
upside_down_char_count++;
710+
}
711+
// END FOR
712+
713+
// If we placed too many characters upside down
714+
if (upside_down_char_count >= labelInfo->char_num/2.0)
715+
{
716+
// if we auto-detected the orientation then retry with the opposite orientation
717+
if (!orientation_forced)
718+
{
719+
orientation = -orientation;
720+
slp = curvedPlacementAtOffset(path_positions, path_distances, orientation, initial_index, initial_distance);
721+
}
722+
else
723+
{
724+
// Otherwise we have failed to find a placement
725+
std::cerr << "err7" << std::endl;
726+
return NULL;
727+
}
728+
}
729+
730+
return slp;
731+
}
732+
733+
int Feature::setPositionForLineCurved( LabelPosition ***lPos, PointSet* mapShape )
734+
{
735+
// label info must be present
736+
if (labelInfo == NULL || labelInfo->char_num == 0)
737+
return 0;
738+
739+
// distance calculation
740+
double* path_distances = new double[mapShape->nbPoints];
741+
double old_x, old_y, new_x, new_y;
742+
for (int i = 0; i < mapShape->nbPoints; i++)
743+
{
744+
if (i == 0)
745+
path_distances[i] = 0;
746+
else
747+
path_distances[i] = sqrt( pow(old_x - mapShape->x[i], 2) + pow(old_y - mapShape->y[i],2) );
748+
old_x = mapShape->x[i];
749+
old_y = mapShape->y[i];
750+
}
751+
752+
// TODO: generate more labels
753+
754+
// generate curved label
755+
StraightLabelPosition* slp = curvedPlacementAtOffset(mapShape, path_distances, 0, 1, 0.0);
756+
757+
if (!slp)
758+
return 0;
759+
760+
// TODO: evaluate cost
761+
762+
int nbp = 1;
763+
( *lPos ) = new LabelPosition*[nbp];
764+
(*lPos)[0] = slp;
765+
766+
return nbp;
767+
}
768+
769+
770+
771+
535772
/*
536773
* seg 2
537774
* pt3 ____________pt2
@@ -843,7 +1080,10 @@ namespace pal
8431080
releaseCoordinates();
8441081
break;
8451082
case GEOS_LINESTRING:
846-
nbp = setPositionForLine( scale, lPos, mapShape, delta );
1083+
if ( layer->getArrangement() == P_CURVED )
1084+
nbp = setPositionForLineCurved( lPos, mapShape );
1085+
else
1086+
nbp = setPositionForLine( scale, lPos, mapShape, delta );
8471087
break;
8481088

8491089
case GEOS_POLYGON:

src/core/pal/feature.h

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,32 @@
4848

4949
namespace pal
5050
{
51+
/** optional additional info about label (for curved labels) */
52+
class LabelInfo
53+
{
54+
public:
55+
typedef struct
56+
{
57+
ushort chr;
58+
double width;
59+
} CharacterInfo;
60+
61+
LabelInfo(int num, double height)
62+
{
63+
max_char_angle_delta = 20;
64+
label_height = height;
65+
char_num = num;
66+
char_info = new CharacterInfo[num];
67+
}
68+
~LabelInfo() { delete [] char_info; }
69+
70+
double max_char_angle_delta;
71+
double label_height;
72+
int char_num;
73+
CharacterInfo* char_info;
74+
};
75+
76+
class StraightLabelPosition;
5177

5278
/**
5379
* \brief Main class to handle feature
@@ -59,6 +85,7 @@ namespace pal
5985
//int id; /* feature no id into layer */
6086
double label_x;
6187
double label_y;
88+
LabelInfo* labelInfo; // optional
6289

6390
int nbSelfObs;
6491
PointSet **selfObs;
@@ -105,6 +132,14 @@ namespace pal
105132
*/
106133
int setPositionForLine( double scale, LabelPosition ***lPos, PointSet *mapShape, double delta_width );
107134

135+
StraightLabelPosition* curvedPlacementAtOffset( PointSet* path_positions, double* path_distances,
136+
int orientation, int index, double distance );
137+
138+
/**
139+
* Generate curved candidates for line features
140+
*/
141+
int setPositionForLineCurved( LabelPosition ***lPos, PointSet* mapShape );
142+
108143
/**
109144
* \brief generate candidates for point feature
110145
* Generate candidates for point features
@@ -116,7 +151,6 @@ namespace pal
116151
int setPositionForPolygon( double scale, LabelPosition ***lPos, PointSet *mapShape, double delta_width );
117152

118153

119-
120154
/**
121155
* \brief Feature against problem bbox
122156
* \param bbox[0] problem x min
@@ -244,6 +278,9 @@ namespace pal
244278
int getNumSelfObstacles() const { return nbSelfObs; }
245279
PointSet* getSelfObstacle(int i) { return selfObs[i]; }
246280

281+
void setLabelInfo(LabelInfo* info) { labelInfo = info; }
282+
283+
247284
};
248285

249286
} // end namespace pal

src/core/pal/geomfunction.cpp

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -626,5 +626,39 @@ namespace pal
626626
#endif
627627

628628

629+
void findLineCircleIntersection(double cx, double cy, double radius,
630+
double x1, double y1, double x2, double y2,
631+
double& xRes, double& yRes)
632+
{
633+
double dx = x2 - x1;
634+
double dy = y2 - y1;
635+
636+
double A = dx * dx + dy * dy;
637+
double B = 2 * (dx * (x1 - cx) + dy * (y1 - cy));
638+
double C = (x1 - cx) * (x1 - cx) + (y1 - cy) * (y1 - cy) - radius * radius;
639+
640+
double det = B * B - 4 * A * C;
641+
if (A <= 0.0000001 || det < 0)
642+
// Should never happen, No real solutions.
643+
return;
644+
645+
if (det == 0)
646+
{
647+
// Could potentially happen.... One solution.
648+
double t = -B / (2 * A);
649+
xRes = x1 + t * dx;
650+
yRes = y1 + t * dy;
651+
}
652+
else
653+
{
654+
// Two solutions.
655+
// Always use the 1st one
656+
// We only really have one solution here, as we know the line segment will start in the circle and end outside
657+
double t = (-B + sqrt(det)) / (2 * A);
658+
xRes = x1 + t * dx;
659+
yRes = y1 + t * dy;
660+
}
661+
}
662+
629663

630664
} // end namespace

src/core/pal/geomfunction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ namespace pal
8181

8282

8383

84+
void findLineCircleIntersection(double cx, double cy, double radius,
85+
double x1, double y1, double x2, double y2,
86+
double& xRes, double& yRes);
8487

8588

8689
int convexHull( int *id, const double* const x, const double* const y, int n );

0 commit comments

Comments
 (0)