1 /******************************************************************************
2  * $Id$
3  *
4  * Project:  MapServer
5  * Purpose: RFC48 implementation of geometry transformations for styling
6  * Author:   Thomas Bonfort , Camptocamp (thomas.bonfort at camptocamp.com)
7  *
8  ******************************************************************************
9  * Copyright (c) 1996-2005 Regents of the University of Minnesota.
10  *
11  * Permission is hereby granted, free of charge, to any person obtaining a
12  * copy of this software and associated documentation files (the "Software"),
13  * to deal in the Software without restriction, including without limitation
14  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15  * and/or sell copies of the Software, and to permit persons to whom the
16  * Software is furnished to do so, subject to the following conditions:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies of this Software or works derived from this Software.
20  *
21  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
22  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
24  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27  * DEALINGS IN THE SOFTWARE.
28  *****************************************************************************/
29 
30 #include "mapserver.h"
31 #include "mapthread.h"
32 
33 extern int yyparse(parseObj *p);
34 
msStyleSetGeomTransform(styleObj * s,char * transform)35 void msStyleSetGeomTransform(styleObj *s, char *transform)
36 {
37   msFree(s->_geomtransform.string);
38   if (!transform) {
39     s->_geomtransform.type = MS_GEOMTRANSFORM_NONE;
40     s->_geomtransform.string = NULL;
41     return;
42   }
43   s->_geomtransform.string = msStrdup(transform);
44   if(!strncasecmp("start",transform,5)) {
45     s->_geomtransform.type = MS_GEOMTRANSFORM_START;
46   } else if(!strncasecmp("end",transform,3)) {
47     s->_geomtransform.type = MS_GEOMTRANSFORM_END;
48   } else if(!strncasecmp("vertices",transform,8)) {
49     s->_geomtransform.type = MS_GEOMTRANSFORM_VERTICES;
50   } else if(!strncasecmp("bbox",transform,4)) {
51     s->_geomtransform.type = MS_GEOMTRANSFORM_BBOX;
52   } else if(!strncasecmp("labelpnt",transform,8)) {
53     s->_geomtransform.type = MS_GEOMTRANSFORM_LABELPOINT;
54   } else if(!strncasecmp("labelpoly",transform,9)) {
55     s->_geomtransform.type = MS_GEOMTRANSFORM_LABELPOLY;
56   } else if(!strncasecmp("labelcenter",transform,11)) {
57     s->_geomtransform.type = MS_GEOMTRANSFORM_LABELCENTER;
58   } else if(!strncasecmp("centroid",transform,8)) {
59     s->_geomtransform.type = MS_GEOMTRANSFORM_CENTROID;
60   } else {
61     s->_geomtransform.type = MS_GEOMTRANSFORM_NONE;
62     msSetError(MS_MISCERR,"unknown transform expression","msStyleSetGeomTransform()");
63     msFree(s->_geomtransform.string);
64     s->_geomtransform.string = NULL;
65   }
66 }
67 
68 
69 /*
70  * return a copy of the geometry transform expression
71  * returned char* must be freed by the caller
72  */
msStyleGetGeomTransform(styleObj * s)73 char *msStyleGetGeomTransform(styleObj *s)
74 {
75   return msStrdup(s->_geomtransform.string);
76 }
77 
78 
calcOrientation(pointObj * p1,pointObj * p2)79 double calcOrientation(pointObj *p1, pointObj *p2)
80 {
81   double theta;
82   theta = atan2(p2->x - p1->x,p2->y - p1->y);
83   return MS_RAD_TO_DEG*(theta-MS_PI2);
84 }
85 
calcMidAngle(pointObj * p1,pointObj * p2,pointObj * p3)86 double calcMidAngle(pointObj *p1, pointObj *p2, pointObj *p3)
87 {
88   pointObj p1n;
89   double dx12, dy12, dx23, dy23, l12, l23;
90 
91   /* We treat both segments as vector 1-2 and vector 2-3 and
92    * compute their dx,dy and length
93    */
94   dx12 = p2->x - p1->x;
95   dy12 = p2->y - p1->y;
96   l12 = sqrt(dx12*dx12 + dy12*dy12);
97   dx23 = p3->x - p2->x;
98   dy23 = p3->y - p2->y;
99   l23 = sqrt(dx23*dx23 + dy23*dy23);
100 
101   /* Normalize length of vector 1-2 to same as length of vector 2-3 */
102   if (l12 > 0.0)
103   {
104     p1n.x = p2->x - dx12*(l23/l12);
105     p1n.y = p2->y - dy12*(l23/l12);
106   }
107   else
108     p1n = *p2;  /* segment 1-2 is 0-length, use segment 2-3 for orientation */
109 
110   /* Return the orientation defined by the sum of the normalized vectors */
111   return calcOrientation(&p1n, p3);
112 }
113 
114 /*
115  * RFC48 implementation:
116  *  - transform the original shapeobj
117  *  - use the styleObj to render the transformed shapeobj
118  */
msDrawTransformedShape(mapObj * map,imageObj * image,shapeObj * shape,styleObj * style,double scalefactor)119 int msDrawTransformedShape(mapObj *map, imageObj *image, shapeObj *shape, styleObj *style, double scalefactor)
120 {
121   int type = style->_geomtransform.type;
122   int i,j,status = MS_SUCCESS;
123   switch(type) {
124     case MS_GEOMTRANSFORM_END: /*render point on last vertex only*/
125       for(j=0; j<shape->numlines; j++) {
126         lineObj *line = &(shape->line[j]);
127         pointObj *p = &(line->point[line->numpoints-1]);
128         if(p->x<0||p->x>image->width||p->y<0||p->y>image->height)
129           continue;
130         if(style->autoangle==MS_TRUE && line->numpoints>1) {
131           style->angle = calcOrientation(&(line->point[line->numpoints-2]),p);
132         }
133         status = msDrawMarkerSymbol(map,image,p,style,scalefactor);
134       }
135       break;
136     case MS_GEOMTRANSFORM_START: /*render point on first vertex only*/
137       for(j=0; j<shape->numlines; j++) {
138         lineObj *line = &(shape->line[j]);
139         pointObj *p = &(line->point[0]);
140         /*skip if outside image*/
141         if(p->x<0||p->x>image->width||p->y<0||p->y>image->height)
142           continue;
143         if(style->autoangle==MS_TRUE && line->numpoints>1) {
144           style->angle = calcOrientation(p,&(line->point[1]));
145         }
146         status = msDrawMarkerSymbol(map,image,p,style,scalefactor);
147       }
148       break;
149     case MS_GEOMTRANSFORM_VERTICES:
150       for(j=0; j<shape->numlines; j++) {
151         lineObj *line = &(shape->line[j]);
152         for(i=1; i<line->numpoints-1; i++) {
153           pointObj *p = &(line->point[i]);
154           /*skip points outside image*/
155           if(p->x<0||p->x>image->width||p->y<0||p->y>image->height)
156             continue;
157           if(style->autoangle==MS_TRUE) {
158             style->angle = calcMidAngle(&(line->point[i-1]),&(line->point[i]),&(line->point[i+1]));
159           }
160           status = msDrawMarkerSymbol(map,image,p,style,scalefactor);
161         }
162       }
163       break;
164     case MS_GEOMTRANSFORM_BBOX: {
165       shapeObj bbox;
166       lineObj bbox_line;
167       pointObj bbox_points[5];
168       int padding = MS_MAX(style->width,style->size)+3; /* so clipped shape does not extent into image */
169 
170       /*create a shapeObj representing the bounding box (clipped by the image size)*/
171       bbox.numlines = 1;
172       bbox.line = &bbox_line;
173       bbox.line->numpoints = 5;
174       bbox.line->point = bbox_points;
175 
176       msComputeBounds(shape);
177       bbox_points[0].x=bbox_points[4].x=bbox_points[1].x =
178                                           (shape->bounds.minx < -padding) ? -padding : shape->bounds.minx;
179       bbox_points[2].x=bbox_points[3].x =
180                          (shape->bounds.maxx > image->width+padding) ? image->width+padding : shape->bounds.maxx;
181       bbox_points[0].y=bbox_points[4].y=bbox_points[3].y =
182                                           (shape->bounds.miny < -padding) ? -padding : shape->bounds.miny;
183       bbox_points[1].y=bbox_points[2].y =
184                          (shape->bounds.maxy > image->height+padding) ? image->height+padding : shape->bounds.maxy;
185       status = msDrawShadeSymbol(map, image, &bbox, style, scalefactor);
186     }
187     break;
188     case MS_GEOMTRANSFORM_CENTROID: {
189       double unused; /*used by centroid function*/
190       pointObj centroid;
191       if(MS_SUCCESS == msGetPolygonCentroid(shape,&centroid,&unused,&unused)) {
192         status = msDrawMarkerSymbol(map,image,&centroid,style,scalefactor);
193       }
194     }
195     break;
196     case MS_GEOMTRANSFORM_EXPRESSION: {
197       int status;
198       shapeObj *tmpshp;
199       parseObj p;
200 
201       p.shape = shape; /* set a few parser globals (hence the lock) */
202       p.expr = &(style->_geomtransform);
203 
204       if(p.expr->tokens == NULL) { /* this could happen if drawing originates from legend code (#5193) */
205         status = msTokenizeExpression(p.expr, NULL, NULL);
206         if(status != MS_SUCCESS) {
207           msSetError(MS_MISCERR, "Unable to tokenize expression.", "msDrawTransformedShape()");
208           return MS_FAILURE;
209         }
210       }
211 
212       p.expr->curtoken = p.expr->tokens; /* reset */
213       p.type = MS_PARSE_TYPE_SHAPE;
214 
215       status = yyparse(&p);
216       if (status != 0) {
217         msSetError(MS_PARSEERR, "Failed to process shape expression: %s", "msDrawTransformedShape", style->_geomtransform.string);
218         return MS_FAILURE;
219       }
220       tmpshp = p.result.shpval;
221 
222       switch (tmpshp->type) {
223         case MS_SHAPE_POINT:
224         case MS_SHAPE_POLYGON:
225           status = msDrawShadeSymbol(map, image, tmpshp, style, scalefactor);
226           break;
227         case MS_SHAPE_LINE:
228           status = msDrawLineSymbol(map, image, tmpshp, style, scalefactor);
229           break;
230       }
231 
232       msFreeShape(tmpshp);
233       msFree(tmpshp);
234     }
235     break;
236     case MS_GEOMTRANSFORM_LABELPOINT:
237     case MS_GEOMTRANSFORM_LABELPOLY:
238       break;
239     default:
240       msSetError(MS_MISCERR, "unknown geomtransform", "msDrawTransformedShape()");
241       return MS_FAILURE;
242   }
243   return status;
244 }
245 
246 /*
247  * RFC89 implementation:
248  *  - transform directly the shapeobj
249  */
msGeomTransformShape(mapObj * map,layerObj * layer,shapeObj * shape)250 int msGeomTransformShape(mapObj *map, layerObj *layer, shapeObj *shape)
251 {
252   int i;
253   expressionObj *e =  &layer->_geomtransform;
254 
255 #ifdef USE_V8_MAPSCRIPT
256   if (!map->v8context) {
257     msV8CreateContext(map);
258     if (!map->v8context)
259     {
260       msSetError(MS_V8ERR, "Unable to create v8 context.", "msGeomTransformShape()");
261       return MS_FAILURE;
262     }
263   }
264 
265   msV8ContextSetLayer(map, layer);
266 #endif
267 
268   switch(e->type) {
269     case MS_GEOMTRANSFORM_EXPRESSION: {
270       int status;
271       shapeObj *tmpshp;
272       parseObj p;
273 
274       p.shape = shape; /* set a few parser globals (hence the lock) */
275       p.expr = e;
276       p.expr->curtoken = p.expr->tokens; /* reset */
277       p.type = MS_PARSE_TYPE_SHAPE;
278       p.dblval = map->cellsize * (msInchesPerUnit(map->units,0)/msInchesPerUnit(layer->units,0));
279       p.dblval2 = 0;
280       /* data_cellsize is only set with contour layer */
281       if (layer->connectiontype == MS_CONTOUR)
282       {
283         const char *value = msLookupHashTable(&layer->metadata, "__data_cellsize__");
284         if (value)
285           p.dblval2 = atof(value);
286       }
287 
288       status = yyparse(&p);
289       if (status != 0) {
290         msSetError(MS_PARSEERR, "Failed to process shape expression: %s", "msGeomTransformShape()", e->string);
291         return MS_FAILURE;
292       }
293 
294       tmpshp = p.result.shpval;
295 
296       for (i= 0; i < shape->numlines; i++)
297         free(shape->line[i].point);
298       shape->numlines = 0;
299       if (shape->line) free(shape->line);
300 
301       for(i=0; i<tmpshp->numlines; i++)
302         msAddLine(shape, &(tmpshp->line[i])); /* copy each line */
303 
304       msFreeShape(tmpshp);
305       msFree(tmpshp);
306     }
307     break;
308     default:
309       msSetError(MS_MISCERR, "unknown geomtransform", "msGeomTransformShape()");
310       return MS_FAILURE;
311   }
312 
313   return MS_SUCCESS;
314 }
315