Contents of /trunk/src/josm_elemstyles.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 194 - (show annotations)
Tue Jul 7 19:31:45 2009 UTC (14 years, 11 months ago) by harbaum
File MIME type: text/plain
File size: 16922 byte(s)
Status message simplification
1 /*
2 * Copyright (C) 2008 Till Harbaum <till@harbaum.org>.
3 *
4 * This file is part of OSM2Go.
5 *
6 * OSM2Go is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * OSM2Go is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with OSM2Go. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20 /*
21 http://josm.openstreetmap.de/svn/trunk/styles/standard/elemstyles.xml
22 */
23
24 #include "appdata.h"
25
26 #include <libxml/parser.h>
27 #include <libxml/tree.h>
28
29 #ifndef LIBXML_TREE_ENABLED
30 #error "Tree not enabled in libxml"
31 #endif
32
33 // ratio conversions
34
35 // Scaling constants. Our "zoom" is a screenpx:canvasunit ratio, and the figure
36 // given by an elemstyles.xml is the denominator of a screen:real ratio.
37
38 #define N810_PX_PER_METRE (800 / 0.09)
39 // XXX should probably ask the windowing system for DPI and
40 // work from that instead
41
42 inline float scaledn_to_zoom(const float scaledn) {
43 return N810_PX_PER_METRE / scaledn;
44 }
45
46 inline float zoom_to_scaledn(const float zoom) {
47 return N810_PX_PER_METRE / zoom;
48 }
49
50
51 /* --------------------- elemstyles.xml parsing ----------------------- */
52
53 static int get_hex_digit(char c) {
54 if((c >= '0')&&(c <= '9')) return c-'0';
55 if((c >= 'a')&&(c <= 'f')) return c-'a'+10;
56 if((c >= 'A')&&(c <= 'F')) return c-'A'+10;
57 return -1;
58 }
59
60 static int get_hex_byte(char *str) {
61 int d0 = get_hex_digit(str[0]);
62 int d1 = get_hex_digit(str[1]);
63
64 if((d0 < 0) || (d1 < 0)) return -1;
65
66 return 16*d0+d1;
67 }
68
69 gboolean parse_color(xmlNode *a_node, char *name,
70 elemstyle_color_t *color) {
71 char *color_str = (char*)xmlGetProp(a_node, BAD_CAST name);
72 if(color_str) {
73 /* if the color name contains a # it's a hex representation */
74 /* we parse this directly since gdk_color_parse doesn't cope */
75 /* with the alpha channel that may be present */
76 if(strchr(color_str, '#')) {
77 char *begin = strchr(color_str, '#')+1;
78
79 g_assert(strlen(begin) >= 6);
80 int r = get_hex_byte(begin+0);
81 int g = get_hex_byte(begin+2);
82 int b = get_hex_byte(begin+4);
83
84 /* get alpha channel if present, 0xff otherwise */
85 int a = 0xff;
86 if(strlen(begin) >= 8) {
87 a = get_hex_byte(begin+6);
88 if(a < 0) a = 0xff;
89 }
90
91 *color = (r<<24) + (g<<16) + (b<<8) + a;
92 xmlFree(color_str);
93 return TRUE;
94 }
95
96 GdkColor gdk_color;
97 if(gdk_color_parse(color_str, &gdk_color)) {
98 *color =
99 ((gdk_color.red << 16) & 0xff000000) |
100 ((gdk_color.green << 8) & 0xff0000) |
101 ((gdk_color.blue ) & 0xff00) |
102 (0xff);
103
104 xmlFree(color_str);
105 return TRUE;
106 }
107 xmlFree(color_str);
108 }
109 return FALSE;
110 }
111
112 static gboolean parse_gint(xmlNode *a_node, char *name, gint *val) {
113 char *num_str = (char*)xmlGetProp(a_node, BAD_CAST name);
114 if(num_str) {
115 *val = strtoul(num_str, NULL, 10);
116 xmlFree(num_str);
117 return TRUE;
118 }
119 return FALSE;
120 }
121
122 static gboolean parse_scale_max(xmlNode *a_node, float *val) {
123 xmlChar *val_str = xmlNodeGetContent(a_node);
124 if (val_str) {
125 *val = scaledn_to_zoom(strtod((char *)val_str, NULL));
126 xmlFree(val_str);
127 return TRUE;
128 }
129 return FALSE;
130 }
131
132 static gboolean parse_gboolean(xmlNode *a_node, char *name, gboolean *val) {
133 char *bool_str = (char*)xmlGetProp(a_node, BAD_CAST name);
134 if (!bool_str) {
135 *val = FALSE;
136 return FALSE;
137 }
138 static const char *true_str[] = { "1", "yes", "true", 0 };
139 int i;
140 for (i=0; true_str[i]; ++i) {
141 if (strcasecmp(bool_str, true_str[i])==0) {
142 *val = TRUE;
143 return TRUE;
144 }
145 }
146 *val = FALSE;
147 return TRUE;
148 }
149
150 static elemstyle_line_t *parse_line(xmlDocPtr doc, xmlNode *a_node) {
151 elemstyle_line_t *line = g_new0(elemstyle_line_t, 1);
152
153 /* these have to be present */
154 g_assert(parse_color(a_node, "colour", &line->color));
155 g_assert(parse_gint(a_node, "width", &line->width));
156
157 line->real.valid =
158 parse_gint(a_node, "realwidth", &line->real.width);
159
160 line->bg.valid =
161 parse_gint(a_node, "width_bg", &line->bg.width) &&
162 parse_color(a_node, "colour_bg", &line->bg.color);
163
164 parse_gboolean(a_node, "dashed", &line->dashed);
165 if (!parse_gint(a_node, "dash_length", &line->dash_length)) {
166 line->dash_length = DEFAULT_DASH_LENGTH;
167 }
168
169 return line;
170 }
171
172 /* parse "+123", "-123" and "123%" */
173 static void parse_width_mod(xmlNode *a_node, char *name,
174 elemstyle_width_mod_t *value) {
175 char *mod_str = (char*)xmlGetProp(a_node, BAD_CAST name);
176 if(mod_str && strlen(mod_str) > 0) {
177 if(mod_str[0] == '+') {
178 value->mod = ES_MOD_ADD;
179 value->width = strtoul(mod_str+1, NULL, 10);
180 } else if(mod_str[0] == '-') {
181 value->mod = ES_MOD_SUB;
182 value->width = strtoul(mod_str+1, NULL, 10);
183 } else if(mod_str[strlen(mod_str)-1] == '%') {
184 value->mod = ES_MOD_PERCENT;
185 value->width = strtoul(mod_str, NULL, 10);
186 } else
187 printf("warning: unable to parse modifier %s\n", mod_str);
188 }
189 }
190
191 static elemstyle_line_mod_t *parse_line_mod(xmlDocPtr doc, xmlNode *a_node) {
192 elemstyle_line_mod_t *line_mod = g_new0(elemstyle_line_mod_t, 1);
193
194 parse_width_mod(a_node, "width", &line_mod->line);
195 parse_width_mod(a_node, "width_bg", &line_mod->bg);
196
197 return line_mod;
198 }
199
200 static elemstyle_area_t *parse_area(xmlDocPtr doc, xmlNode *a_node) {
201 elemstyle_area_t *area = g_new0(elemstyle_area_t, 1);
202
203 /* these have to be present */
204 g_assert(parse_color(a_node, "colour", &area->color));
205 return area;
206 }
207
208 static elemstyle_icon_t *parse_icon(xmlDocPtr doc, xmlNode *a_node) {
209 elemstyle_icon_t *icon = g_new0(elemstyle_icon_t, 1);
210
211 char *annotate = (char*)xmlGetProp(a_node, BAD_CAST "annotate");
212 if(annotate) {
213 icon->annotate = (strcasecmp(annotate, "true")==0);
214 xmlFree(annotate);
215 }
216
217 icon->filename = (char*)xmlGetProp(a_node, BAD_CAST "src");
218 g_assert(icon->filename);
219
220 icon->filename = josm_icon_name_adjust(icon->filename);
221
222 icon->zoom_max = 0;
223
224 return icon;
225 }
226
227 static elemstyle_t *parse_rule(xmlDocPtr doc, xmlNode *a_node) {
228 xmlNode *cur_node = NULL;
229 elemstyle_t *elemstyle = g_new0(elemstyle_t, 1);
230
231 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
232 if (cur_node->type == XML_ELEMENT_NODE) {
233 if(strcasecmp((char*)cur_node->name, "condition") == 0) {
234 /* ------ parse condition ------ */
235 elemstyle->condition.key = (char*)xmlGetProp(cur_node, BAD_CAST "k");
236 elemstyle->condition.value = (char*)xmlGetProp(cur_node, BAD_CAST "v");
237 /* todo: add support for "b" (boolean) value */
238 } else if(strcasecmp((char*)cur_node->name, "line") == 0) {
239 /* ------ parse line ------ */
240 g_assert(elemstyle->type == ES_TYPE_NONE);
241 elemstyle->type = ES_TYPE_LINE;
242 elemstyle->line = parse_line(doc, cur_node);
243 } else if(strcasecmp((char*)cur_node->name, "linemod") == 0) {
244 /* ------ parse linemod ------ */
245 g_assert(elemstyle->type == ES_TYPE_NONE);
246 elemstyle->type = ES_TYPE_LINE_MOD;
247 elemstyle->line_mod = parse_line_mod(doc, cur_node);
248 } else if(strcasecmp((char*)cur_node->name, "area") == 0) {
249 /* ------ parse area ------ */
250 g_assert(elemstyle->type == ES_TYPE_NONE);
251 elemstyle->type = ES_TYPE_AREA;
252 elemstyle->area = parse_area(doc, cur_node);
253 } else if(strcasecmp((char*)cur_node->name, "icon") == 0) {
254 elemstyle->icon = parse_icon(doc, cur_node);
255 } else if(strcasecmp((char*)cur_node->name, "scale_min") == 0) {
256 /* scale_min is currently ignored */
257 } else if(strcasecmp((char*)cur_node->name, "scale_max") == 0) {
258 switch (elemstyle->type) {
259 case ES_TYPE_LINE:
260 parse_scale_max(cur_node, &elemstyle->line->zoom_max);
261 break;
262 case ES_TYPE_AREA:
263 parse_scale_max(cur_node, &elemstyle->area->zoom_max);
264 break;
265 default:
266 if (elemstyle->icon) {
267 parse_scale_max(cur_node, &elemstyle->icon->zoom_max);
268 }
269 else {
270 printf("scale_max for unhandled elemstyletype=0x02%x\n",
271 elemstyle->type);
272 }
273 break;
274 }
275 } else {
276 printf("found unhandled rules/rule/%s\n", cur_node->name);
277 }
278 }
279 }
280
281 return elemstyle;
282 }
283
284 static elemstyle_t *parse_rules(xmlDocPtr doc, xmlNode *a_node) {
285 xmlNode *cur_node = NULL;
286 elemstyle_t *elemstyles = NULL, **elemstyle = &elemstyles;
287
288 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
289 if (cur_node->type == XML_ELEMENT_NODE) {
290 if(strcasecmp((char*)cur_node->name, "rule") == 0) {
291 *elemstyle = parse_rule(doc, cur_node);
292 if(*elemstyle) elemstyle = &((*elemstyle)->next);
293 } else
294 printf("found unhandled rules/%s\n", cur_node->name);
295 }
296 }
297 return elemstyles;
298 }
299
300 static elemstyle_t *parse_doc(xmlDocPtr doc) {
301 /* Get the root element node */
302 xmlNode *cur_node = NULL;
303 elemstyle_t *elemstyles = NULL;
304
305 for(cur_node = xmlDocGetRootElement(doc);
306 cur_node; cur_node = cur_node->next) {
307 if (cur_node->type == XML_ELEMENT_NODE) {
308 if(strcasecmp((char*)cur_node->name, "rules") == 0) {
309 elemstyles = parse_rules(doc, cur_node);
310 } else
311 printf("found unhandled %s\n", cur_node->name);
312 }
313 }
314
315 xmlFreeDoc(doc);
316 xmlCleanupParser();
317 return elemstyles;
318 }
319
320 elemstyle_t *josm_elemstyles_load(char *name) {
321 elemstyle_t *elemstyles = NULL;
322
323 printf("Loading JOSM elemstyles ...\n");
324
325 char *filename = find_file(name);
326 if(!filename) {
327 printf("elemstyle file not found\n");
328 return NULL;
329 }
330
331 LIBXML_TEST_VERSION;
332
333 /* parse the file and get the DOM */
334 xmlDoc *doc = NULL;
335 if((doc = xmlReadFile(filename, NULL, 0)) == NULL) {
336 xmlErrorPtr errP = xmlGetLastError();
337 printf("elemstyles download failed: "
338 "XML error while parsing:\n"
339 "%s\n", errP->message);
340 } else {
341 printf("ok, parse doc tree\n");
342 elemstyles = parse_doc(doc);
343 }
344
345 g_free(filename);
346 return elemstyles;
347 }
348
349 /* ----------------------- cleaning up --------------------- */
350
351 static void free_line(elemstyle_line_t *line) {
352 g_free(line);
353 }
354
355 static void free_line_mod(elemstyle_line_mod_t *line_mod) {
356 g_free(line_mod);
357 }
358
359 static void free_area(elemstyle_area_t *area) {
360 g_free(area);
361 }
362
363 static void free_icon(elemstyle_icon_t *icon) {
364 if(icon->filename) xmlFree(icon->filename);
365 g_free(icon);
366 }
367
368 static void elemstyle_free(elemstyle_t *elemstyle) {
369 if(elemstyle->condition.key) xmlFree(elemstyle->condition.key);
370 if(elemstyle->condition.value) xmlFree(elemstyle->condition.value);
371
372 switch(elemstyle->type) {
373 case ES_TYPE_NONE:
374 break;
375 case ES_TYPE_LINE:
376 free_line(elemstyle->line);
377 break;
378 case ES_TYPE_AREA:
379 free_area(elemstyle->area);
380 break;
381 case ES_TYPE_LINE_MOD:
382 free_line_mod(elemstyle->line_mod);
383 break;
384 }
385 if(elemstyle->icon) free_icon(elemstyle->icon);
386 g_free(elemstyle);
387 }
388
389 void josm_elemstyles_free(elemstyle_t *elemstyles) {
390 while(elemstyles) {
391 elemstyle_t *next = elemstyles->next;
392 elemstyle_free(elemstyles);
393 elemstyles = next;
394 }
395 }
396
397 #define WIDTH_SCALE (1.0)
398
399 void josm_elemstyles_colorize_node(style_t *style, node_t *node) {
400 node->zoom_max = style->node.zoom_max;
401 elemstyle_t *elemstyle = style->elemstyles;
402
403 while(elemstyle) {
404 gboolean match = FALSE;
405
406 if(elemstyle->condition.key) {
407 char *value = osm_node_get_value(node, elemstyle->condition.key);
408 if(value) {
409 if(elemstyle->condition.value) {
410 if(strcasecmp(value, elemstyle->condition.value) == 0)
411 match = TRUE;
412 } else
413 match = TRUE;
414 }
415 } else
416 if(osm_node_has_value(node, elemstyle->condition.value))
417 match = TRUE;
418
419
420 if(match && elemstyle->icon) {
421 char *name = g_strdup_printf("styles/%s/%s",
422 style->icon.path_prefix,
423 elemstyle->icon->filename);
424
425 /* free old icon if there's one present */
426 if(node->icon_buf) {
427 icon_free(style->iconP, node->icon_buf);
428 node->icon_buf = NULL;
429 }
430
431 node->icon_buf = icon_load(style->iconP, name);
432 g_free(name);
433
434 if (elemstyle->icon->zoom_max > 0) {
435 node->zoom_max = elemstyle->icon->zoom_max;
436 }
437 }
438
439 elemstyle = elemstyle->next;
440 }
441 }
442
443 static void line_mod_apply(gint *width, elemstyle_width_mod_t *mod) {
444 switch(mod->mod) {
445 case ES_MOD_NONE:
446 break;
447
448 case ES_MOD_ADD:
449 *width += mod->width;
450 break;
451
452 case ES_MOD_SUB:
453 *width -= mod->width;
454 break;
455
456 case ES_MOD_PERCENT:
457 *width = 100 * *width / mod->width;
458 break;
459 }
460 }
461
462 void josm_elemstyles_colorize_way(style_t *style, way_t *way) {
463 elemstyle_t *elemstyle = style->elemstyles;
464
465 /* use dark grey/no stroke/not filled for everything unknown */
466 way->draw.color = style->way.color;
467 way->draw.width = style->way.width;
468 way->draw.flags = 0;
469 way->draw.zoom_max = 0; // draw at all zoom levels
470
471 /* during the elemstyle search a line_mod may be found. save it here */
472 elemstyle_line_mod_t *line_mod = NULL;
473
474 gboolean way_processed = FALSE;
475 gboolean way_is_closed =
476 (osm_way_get_last_node(way) == osm_way_get_first_node(way));
477
478 while(elemstyle) {
479 // printf("a %s %s\n", elemstyle->condition.key,
480 // elemstyle->condition.value);
481
482 gboolean match = FALSE;
483
484 if(elemstyle->condition.key) {
485 char *value = osm_way_get_value(way, elemstyle->condition.key);
486 if(value) {
487 if(elemstyle->condition.value) {
488 if(strcasecmp(value, elemstyle->condition.value) == 0)
489 match = TRUE;
490 } else
491 match = TRUE;
492 }
493 } else
494 if(osm_way_has_value(way, elemstyle->condition.value))
495 match = TRUE;
496
497 if(match) {
498 switch(elemstyle->type) {
499 case ES_TYPE_NONE:
500 /* this entry does not contain line or area descriptions and is */
501 /* likely just an icon. ignore this as it doesn't make much sense */
502 /* for a way */
503 break;
504
505 case ES_TYPE_LINE:
506 if(!way_processed) {
507 way->draw.color = elemstyle->line->color;
508 way->draw.width = WIDTH_SCALE * elemstyle->line->width;
509 if(elemstyle->line->bg.valid) {
510 way->draw.flags |= OSM_DRAW_FLAG_BG;
511 way->draw.bg.color = elemstyle->line->bg.color;
512 way->draw.bg.width = WIDTH_SCALE * elemstyle->line->bg.width;
513 }
514 if (elemstyle->line->zoom_max > 0) {
515 way->draw.zoom_max = elemstyle->line->zoom_max;
516 }
517 else {
518 way->draw.zoom_max = style->way.zoom_max;
519 }
520 way->draw.dashed = elemstyle->line->dashed;
521 way->draw.dash_length = elemstyle->line->dash_length;
522 way_processed = TRUE;
523 }
524 break;
525
526 case ES_TYPE_LINE_MOD:
527 /* just save the fact that a line mod was found for later */
528 line_mod = elemstyle->line_mod;
529 break;
530
531 case ES_TYPE_AREA:
532 if(way_is_closed && !way_processed) {
533 way->draw.flags |= OSM_DRAW_FLAG_AREA;
534 /* comment the following line for grey border around all areas */
535 /* (potlatch style) */
536
537 if(style->area.has_border_color)
538 way->draw.color = style->area.border_color;
539 else
540 way->draw.color = elemstyle->area->color;
541
542 way->draw.width = WIDTH_SCALE * style->area.border_width;
543 /* apply area alpha */
544 way->draw.area.color =
545 RGBA_COMBINE(elemstyle->area->color, style->area.color);
546 if (elemstyle->area->zoom_max > 0) {
547 way->draw.zoom_max = elemstyle->area->zoom_max;
548 }
549 else {
550 way->draw.zoom_max = style->area.zoom_max;
551 }
552 way_processed = TRUE;
553 }
554 break;
555 }
556 }
557 elemstyle = elemstyle->next;
558 }
559
560 /* apply the last line mod entry that has been found during search */
561 if(line_mod) {
562 printf("applying last matching line mod to way #"ITEM_ID_FORMAT"\n",
563 way->id);
564 line_mod_apply(&way->draw.width, &line_mod->line);
565
566 /* special case: the way does not have a background, but it is to */
567 /* be modified */
568 if((line_mod->bg.mod != ES_MOD_NONE) &&
569 (!(way->draw.flags & OSM_DRAW_FLAG_BG))) {
570 printf("forcing background\n");
571
572 /* add a background in black color */
573 way->draw.flags |= OSM_DRAW_FLAG_BG;
574 way->draw.bg.color = (0) | 0xff;
575 way->draw.bg.width = way->draw.width;
576 }
577
578 line_mod_apply(&way->draw.bg.width, &line_mod->bg);
579 }
580 }
581
582 void josm_elemstyles_colorize_world(style_t *styles, osm_t *osm) {
583
584 printf("preparing colors\n");
585
586 /* colorize ways */
587 way_t *way = osm->way;
588 while(way) {
589 josm_elemstyles_colorize_way(styles, way);
590 way = way->next;
591 }
592
593 /* icons */
594 node_t *node = osm->node;
595 while(node) {
596 /* remove all icon references that may still be there from */
597 /* an old style */
598 if(node->icon_buf) {
599 icon_free(styles->iconP, node->icon_buf);
600 node->icon_buf = NULL;
601 }
602
603 josm_elemstyles_colorize_node(styles, node);
604 node = node->next;
605 }
606 }
607
608 // vim:et:ts=8:sw=2:sts=2:ai