Contents of /trunk/src/josm_elemstyles.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 22 - (show annotations)
Wed Dec 17 16:43:46 2008 UTC (15 years, 4 months ago) by harbaum
File MIME type: text/plain
File size: 16503 byte(s)
Color system cleaned up, desktop fullscreen
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
166 return line;
167 }
168
169 /* parse "+123", "-123" and "123%" */
170 static void parse_width_mod(xmlNode *a_node, char *name,
171 elemstyle_width_mod_t *value) {
172 char *mod_str = (char*)xmlGetProp(a_node, BAD_CAST name);
173 if(mod_str && strlen(mod_str) > 0) {
174 if(mod_str[0] == '+') {
175 value->mod = ES_MOD_ADD;
176 value->width = strtoul(mod_str+1, NULL, 10);
177 } else if(mod_str[0] == '-') {
178 value->mod = ES_MOD_SUB;
179 value->width = strtoul(mod_str+1, NULL, 10);
180 } else if(mod_str[strlen(mod_str)-1] == '%') {
181 value->mod = ES_MOD_PERCENT;
182 value->width = strtoul(mod_str, NULL, 10);
183 } else
184 printf("warning: unable to parse modifier %s\n", mod_str);
185 }
186 }
187
188 static elemstyle_line_mod_t *parse_line_mod(xmlDocPtr doc, xmlNode *a_node) {
189 elemstyle_line_mod_t *line_mod = g_new0(elemstyle_line_mod_t, 1);
190
191 parse_width_mod(a_node, "width", &line_mod->line);
192 parse_width_mod(a_node, "width_bg", &line_mod->bg);
193
194 return line_mod;
195 }
196
197 static elemstyle_area_t *parse_area(xmlDocPtr doc, xmlNode *a_node) {
198 elemstyle_area_t *area = g_new0(elemstyle_area_t, 1);
199
200 /* these have to be present */
201 g_assert(parse_color(a_node, "colour", &area->color));
202 return area;
203 }
204
205 static elemstyle_icon_t *parse_icon(xmlDocPtr doc, xmlNode *a_node) {
206 elemstyle_icon_t *icon = g_new0(elemstyle_icon_t, 1);
207
208 char *annotate = (char*)xmlGetProp(a_node, BAD_CAST "annotate");
209 if(annotate) {
210 icon->annotate = (strcasecmp(annotate, "true")==0);
211 xmlFree(annotate);
212 }
213
214 icon->filename = (char*)xmlGetProp(a_node, BAD_CAST "src");
215 g_assert(icon->filename);
216
217 icon->filename = josm_icon_name_adjust(icon->filename);
218
219 icon->zoom_max = 0;
220
221 return icon;
222 }
223
224 static elemstyle_t *parse_rule(xmlDocPtr doc, xmlNode *a_node) {
225 xmlNode *cur_node = NULL;
226 elemstyle_t *elemstyle = g_new0(elemstyle_t, 1);
227
228 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
229 if (cur_node->type == XML_ELEMENT_NODE) {
230 if(strcasecmp((char*)cur_node->name, "condition") == 0) {
231 /* ------ parse condition ------ */
232 elemstyle->condition.key = (char*)xmlGetProp(cur_node, BAD_CAST "k");
233 elemstyle->condition.value = (char*)xmlGetProp(cur_node, BAD_CAST "v");
234 /* todo: add support for "b" (boolean) value */
235 } else if(strcasecmp((char*)cur_node->name, "line") == 0) {
236 /* ------ parse line ------ */
237 g_assert(elemstyle->type == ES_TYPE_NONE);
238 elemstyle->type = ES_TYPE_LINE;
239 elemstyle->line = parse_line(doc, cur_node);
240 } else if(strcasecmp((char*)cur_node->name, "linemod") == 0) {
241 /* ------ parse linemod ------ */
242 g_assert(elemstyle->type == ES_TYPE_NONE);
243 elemstyle->type = ES_TYPE_LINE_MOD;
244 elemstyle->line_mod = parse_line_mod(doc, cur_node);
245 } else if(strcasecmp((char*)cur_node->name, "area") == 0) {
246 /* ------ parse area ------ */
247 g_assert(elemstyle->type == ES_TYPE_NONE);
248 elemstyle->type = ES_TYPE_AREA;
249 elemstyle->area = parse_area(doc, cur_node);
250 } else if(strcasecmp((char*)cur_node->name, "icon") == 0) {
251 elemstyle->icon = parse_icon(doc, cur_node);
252 } else if(strcasecmp((char*)cur_node->name, "scale_min") == 0) {
253 /* scale_min is currently ignored */
254 } else if(strcasecmp((char*)cur_node->name, "scale_max") == 0) {
255 switch (elemstyle->type) {
256 case ES_TYPE_LINE:
257 parse_scale_max(cur_node, &elemstyle->line->zoom_max);
258 break;
259 case ES_TYPE_AREA:
260 parse_scale_max(cur_node, &elemstyle->area->zoom_max);
261 break;
262 default:
263 if (elemstyle->icon) {
264 parse_scale_max(cur_node, &elemstyle->icon->zoom_max);
265 }
266 else {
267 printf("scale_max for unhandled elemstyletype=0x02%x\n",
268 elemstyle->type);
269 }
270 break;
271 }
272 } else {
273 printf("found unhandled rules/rule/%s\n", cur_node->name);
274 }
275 }
276 }
277
278 return elemstyle;
279 }
280
281 static elemstyle_t *parse_rules(xmlDocPtr doc, xmlNode *a_node) {
282 xmlNode *cur_node = NULL;
283 elemstyle_t *elemstyles = NULL, **elemstyle = &elemstyles;
284
285 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
286 if (cur_node->type == XML_ELEMENT_NODE) {
287 if(strcasecmp((char*)cur_node->name, "rule") == 0) {
288 *elemstyle = parse_rule(doc, cur_node);
289 if(*elemstyle) elemstyle = &((*elemstyle)->next);
290 } else
291 printf("found unhandled rules/%s\n", cur_node->name);
292 }
293 }
294 return elemstyles;
295 }
296
297 static elemstyle_t *parse_doc(xmlDocPtr doc) {
298 /* Get the root element node */
299 xmlNode *cur_node = NULL;
300 elemstyle_t *elemstyles = NULL;
301
302 for(cur_node = xmlDocGetRootElement(doc);
303 cur_node; cur_node = cur_node->next) {
304 if (cur_node->type == XML_ELEMENT_NODE) {
305 if(strcasecmp((char*)cur_node->name, "rules") == 0) {
306 elemstyles = parse_rules(doc, cur_node);
307 } else
308 printf("found unhandled %s\n", cur_node->name);
309 }
310 }
311
312 xmlFreeDoc(doc);
313 xmlCleanupParser();
314 return elemstyles;
315 }
316
317 elemstyle_t *josm_elemstyles_load(char *name) {
318 elemstyle_t *elemstyles = NULL;
319
320 printf("Loading JOSM elemstyles ...\n");
321
322 char *filename = find_file(name);
323 if(!filename) {
324 printf("elemstyle file not found\n");
325 return NULL;
326 }
327
328 LIBXML_TEST_VERSION;
329
330 /* parse the file and get the DOM */
331 xmlDoc *doc = NULL;
332 if((doc = xmlReadFile(filename, NULL, 0)) == NULL) {
333 xmlErrorPtr errP = xmlGetLastError();
334 printf("elemstyles download failed: "
335 "XML error while parsing:\n"
336 "%s\n", errP->message);
337 } else {
338 printf("ok, parse doc tree\n");
339 elemstyles = parse_doc(doc);
340 }
341
342 g_free(filename);
343 return elemstyles;
344 }
345
346 /* ----------------------- cleaning up --------------------- */
347
348 static void free_line(elemstyle_line_t *line) {
349 g_free(line);
350 }
351
352 static void free_line_mod(elemstyle_line_mod_t *line_mod) {
353 g_free(line_mod);
354 }
355
356 static void free_area(elemstyle_area_t *area) {
357 g_free(area);
358 }
359
360 static void free_icon(elemstyle_icon_t *icon) {
361 if(icon->filename) xmlFree(icon->filename);
362 g_free(icon);
363 }
364
365 static void elemstyle_free(elemstyle_t *elemstyle) {
366 if(elemstyle->condition.key) xmlFree(elemstyle->condition.key);
367 if(elemstyle->condition.value) xmlFree(elemstyle->condition.value);
368
369 switch(elemstyle->type) {
370 case ES_TYPE_NONE:
371 break;
372 case ES_TYPE_LINE:
373 free_line(elemstyle->line);
374 break;
375 case ES_TYPE_AREA:
376 free_area(elemstyle->area);
377 break;
378 case ES_TYPE_LINE_MOD:
379 free_line_mod(elemstyle->line_mod);
380 break;
381 }
382 if(elemstyle->icon) free_icon(elemstyle->icon);
383 g_free(elemstyle);
384 }
385
386 void josm_elemstyles_free(elemstyle_t *elemstyles) {
387 while(elemstyles) {
388 elemstyle_t *next = elemstyles->next;
389 elemstyle_free(elemstyles);
390 elemstyles = next;
391 }
392 }
393
394 #define WIDTH_SCALE (1.0)
395
396 void josm_elemstyles_colorize_node(style_t *style, node_t *node) {
397 node->zoom_max = style->node.zoom_max;
398 elemstyle_t *elemstyle = style->elemstyles;
399
400 while(elemstyle) {
401 gboolean match = FALSE;
402
403 if(elemstyle->condition.key) {
404 char *value = osm_node_get_value(node, elemstyle->condition.key);
405 if(value) {
406 if(elemstyle->condition.value) {
407 if(strcasecmp(value, elemstyle->condition.value) == 0)
408 match = TRUE;
409 } else
410 match = TRUE;
411 }
412 } else
413 if(osm_node_has_value(node, elemstyle->condition.value))
414 match = TRUE;
415
416
417 if(match && elemstyle->icon) {
418 char *name = g_strdup_printf("styles/%s/%s",
419 style->icon.path_prefix,
420 elemstyle->icon->filename);
421
422 /* free old icon if there's one present */
423 if(node->icon_buf)
424 icon_free(style->iconP, node->icon_buf);
425
426 node->icon_buf = icon_load(style->iconP, name);
427 g_free(name);
428
429 if (elemstyle->icon->zoom_max > 0) {
430 node->zoom_max = elemstyle->icon->zoom_max;
431 }
432 }
433
434 elemstyle = elemstyle->next;
435 }
436 }
437
438 static void line_mod_apply(gint *width, elemstyle_width_mod_t *mod) {
439 switch(mod->mod) {
440 case ES_MOD_NONE:
441 break;
442
443 case ES_MOD_ADD:
444 *width += mod->width;
445 break;
446
447 case ES_MOD_SUB:
448 *width -= mod->width;
449 break;
450
451 case ES_MOD_PERCENT:
452 *width = 100 * *width / mod->width;
453 break;
454 }
455 }
456
457 void josm_elemstyles_colorize_way(style_t *style, way_t *way) {
458 elemstyle_t *elemstyle = style->elemstyles;
459
460 /* use dark grey/no stroke/not filled for everything unknown */
461 way->draw.color = style->way.color;
462 way->draw.width = style->way.width;
463 way->draw.flags = 0;
464 way->draw.zoom_max = 0; // draw at all zoom levels
465
466 /* during the elemstyle search a line_mod may be found. save it here */
467 elemstyle_line_mod_t *line_mod = NULL;
468
469 gboolean way_processed = FALSE;
470 gboolean way_is_closed =
471 (osm_way_get_last_node(way) == osm_way_get_first_node(way));
472
473 while(elemstyle) {
474 // printf("a %s %s\n", elemstyle->condition.key,
475 // elemstyle->condition.value);
476
477 gboolean match = FALSE;
478
479 if(elemstyle->condition.key) {
480 char *value = osm_way_get_value(way, elemstyle->condition.key);
481 if(value) {
482 if(elemstyle->condition.value) {
483 if(strcasecmp(value, elemstyle->condition.value) == 0)
484 match = TRUE;
485 } else
486 match = TRUE;
487 }
488 } else
489 if(osm_way_has_value(way, elemstyle->condition.value))
490 match = TRUE;
491
492 if(match) {
493 switch(elemstyle->type) {
494 case ES_TYPE_NONE:
495 /* this entry does not contain line or area descriptions and is */
496 /* likely just an icon. ignore this as it doesn't make much sense */
497 /* for a way */
498 break;
499
500 case ES_TYPE_LINE:
501 if(!way_processed) {
502 way->draw.color = elemstyle->line->color;
503 way->draw.width = WIDTH_SCALE * elemstyle->line->width;
504 if(elemstyle->line->bg.valid) {
505 way->draw.flags |= OSM_DRAW_FLAG_BG;
506 way->draw.bg.color = elemstyle->line->bg.color;
507 way->draw.bg.width = WIDTH_SCALE * elemstyle->line->bg.width;
508 }
509 if (elemstyle->line->zoom_max > 0) {
510 way->draw.zoom_max = elemstyle->line->zoom_max;
511 }
512 else {
513 way->draw.zoom_max = style->way.zoom_max;
514 }
515 way->draw.dashed = elemstyle->line->dashed;
516 way_processed = TRUE;
517 }
518 break;
519
520 case ES_TYPE_LINE_MOD:
521 /* just save the fact that a line mod was found for later */
522 line_mod = elemstyle->line_mod;
523 break;
524
525 case ES_TYPE_AREA:
526 if(way_is_closed && !way_processed) {
527 way->draw.flags |= OSM_DRAW_FLAG_AREA;
528 /* comment the following line for grey border around all areas */
529 /* (potlatch style) */
530
531 if(style->area.has_border_color)
532 way->draw.color = style->area.border_color;
533 else
534 way->draw.color = elemstyle->area->color;
535
536 way->draw.width = WIDTH_SCALE * style->area.border_width;
537 /* apply area alpha */
538 way->draw.area.color =
539 RGBA_COMBINE(elemstyle->area->color, style->area.color);
540 if (elemstyle->area->zoom_max > 0) {
541 way->draw.zoom_max = elemstyle->area->zoom_max;
542 }
543 else {
544 way->draw.zoom_max = style->area.zoom_max;
545 }
546 way_processed = TRUE;
547 }
548 break;
549 }
550 }
551 elemstyle = elemstyle->next;
552 }
553
554 /* apply the last line mod entry that has been found during search */
555 if(line_mod) {
556 printf("applying last matching line mod to way #%ld\n", way->id);
557 line_mod_apply(&way->draw.width, &line_mod->line);
558
559 /* special case: the way does not have a background, but it is to */
560 /* be modified */
561 if((line_mod->bg.mod != ES_MOD_NONE) &&
562 (!(way->draw.flags & OSM_DRAW_FLAG_BG))) {
563 printf("forcing background\n");
564
565 /* add a background in black color */
566 way->draw.flags |= OSM_DRAW_FLAG_BG;
567 way->draw.bg.color = (0) | 0xff;
568 way->draw.bg.width = way->draw.width;
569 }
570
571 line_mod_apply(&way->draw.bg.width, &line_mod->bg);
572 }
573 }
574
575 void josm_elemstyles_colorize_world(style_t *styles, osm_t *osm) {
576
577 printf("preparing colors\n");
578
579 /* colorize ways */
580 way_t *way = osm->way;
581 while(way) {
582 josm_elemstyles_colorize_way(styles, way);
583 way = way->next;
584 }
585
586 /* icons */
587 node_t *node = osm->node;
588 while(node) {
589 josm_elemstyles_colorize_node(styles, node);
590 node = node->next;
591 }
592 }
593
594 // vim:et:ts=8:sw=2:sts=2:ai