1 /**********************************************************************
2 *
3 * PostGIS - Spatial Types for PostgreSQL
4 * http://postgis.net
5 * Copyright 2008 Kevin Neufeld
6 *
7 * This is free software; you can redistribute and/or modify it under
8 * the terms of the GNU General Public Licence. See the COPYING file.
9 *
10 * This program will generate a .png image for every .wkt file specified
11 * in this directory's Makefile. Every .wkt file may contain several
12 * entries of geometries represented as WKT strings. Every line in
13 * a wkt file is stylized using a predetermined style (line thinkness,
14 * fill color, etc) currently hard coded in this programs main function.
15 *
16 * In order to generate a png file, ImageMagicK must be installed in the
17 * user's path as system calls are invoked to "convert". In this manner,
18 * WKT files are converted into SVG syntax and rasterized as png. (PostGIS's
19 * internal SVG methods could not be used dues to syntax issues with ImageMagick)
20 *
21 * The goal of this application is to dynamically generate all the spatial
22 * pictures used in PostGIS's documentation pages.
23 *
24 * Note: the coordinates of the supplied geometries should be within and scaled
25 * to the x-y range of [0,200], otherwise the rendered geometries may be
26 * rendered outside of the generated image's extents, or may be rendered too
27 * small to be recognizable as anything other than a single point.
28 *
29 **********************************************************************/
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <string.h>
34 #include <ctype.h>
35 #include <sys/wait.h> /* for WEXITSTATUS */
36
37 #include "liblwgeom.h"
38 #include "liblwgeom_internal.h"
39 #include "lwgeom_log.h"
40 #include "styles.h"
41
42 #define SHOW_DIGS_DOUBLE 15
43 #define MAX_DOUBLE_PRECISION 15
44 #define MAX_DIGS_DOUBLE (SHOW_DIGS_DOUBLE + 2) /* +2 for dot and sign */
45
46 // Some global styling variables
47 char *imageSize = "200x200";
48
49 int getStyleName(char **styleName, char* line);
50
51 static void
checked_system(const char * cmd)52 checked_system(const char* cmd)
53 {
54 int ret = system(cmd);
55 if ( WEXITSTATUS(ret) != 0 ) {
56 fprintf(stderr, "Failure return code (%d) from command: %s", WEXITSTATUS(ret), cmd);
57 }
58 }
59
60 /**
61 * Writes the coordinates of a POINTARRAY to a char* where ordinates are
62 * separated by a comma and coordinates by a space so that the coordinate
63 * pairs can be interpreted by ImageMagick's SVG draw command.
64 *
65 * @param output a reference to write the POINTARRAY to
66 * @param pa a reference to a POINTARRAY
67 * @return the numbers of character written to *output
68 */
69 static size_t
pointarrayToString(char * output,POINTARRAY * pa)70 pointarrayToString(char *output, POINTARRAY *pa)
71 {
72 char x[OUT_DOUBLE_BUFFER_SIZE];
73 char y[OUT_DOUBLE_BUFFER_SIZE];
74 int i;
75 char *ptr = output;
76
77 for ( i=0; i < pa->npoints; i++ )
78 {
79 POINT2D pt;
80 getPoint2d_p(pa, i, &pt);
81
82 lwprint_double(pt.x, 10, x, OUT_DOUBLE_BUFFER_SIZE);
83 lwprint_double(pt.y, 10, y, OUT_DOUBLE_BUFFER_SIZE);
84
85 if ( i ) ptr += sprintf(ptr, " ");
86 ptr += sprintf(ptr, "%s,%s", x, y);
87 }
88
89 return (ptr - output);
90 }
91
92 /**
93 * Serializes a LWPOINT to a char*. This is a helper function that partially
94 * writes the appropriate draw and fill commands used to generate an SVG image
95 * using ImageMagick's "convert" command.
96
97 * @param output a char reference to write the LWPOINT to
98 * @param lwp a reference to a LWPOINT
99 * @return the numbers of character written to *output
100 */
101 static size_t
drawPoint(char * output,LWPOINT * lwp,LAYERSTYLE * styles)102 drawPoint(char *output, LWPOINT *lwp, LAYERSTYLE *styles)
103 {
104 char x[OUT_DOUBLE_BUFFER_SIZE];
105 char y1[OUT_DOUBLE_BUFFER_SIZE];
106 char y2[OUT_DOUBLE_BUFFER_SIZE];
107 char *ptr = output;
108 POINTARRAY *pa = lwp->point;
109 POINT2D p;
110 getPoint2d_p(pa, 0, &p);
111
112 LWDEBUGF(4, "%s", "drawPoint called");
113 LWDEBUGF( 4, "point = %s", lwgeom_to_ewkt((LWGEOM*)lwp) );
114
115 lwprint_double(p.x, 10, x, OUT_DOUBLE_BUFFER_SIZE);
116 lwprint_double(p.y, 10, y1, OUT_DOUBLE_BUFFER_SIZE);
117 lwprint_double(p.y + styles->pointSize, 10, y2, OUT_DOUBLE_BUFFER_SIZE);
118
119 ptr += sprintf(ptr, "-fill %s -strokewidth 0 ", styles->pointColor);
120 ptr += sprintf(ptr, "-draw \"circle %s,%s %s,%s", x, y1, x, y2);
121 ptr += sprintf(ptr, "'\" ");
122
123 return (ptr - output);
124 }
125
126 /**
127 * Serializes a LWLINE to a char*. This is a helper function that partially
128 * writes the appropriate draw and stroke commands used to generate an SVG image
129 * using ImageMagick's "convert" command.
130
131 * @param output a char reference to write the LWLINE to
132 * @param lwl a reference to a LWLINE
133 * @return the numbers of character written to *output
134 */
135 static size_t
drawLineString(char * output,LWLINE * lwl,LAYERSTYLE * style)136 drawLineString(char *output, LWLINE *lwl, LAYERSTYLE *style)
137 {
138 char *ptr = output;
139
140 LWDEBUGF(4, "%s", "drawLineString called");
141 LWDEBUGF( 4, "line = %s", lwgeom_to_ewkt((LWGEOM*)lwl) );
142
143 ptr += sprintf(ptr, "-fill none -stroke %s -strokewidth %d ", style->lineColor, style->lineWidth);
144 ptr += sprintf(ptr, "-draw \"stroke-linecap round stroke-linejoin round path 'M ");
145 ptr += pointarrayToString(ptr, lwl->points );
146 ptr += sprintf(ptr, "'\" ");
147
148 return (ptr - output);
149 }
150
151 /**
152 * Serializes a LWPOLY to a char*. This is a helper function that partially
153 * writes the appropriate draw and fill commands used to generate an SVG image
154 * using ImageMagick's "convert" command.
155
156 * @param output a char reference to write the LWPOLY to
157 * @param lwp a reference to a LWPOLY
158 * @return the numbers of character written to *output
159 */
160 static size_t
drawPolygon(char * output,LWPOLY * lwp,LAYERSTYLE * style)161 drawPolygon(char *output, LWPOLY *lwp, LAYERSTYLE *style)
162 {
163 char *ptr = output;
164 int i;
165
166 LWDEBUGF(4, "%s", "drawPolygon called");
167 LWDEBUGF( 4, "poly = %s", lwgeom_to_ewkt((LWGEOM*)lwp) );
168
169 ptr += sprintf(ptr, "-fill %s -stroke %s -strokewidth %d ", style->polygonFillColor, style->polygonStrokeColor, style->polygonStrokeWidth );
170 ptr += sprintf(ptr, "-draw \"path '");
171 for (i=0; i<lwp->nrings; i++)
172 {
173 ptr += sprintf(ptr, "M ");
174 ptr += pointarrayToString(ptr, lwp->rings[i] );
175 ptr += sprintf(ptr, " ");
176 }
177 ptr += sprintf(ptr, "'\" ");
178
179 return (ptr - output);
180 }
181
182 /**
183 * Serializes a LWGEOM to a char*. This is a helper function that partially
184 * writes the appropriate draw, stroke, and fill commands used to generate an
185 * SVG image using ImageMagick's "convert" command.
186
187 * @param output a char reference to write the LWGEOM to
188 * @param lwgeom a reference to a LWGEOM
189 * @return the numbers of character written to *output
190 */
191 static size_t
drawGeometry(char * output,LWGEOM * lwgeom,LAYERSTYLE * styles)192 drawGeometry(char *output, LWGEOM *lwgeom, LAYERSTYLE *styles )
193 {
194 char *ptr = output;
195 int i;
196 int type = lwgeom->type;
197
198 switch (type)
199 {
200 case POINTTYPE:
201 ptr += drawPoint(ptr, (LWPOINT*)lwgeom, styles );
202 break;
203 case LINETYPE:
204 ptr += drawLineString(ptr, (LWLINE*)lwgeom, styles );
205 break;
206 case POLYGONTYPE:
207 ptr += drawPolygon(ptr, (LWPOLY*)lwgeom, styles );
208 break;
209 case MULTIPOINTTYPE:
210 case MULTILINETYPE:
211 case MULTIPOLYGONTYPE:
212 case COLLECTIONTYPE:
213 for (i=0; i<((LWCOLLECTION*)lwgeom)->ngeoms; i++)
214 {
215 ptr += drawGeometry( ptr, lwcollection_getsubgeom ((LWCOLLECTION*)lwgeom, i), styles );
216 }
217 break;
218 }
219
220 return (ptr - output);
221 }
222
223 /**
224 * Invokes a system call to ImageMagick's "convert" command that adds a drop
225 * shadow to the current layer image.
226 *
227 * @param layerNumber the current working layer number.
228 static void
229 addDropShadow(int layerNumber)
230 {
231 char str[512];
232 sprintf(
233 str,
234 "convert tmp%d.png -gravity center \"(\" +clone -background navy -shadow 100x3+4+4 \")\" +swap -background none -flatten tmp%d.png",
235 layerNumber, layerNumber);
236 LWDEBUGF(4, "%s", str);
237 checked_system(str);
238 }
239 */
240
241 /**
242 * Invokes a system call to ImageMagick's "convert" command that adds a
243 * highlight to the current layer image.
244 *
245 * @param layerNumber the current working layer number.
246 */
247 static void
addHighlight(int layerNumber)248 addHighlight(int layerNumber)
249 {
250 // TODO: change to properly sized string
251 char str[512];
252 sprintf(
253 str,
254 "convert tmp%d.png \"(\" +clone -channel A -separate +channel -negate -background black -virtual-pixel background -blur 0x3 -shade 120x55 -contrast-stretch 0%% +sigmoidal-contrast 7x50%% -fill grey50 -colorize 10%% +clone +swap -compose overlay -composite \")\" -compose In -composite tmp%d.png",
255 layerNumber, layerNumber);
256 LWDEBUGF(4, "%s", str);
257 checked_system(str);
258 }
259
260 /**
261 * Invokes a system call to ImageMagick's "convert" command that reduces
262 * the overall filesize
263 *
264 * @param filename the current working image.
265 */
266 static void
optimizeImage(char * filename)267 optimizeImage(char* filename)
268 {
269 char *str;
270 str = malloc( (18 + (2*strlen(filename)) + 1) * sizeof(char) );
271 sprintf(str, "convert %s -depth 8 %s", filename, filename);
272 LWDEBUGF(4, "%s", str);
273 checked_system(str);
274 free(str);
275 }
276
277 /**
278 * Flattens all the temporary processing png files into a single image
279 */
280 static void
flattenLayers(char * filename)281 flattenLayers(char* filename)
282 {
283 char *str = malloc( (48 + strlen(filename) + 1) * sizeof(char) );
284 sprintf(str, "convert tmp[0-9].png -background white -flatten %s", filename);
285
286 LWDEBUGF(4, "%s", str);
287 checked_system(str);
288 // TODO: only remove the tmp files if they exist.
289 remove("tmp0.png");
290 remove("tmp1.png");
291 remove("tmp2.png");
292 remove("tmp3.png");
293 remove("tmp4.png");
294 remove("tmp5.png");
295 remove("tmp6.png");
296 remove("tmp7.png");
297 remove("tmp8.png");
298 remove("tmp9.png");
299 free(str);
300 }
301
302
303 // TODO: comments
304 int
getStyleName(char ** styleName,char * line)305 getStyleName(char **styleName, char* line)
306 {
307 char *ptr = strrchr(line, ';');
308 if (ptr == NULL)
309 {
310 *styleName = malloc( 8 );
311 strncpy(*styleName, "Default", 7);
312 (*styleName)[7] = '\0';
313 return 1;
314 }
315 else
316 {
317 *styleName = malloc( ptr - line + 1);
318 strncpy(*styleName, line, ptr - line);
319 (*styleName)[ptr - line] = '\0';
320 LWDEBUGF( 4, "%s", *styleName );
321 return 0;
322 }
323 }
324
325
326 /**
327 * Main Application. Currently, drawing styles are hardcoded in this method.
328 * Future work may entail reading the styles from a .properties file.
329 */
main(int argc,const char * argv[])330 int main( int argc, const char* argv[] )
331 {
332 FILE *pfile;
333 LWGEOM *lwgeom;
334 char line [2048];
335 char *filename;
336 int layerCount;
337 LAYERSTYLE *styles;
338 char *image_path = "../images/";
339
340 getStyles(&styles);
341
342 if ( argc != 2 || strlen(argv[1]) < 3)
343 {
344 lwerror("You must specify a wkt filename to convert, and it must be 3 or more characters long.\n");
345 return -1;
346 }
347
348 if ( (pfile = fopen(argv[1], "r")) == NULL)
349 {
350 perror ( argv[1] );
351 return -1;
352 }
353
354 filename = malloc( strlen(argv[1]) + strlen(image_path) + 1 );
355 strcpy( filename, image_path );
356 strncat( filename, argv[1], strlen(argv[1])-3 );
357 strncat( filename, "png", 3 );
358
359 printf( "generating %s\n", filename );
360
361 layerCount = 0;
362 while ( fgets ( line, sizeof line, pfile ) != NULL && !isspace(*line) )
363 {
364
365 char output[32768];
366 char *ptr = output;
367 char *styleName;
368 LAYERSTYLE *style;
369 int useDefaultStyle;
370
371 ptr += sprintf( ptr, "convert -size %s xc:none ", imageSize );
372
373 useDefaultStyle = getStyleName(&styleName, line);
374 LWDEBUGF( 4, "%s", styleName );
375
376 if (useDefaultStyle)
377 {
378 printf(" Warning: using Default style for layer %d\n", layerCount);
379 lwgeom = lwgeom_from_wkt( line, LW_PARSER_CHECK_NONE );
380 }
381 else
382 lwgeom = lwgeom_from_wkt( line+strlen(styleName)+1, LW_PARSER_CHECK_NONE );
383 LWDEBUGF( 4, "geom = %s", lwgeom_to_ewkt((LWGEOM*)lwgeom) );
384
385 style = getStyle(styles, styleName);
386 if ( ! style ) {
387 lwerror("Could not find style named %s", styleName);
388 return -1;
389 }
390 ptr += drawGeometry( ptr, lwgeom, style );
391
392 ptr += sprintf( ptr, "-flip tmp%d.png", layerCount );
393
394 lwfree( lwgeom );
395
396 LWDEBUGF( 4, "%s", output );
397 checked_system(output);
398
399 addHighlight( layerCount );
400 // addDropShadow( layerCount );
401 layerCount++;
402 free(styleName);
403 }
404
405 flattenLayers(filename);
406 optimizeImage(filename);
407
408 fclose(pfile);
409 free(filename);
410 freeStyles(&styles);
411 return 0;
412 }
413