27
27
******************************************************************************
29
29
* $Log: mapprimitive.c,v $
30
* Revision 1.60.2.1 2006/02/17 03:06:13 sdlime
30
* Revision 1.76 2006/09/01 18:03:19 umberto
31
* Removed USE_GEOS defines, bug #1890
33
* Revision 1.75 2006/08/17 04:32:16 sdlime
34
* Disable path following labels unless GD 2.0.29 or greater is available.
36
* Revision 1.74 2006/06/27 13:58:19 frank
37
* Place shapeObj->geometry assignment into #ifdef USE_GEOS.
39
* Revision 1.73 2006/06/27 06:32:09 sdlime
40
* Fixed msCopyShape to initialize the geometry property of the destination shape to NULL.
42
* Revision 1.72 2006/05/18 22:20:37 sdlime
43
* Fixed offseting code percentage of 1.0 maps to width-1 or height-1.
45
* Revision 1.71 2006/04/28 03:13:02 sdlime
46
* Fixed a few issues with relative coordinates. Added support for all nine relative positions. (bug 1547)
48
* Revision 1.70 2006/04/27 04:05:17 sdlime
49
* Initial support for relative coordinates. (bug 1547)
51
* Revision 1.69 2006/04/26 13:42:53 frank
52
* temporarily block out MS_PERCENTAGES use till it is defined.
54
* Revision 1.68 2006/04/26 03:25:47 sdlime
55
* Applied most recent patch for curved labels. (bug 1620)
57
* Revision 1.67 2006/03/22 23:31:20 sdlime
58
* Applied latest patch for curved labels. (bug 1620)
60
* Revision 1.66 2006/03/02 06:43:51 sdlime
61
* Applied latest patch for curved labels. (bug 1620)
63
* Revision 1.65 2006/02/24 05:53:49 sdlime
64
* Applied another round of patches for bug 1620.
66
* Revision 1.64 2006/02/18 21:14:09 hobu
67
* INFINITY is already defined on in the math headers on osx.
68
* Don't redefine it if it is already there.
70
* Revision 1.63 2006/02/18 20:59:13 sdlime
71
* Initial code for curved labels. (bug 1620)
73
* Revision 1.62 2006/02/17 03:05:23 sdlime
74
* Slightly more efficient version (no modulus operator) of the routine to test if a ring is an outer ring. (bug 1648)
76
* Revision 1.61 2006/02/16 07:51:31 sdlime
31
77
* Fixed a flaw in routine that computes outer ring list. In certain cases it could miss an outer ring with holes in certain places. (bug 1648)
33
79
* Revision 1.60 2005/11/01 05:35:50 frank
790
** offsets a point relative to an image position
792
void msOffsetPointRelativeTo(pointObj *point, layerObj *layer)
796
if(layer->transform == MS_TRUE) return; /* nothing to do */
798
if(layer->units == MS_PERCENTAGES) {
799
point->x *= (layer->map->width-1);
800
point->y *= (layer->map->height-1);
803
if(layer->transform == MS_FALSE || layer->transform == MS_UL) return; /* done */
805
switch(layer->transform) {
807
x = (layer->map->width-1)/2;
811
x = layer->map->width-1;
816
y = layer->map->height/2;
819
x = layer->map->width/2;
820
y = layer->map->height/2;
823
x = layer->map->width-1;
824
y = layer->map->height/2;
828
y = layer->map->height-1;
831
x = layer->map->width/2;
832
y = layer->map->height-1;
835
x = layer->map->width-1;
836
y = layer->map->height-1;
847
** offsets a shape relative to an image position
849
void msOffsetShapeRelativeTo(shapeObj *shape, layerObj *layer)
854
if(layer->transform == MS_TRUE) return; /* nothing to do */
856
if(layer->units == MS_PERCENTAGES) {
857
for (i=0; i<shape->numlines; i++) {
858
for (j=0; j<shape->line[i].numpoints; j++) {
859
shape->line[i].point[j].x *= (layer->map->width-1);
860
shape->line[i].point[j].y *= (layer->map->height-1);
865
if(layer->transform == MS_FALSE || layer->transform == MS_UL) return; /* done */
867
switch(layer->transform) {
869
x = (layer->map->width-1)/2;
873
x = layer->map->width-1;
878
y = layer->map->height/2;
881
x = layer->map->width/2;
882
y = layer->map->height/2;
885
x = layer->map->width-1;
886
y = layer->map->height/2;
890
y = layer->map->height-1;
893
x = layer->map->width/2;
894
y = layer->map->height-1;
897
x = layer->map->width-1;
898
y = layer->map->height-1;
902
for (i=0; i<shape->numlines; i++) {
903
for (j=0; j<shape->line[i].numpoints; j++) {
904
shape->line[i].point[j].x += x;
905
shape->line[i].point[j].y += y;
736
913
** converts from map coordinates to image coordinates
738
915
void msTransformShapeToPixel(shapeObj *shape, rectObj extent, double cellsize)
1147
1324
return(MS_SUCCESS);
1328
* Calculate a series of label points for each character in the label for a
1329
* given polyline. The resultant series of points is stored in *labelpath.
1330
* Note that the points and bounds are allocated in this function. The
1331
* polyline must be converted to image coordinates before calling this
1334
labelPathObj* msPolylineLabelPath(shapeObj *p, int min_length, fontSetObj *fontset, char *string, labelObj *label, double scalefactor, int *status)
1336
double line_length, max_line_length, segment_length, total_length, distance_along_segment;
1337
double fwd_line_length, rev_line_length, text_length, text_start_length;
1338
int segment_index, line_index;
1340
int i,j,k, inc, final_j;
1346
double cx, cy; /* centre of a character, x & y values. */
1349
double dx, dy, w, cos_t, sin_t;
1351
double **segment_lengths = NULL;
1352
labelPathObj *labelpath = NULL;
1355
/* Line smoothing kernel */
1356
double kernel[] = {0.1,0.2,2,0.2,0.1}; /*{1.5, 2, 15, 2, 1.5};*/
1357
double kernel_normal = 2.6; /* Must be sum of kernel elements */
1359
double letterspacing = 1.25;
1363
/* Assume success */
1364
*status = MS_SUCCESS;
1366
#ifndef GD_HAS_FTEX_XSHOW
1367
goto FAILURE; /* we don't have a current enough version of GD, fall back to ANGLE AUTO */
1369
/* Skip the label and use the normal algorithm if it has fewer than 2 characters */
1370
if ( strlen(string) < 2 ) {
1374
segment_index = line_index = 0;
1375
total_length = max_line_length = 0.0;
1377
/* Determine longest line */
1378
segment_lengths = (double**)malloc(sizeof(double*) * p->numlines);
1379
for ( i = 0; i < p->numlines; i++ ) {
1381
segment_lengths[i] = (double*)malloc(sizeof(double) * p->line[i].numpoints);
1384
for ( j = 1; j < p->line[i].numpoints; j++ ) {
1385
segment_length = sqrt( pow((p->line[i].point[j].x - p->line[i].point[j-1].x), 2.0) +
1386
pow((p->line[i].point[j].y - p->line[i].point[j-1].y), 2.0) );
1387
line_length += segment_length;
1388
segment_lengths[i][j-1] = segment_length;
1392
total_length += line_length;
1394
if ( line_length > max_line_length ) {
1396
max_line_length = line_length;
1402
if ( ((min_length != -1) && (total_length < min_length)) ) {
1404
*status = MS_FAILURE;
1408
if ( p->line[i].numpoints < 2 ) {
1410
*status = MS_FAILURE;
1414
if ( p->line[i].numpoints == 2 ) {
1415
/* We can just use the regular algorithm to save some cycles */
1419
/* Determine the total length of the text */
1420
if ( msGetLabelSizeEx(string, label, &bbox, fontset, scalefactor, MS_FALSE, &offsets) == MS_FAILURE ) {
1421
*status = MS_FAILURE;
1425
size = label->size*scalefactor;
1426
size = MS_MAX(size, label->minsize);
1427
size = MS_MIN(size, label->maxsize);
1429
font = msLookupHashTable(&(fontset->fonts), label->font);
1432
msSetError(MS_TTFERR, "Requested font (%s) not found.", "msPolylineLabelPath()", label->font);
1434
msSetError(MS_TTFERR, "Requested font (NULL) not found.", "msPolylineLabelPath()");
1435
*status = MS_FAILURE;
1439
text_length = letterspacing * (bbox.maxx - bbox.minx);
1441
/* If the text length is way longer than the line, skip adding the
1442
label if it isn't forced (long extrapolated labels tend to be
1444
if ( text_length > 1.5 * max_line_length && label->force == MS_FALSE ) {
1445
*status = MS_FAILURE;
1449
/* Allocate the labelpath */
1450
labelpath = (labelPathObj*)malloc(sizeof(labelPathObj));
1451
labelpath->path.numpoints = strlen(string);
1452
labelpath->path.point = (pointObj*)calloc(labelpath->path.numpoints,sizeof(pointObj));
1453
labelpath->angles = (double*)malloc(sizeof(double) * (labelpath->path.numpoints));
1454
msInitShape(&(labelpath->bounds));
1456
/* The bounds will have two points for each character plus an endpoint:
1457
the UL corners of each bbox will be tied together and the LL corners
1458
will be tied together. */
1459
bounds.numpoints = 2*strlen(string) + 1;
1460
bounds.point = (pointObj*)malloc(sizeof(pointObj) * bounds.numpoints);
1462
/* The points start at (max_line_length - text_length) / 2 in order to be centred */
1463
text_start_length = (max_line_length - text_length) / 2.0;
1465
/* The text is longer than the line: extrapolate the first and last segments */
1466
if ( text_start_length < 0.0 ) {
1469
final_j = p->line[i].numpoints - 1;
1470
fwd_line_length = rev_line_length = 0;
1474
/* Proceed until we've traversed text_start_length in distance */
1475
fwd_line_length = 0;
1477
while ( fwd_line_length < text_start_length )
1478
fwd_line_length += segment_lengths[i][j++];
1482
/* Determine the last segment */
1483
rev_line_length = 0;
1484
final_j = p->line[i].numpoints - 1;
1485
while ( rev_line_length < text_start_length ) {
1486
rev_line_length += segment_lengths[i][final_j - 1];
1496
/* Determine if the line is mostly left to right or right to left */
1499
while ( k < final_j ) {
1500
direction += p->line[i].point[k+1].x - p->line[i].point[k].x;
1504
if ( direction > 0 ) {
1506
/* j is already correct */
1509
/* Length of the segment containing the starting point */
1510
segment_length = segment_lengths[i][j];
1511
/* Determine how far along the segment we need to go */
1512
t = 1 - (fwd_line_length - text_start_length) / segment_length;
1519
/* Length of the segment containing the starting point */
1520
segment_length = segment_lengths[i][j-1];
1521
t = 1 - (rev_line_length - text_start_length) / segment_length;
1525
distance_along_segment = t * segment_length; /* Starting point */
1530
while ( k < labelpath->path.numpoints ) {
1534
x = t * (p->line[i].point[j+inc].x - p->line[i].point[j].x) + p->line[i].point[j].x;
1535
y = t * (p->line[i].point[j+inc].y - p->line[i].point[j].y) + p->line[i].point[j].y;
1537
/* Average this label point with its neighbors according to the
1541
labelpath->path.point[k].x += (kernel[0] + kernel[1]) * x;
1542
labelpath->path.point[k].y += (kernel[0] + kernel[1]) * y;
1544
} else if ( k == 1 ) {
1545
labelpath->path.point[k].x += kernel[0] * x;
1546
labelpath->path.point[k].y += kernel[0] * y;
1548
} else if ( k == labelpath->path.numpoints - 2 ) {
1549
labelpath->path.point[k].x += kernel[4] * x;
1550
labelpath->path.point[k].y += kernel[4] * y;
1552
} else if ( k == labelpath->path.numpoints - 1 ) {
1553
labelpath->path.point[k].x += (kernel[3] + kernel[4]) * x;
1554
labelpath->path.point[k].y += (kernel[3] + kernel[4]) * y;
1557
for (m = 0; m < 5; m++) {
1558
if ( m + k - 2 < 0 || m + k - 2 > labelpath->path.numpoints - 1 )
1561
labelpath->path.point[k+m-2].x += kernel[m]*x;
1562
labelpath->path.point[k+m-2].y += kernel[m]*y;
1565
w = letterspacing*offsets[k];
1567
/* Add the character's width to the distance along the line */
1568
distance_along_segment += w;
1570
/* If we still have segments left and we've past the current
1571
segment, move to the next one */
1573
if ( inc == 1 && j < p->line[i].numpoints - 2 ) {
1575
while ( j < p->line[i].numpoints - 2 && distance_along_segment > segment_lengths[i][j] ) {
1576
distance_along_segment -= segment_lengths[i][j];
1578
/* Move to next segment */
1582
segment_length = segment_lengths[i][j];
1585
} else if ( inc == -1 && j > 1 ) {
1587
while ( j > 1 && distance_along_segment > segment_lengths[i][j-1] ) {
1588
distance_along_segment -= segment_lengths[i][j-1];
1590
/* Move to next segment */
1594
segment_length = segment_lengths[i][j-1];
1598
/* Recalculate interpolation parameter */
1599
t = distance_along_segment / segment_length;
1605
/* Pre-calc the character's centre y value. Used for rotation adjustment. */
1608
labelpath->path.point[0].x /= kernel_normal;
1609
labelpath->path.point[0].y /= kernel_normal;
1611
/* Average the points and calculate each angle */
1612
for (k = 1; k <= labelpath->path.numpoints; k++) {
1614
if ( k < labelpath->path.numpoints ) {
1615
labelpath->path.point[k].x /= kernel_normal;
1616
labelpath->path.point[k].y /= kernel_normal;
1617
dx = labelpath->path.point[k].x - labelpath->path.point[k-1].x;
1618
dy = labelpath->path.point[k].y - labelpath->path.point[k-1].y;
1620
/* Handle the last character */
1621
dx = t * (p->line[i].point[j+inc].x - p->line[i].point[j].x) + p->line[i].point[j].x - labelpath->path.point[k-1].x;
1622
dy = t * (p->line[i].point[j+inc].y - p->line[i].point[j].y) + p->line[i].point[j].y - labelpath->path.point[k-1].y;
1625
theta = -atan2(dy,dx);
1627
/* If the difference between subsequent angles is > 80% of 180deg
1628
bail because the line likely overlaps itself. */
1629
if ( k > 2 && abs(theta - labelpath->angles[k-2]) > 0.4 * MS_PI ) {
1630
*status = MS_FAILURE;
1634
/* msDebug("s: %c (x,y): (%0.2f,%0.2f) t: %0.2f\n", string[k-1], labelpath->path.point[k-1].x, labelpath->path.point[k-1].y, theta); */
1636
labelpath->angles[k-1] = theta;
1638
/* Move the previous point so that when the character is rotated and
1639
placed it is centred on the line */
1643
w = letterspacing*offsets[k-1];
1645
cx = 0; /* Center the character vertically only */
1647
dx = - (cx * cos_t + cy * sin_t);
1648
dy = - (cy * cos_t - cx * sin_t);
1650
labelpath->path.point[k-1].x += dx;
1651
labelpath->path.point[k-1].y += dy;
1653
/* Calculate the bounds */
1659
/* Add the label buffer to the bounds */
1660
bbox.maxx += label->buffer;
1661
bbox.maxy += label->buffer;
1662
bbox.minx -= label->buffer;
1663
bbox.miny -= label->buffer;
1665
if ( k < labelpath->path.numpoints ) {
1666
/* Transform the bbox too. We take the UL and LL corners and rotate
1667
then translate them. */
1668
bounds.point[k-1].x = (bbox.minx * cos_t + bbox.maxy * sin_t) + labelpath->path.point[k-1].x;
1669
bounds.point[k-1].y = (bbox.maxy * cos_t - bbox.minx * sin_t) + labelpath->path.point[k-1].y;
1671
/* Start at end and work towards the half way point */
1672
bounds.point[bounds.numpoints - k - 1].x = (bbox.minx * cos_t + bbox.miny * sin_t) + labelpath->path.point[k-1].x;
1673
bounds.point[bounds.numpoints - k - 1].y = (bbox.miny * cos_t - bbox.minx * sin_t) + labelpath->path.point[k-1].y;
1677
/* This is the last character in the string so we take the UR and LR
1678
corners of the bbox */
1679
bounds.point[k-1].x = (bbox.maxx * cos_t + bbox.maxy * sin_t) + labelpath->path.point[k-1].x;
1680
bounds.point[k-1].y = (bbox.maxy * cos_t - bbox.maxx * sin_t) + labelpath->path.point[k-1].y;
1682
bounds.point[bounds.numpoints - k - 1].x = (bbox.maxx * cos_t + bbox.miny * sin_t) + labelpath->path.point[k-1].x;
1683
bounds.point[bounds.numpoints - k - 1].y = (bbox.miny * cos_t - bbox.maxx * sin_t) + labelpath->path.point[k-1].y;
1688
/* Close the bounds */
1689
bounds.point[bounds.numpoints - 1].x = bounds.point[0].x;
1690
bounds.point[bounds.numpoints - 1].y = bounds.point[0].y;
1692
/* Convert the bounds to a shape and store them in the labelpath */
1693
if ( msAddLineDirectly(&(labelpath->bounds), &bounds) == MS_FAILURE ) {
1694
*status = MS_FAILURE;
1698
msComputeBounds(&(labelpath->bounds));
1700
if ( segment_lengths ) {
1701
for ( i = 0; i < p->numlines; i++ )
1702
free(segment_lengths[i]);
1703
free(segment_lengths);
1712
if ( segment_lengths ) {
1713
for ( i = 0; i < p->numlines; i++ )
1714
free(segment_lengths[i]);
1715
free(segment_lengths);
1722
msFreeLabelPathObj(labelpath);
1150
1729
/* ===========================================================================
1151
1730
Pretty printing of primitive objects
1152
1731
======================================================================== */