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