Contents of /trunk/src/josm_elemstyles.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 15 - (show annotations)
Tue Dec 16 17:00:20 2008 UTC (15 years, 4 months ago) by harbaum
File MIME type: text/plain
File size: 15523 byte(s)
initial line mod support and some small fixes
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 gboolean parse_color(xmlNode *a_node, char *name,
54 elemstyle_color_t *color) {
55 char *color_str = (char*)xmlGetProp(a_node, BAD_CAST name);
56 if(color_str) {
57 GdkColor gdk_color;
58 if(gdk_color_parse(color_str, &gdk_color)) {
59 *color =
60 ((gdk_color.red << 8) & 0xff0000) |
61 ((gdk_color.green ) & 0xff00) |
62 ((gdk_color.blue >> 8) & 0xff);
63
64 xmlFree(color_str);
65 return TRUE;
66 }
67 xmlFree(color_str);
68 }
69 return FALSE;
70 }
71
72 static gboolean parse_gint(xmlNode *a_node, char *name, gint *val) {
73 char *num_str = (char*)xmlGetProp(a_node, BAD_CAST name);
74 if(num_str) {
75 *val = strtoul(num_str, NULL, 10);
76 xmlFree(num_str);
77 return TRUE;
78 }
79 return FALSE;
80 }
81
82 static gboolean parse_scale_max(xmlNode *a_node, float *val) {
83 xmlChar *val_str = xmlNodeGetContent(a_node);
84 if (val_str) {
85 *val = scaledn_to_zoom(strtod((char *)val_str, NULL));
86 xmlFree(val_str);
87 return TRUE;
88 }
89 return FALSE;
90 }
91
92 static gboolean parse_gboolean(xmlNode *a_node, char *name, gboolean *val) {
93 char *bool_str = (char*)xmlGetProp(a_node, BAD_CAST name);
94 if (!bool_str) {
95 *val = FALSE;
96 return FALSE;
97 }
98 static const char *true_str[] = { "1", "yes", "true", 0 };
99 int i;
100 for (i=0; true_str[i]; ++i) {
101 if (strcasecmp(bool_str, true_str[i])==0) {
102 *val = TRUE;
103 return TRUE;
104 }
105 }
106 *val = FALSE;
107 return TRUE;
108 }
109
110 static elemstyle_line_t *parse_line(xmlDocPtr doc, xmlNode *a_node) {
111 elemstyle_line_t *line = g_new0(elemstyle_line_t, 1);
112
113 /* these have to be present */
114 g_assert(parse_color(a_node, "colour", &line->color));
115 g_assert(parse_gint(a_node, "width", &line->width));
116
117 line->real.valid =
118 parse_gint(a_node, "realwidth", &line->real.width);
119
120 line->bg.valid =
121 parse_gint(a_node, "width_bg", &line->bg.width) &&
122 parse_color(a_node, "colour_bg", &line->bg.color);
123
124 parse_gboolean(a_node, "dashed", &line->dashed);
125
126 return line;
127 }
128
129 /* parse "+123", "-123" and "123%" */
130 static void parse_width_mod(xmlNode *a_node, char *name,
131 elemstyle_width_mod_t *value) {
132 char *mod_str = (char*)xmlGetProp(a_node, BAD_CAST name);
133 if(mod_str && strlen(mod_str) > 0) {
134 if(mod_str[0] == '+') {
135 value->mod = ES_MOD_ADD;
136 value->width = strtoul(mod_str+1, NULL, 10);
137 } else if(mod_str[0] == '-') {
138 value->mod = ES_MOD_SUB;
139 value->width = strtoul(mod_str+1, NULL, 10);
140 } else if(mod_str[strlen(mod_str)-1] == '%') {
141 value->mod = ES_MOD_PERCENT;
142 value->width = strtoul(mod_str, NULL, 10);
143 } else
144 printf("warning: unable to parse modifier %s\n", mod_str);
145 }
146 }
147
148 static elemstyle_line_mod_t *parse_line_mod(xmlDocPtr doc, xmlNode *a_node) {
149 elemstyle_line_mod_t *line_mod = g_new0(elemstyle_line_mod_t, 1);
150
151 parse_width_mod(a_node, "width", &line_mod->line);
152 parse_width_mod(a_node, "width_bg", &line_mod->bg);
153
154 return line_mod;
155 }
156
157 static elemstyle_area_t *parse_area(xmlDocPtr doc, xmlNode *a_node) {
158 elemstyle_area_t *area = g_new0(elemstyle_area_t, 1);
159
160 /* these have to be present */
161 g_assert(parse_color(a_node, "colour", &area->color));
162 return area;
163 }
164
165 static elemstyle_icon_t *parse_icon(xmlDocPtr doc, xmlNode *a_node) {
166 elemstyle_icon_t *icon = g_new0(elemstyle_icon_t, 1);
167
168 char *annotate = (char*)xmlGetProp(a_node, BAD_CAST "annotate");
169 if(annotate) {
170 icon->annotate = (strcasecmp(annotate, "true")==0);
171 xmlFree(annotate);
172 }
173
174 icon->filename = (char*)xmlGetProp(a_node, BAD_CAST "src");
175 g_assert(icon->filename);
176
177 icon->filename = josm_icon_name_adjust(icon->filename);
178
179 icon->zoom_max = 0;
180
181 return icon;
182 }
183
184 static elemstyle_t *parse_rule(xmlDocPtr doc, xmlNode *a_node) {
185 xmlNode *cur_node = NULL;
186 elemstyle_t *elemstyle = g_new0(elemstyle_t, 1);
187
188 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
189 if (cur_node->type == XML_ELEMENT_NODE) {
190 if(strcasecmp((char*)cur_node->name, "condition") == 0) {
191 /* ------ parse condition ------ */
192 elemstyle->condition.key = (char*)xmlGetProp(cur_node, BAD_CAST "k");
193 elemstyle->condition.value = (char*)xmlGetProp(cur_node, BAD_CAST "v");
194 /* todo: add support for "b" (boolean) value */
195 } else if(strcasecmp((char*)cur_node->name, "line") == 0) {
196 /* ------ parse line ------ */
197 g_assert(elemstyle->type == ES_TYPE_NONE);
198 elemstyle->type = ES_TYPE_LINE;
199 elemstyle->line = parse_line(doc, cur_node);
200 } else if(strcasecmp((char*)cur_node->name, "linemod") == 0) {
201 /* ------ parse linemod ------ */
202 g_assert(elemstyle->type == ES_TYPE_NONE);
203 elemstyle->type = ES_TYPE_LINE_MOD;
204 elemstyle->line_mod = parse_line_mod(doc, cur_node);
205 } else if(strcasecmp((char*)cur_node->name, "area") == 0) {
206 /* ------ parse area ------ */
207 g_assert(elemstyle->type == ES_TYPE_NONE);
208 elemstyle->type = ES_TYPE_AREA;
209 elemstyle->area = parse_area(doc, cur_node);
210 } else if(strcasecmp((char*)cur_node->name, "icon") == 0) {
211 elemstyle->icon = parse_icon(doc, cur_node);
212 } else if(strcasecmp((char*)cur_node->name, "scale_min") == 0) {
213 /* scale_min is currently ignored */
214 } else if(strcasecmp((char*)cur_node->name, "scale_max") == 0) {
215 switch (elemstyle->type) {
216 case ES_TYPE_LINE:
217 parse_scale_max(cur_node, &elemstyle->line->zoom_max);
218 break;
219 case ES_TYPE_AREA:
220 parse_scale_max(cur_node, &elemstyle->area->zoom_max);
221 break;
222 default:
223 if (elemstyle->icon) {
224 parse_scale_max(cur_node, &elemstyle->icon->zoom_max);
225 }
226 else {
227 printf("scale_max for unhandled elemstyletype=0x02%x\n",
228 elemstyle->type);
229 }
230 break;
231 }
232 } else {
233 printf("found unhandled rules/rule/%s\n", cur_node->name);
234 }
235 }
236 }
237
238 return elemstyle;
239 }
240
241 static elemstyle_t *parse_rules(xmlDocPtr doc, xmlNode *a_node) {
242 xmlNode *cur_node = NULL;
243 elemstyle_t *elemstyles = NULL, **elemstyle = &elemstyles;
244
245 for (cur_node = a_node->children; cur_node; cur_node = cur_node->next) {
246 if (cur_node->type == XML_ELEMENT_NODE) {
247 if(strcasecmp((char*)cur_node->name, "rule") == 0) {
248 *elemstyle = parse_rule(doc, cur_node);
249 if(*elemstyle) elemstyle = &((*elemstyle)->next);
250 } else
251 printf("found unhandled rules/%s\n", cur_node->name);
252 }
253 }
254 return elemstyles;
255 }
256
257 static elemstyle_t *parse_doc(xmlDocPtr doc) {
258 /* Get the root element node */
259 xmlNode *cur_node = NULL;
260 elemstyle_t *elemstyles = NULL;
261
262 for(cur_node = xmlDocGetRootElement(doc);
263 cur_node; cur_node = cur_node->next) {
264 if (cur_node->type == XML_ELEMENT_NODE) {
265 if(strcasecmp((char*)cur_node->name, "rules") == 0) {
266 elemstyles = parse_rules(doc, cur_node);
267 } else
268 printf("found unhandled %s\n", cur_node->name);
269 }
270 }
271
272 xmlFreeDoc(doc);
273 xmlCleanupParser();
274 return elemstyles;
275 }
276
277 elemstyle_t *josm_elemstyles_load(char *name) {
278 elemstyle_t *elemstyles = NULL;
279
280 printf("Loading JOSM elemstyles ...\n");
281
282 char *filename = find_file(name);
283 if(!filename) {
284 printf("elemstyle file not found\n");
285 return NULL;
286 }
287
288 LIBXML_TEST_VERSION;
289
290 /* parse the file and get the DOM */
291 xmlDoc *doc = NULL;
292 if((doc = xmlReadFile(filename, NULL, 0)) == NULL) {
293 xmlErrorPtr errP = xmlGetLastError();
294 printf("elemstyles download failed: "
295 "XML error while parsing:\n"
296 "%s\n", errP->message);
297 } else {
298 printf("ok, parse doc tree\n");
299 elemstyles = parse_doc(doc);
300 }
301
302 g_free(filename);
303 return elemstyles;
304 }
305
306 /* ----------------------- cleaning up --------------------- */
307
308 static void free_line(elemstyle_line_t *line) {
309 g_free(line);
310 }
311
312 static void free_line_mod(elemstyle_line_mod_t *line_mod) {
313 g_free(line_mod);
314 }
315
316 static void free_area(elemstyle_area_t *area) {
317 g_free(area);
318 }
319
320 static void free_icon(elemstyle_icon_t *icon) {
321 if(icon->filename) xmlFree(icon->filename);
322 g_free(icon);
323 }
324
325 static void elemstyle_free(elemstyle_t *elemstyle) {
326 if(elemstyle->condition.key) xmlFree(elemstyle->condition.key);
327 if(elemstyle->condition.value) xmlFree(elemstyle->condition.value);
328
329 switch(elemstyle->type) {
330 case ES_TYPE_NONE:
331 break;
332 case ES_TYPE_LINE:
333 free_line(elemstyle->line);
334 break;
335 case ES_TYPE_AREA:
336 free_area(elemstyle->area);
337 break;
338 case ES_TYPE_LINE_MOD:
339 free_line_mod(elemstyle->line_mod);
340 break;
341 }
342 if(elemstyle->icon) free_icon(elemstyle->icon);
343 g_free(elemstyle);
344 }
345
346 void josm_elemstyles_free(elemstyle_t *elemstyles) {
347 while(elemstyles) {
348 elemstyle_t *next = elemstyles->next;
349 elemstyle_free(elemstyles);
350 elemstyles = next;
351 }
352 }
353
354 #define WIDTH_SCALE (1.0)
355
356 void josm_elemstyles_colorize_node(style_t *style, node_t *node) {
357 node->zoom_max = style->node.zoom_max;
358 elemstyle_t *elemstyle = style->elemstyles;
359
360 while(elemstyle) {
361 gboolean match = FALSE;
362
363 if(elemstyle->condition.key) {
364 char *value = osm_node_get_value(node, elemstyle->condition.key);
365 if(value) {
366 if(elemstyle->condition.value) {
367 if(strcasecmp(value, elemstyle->condition.value) == 0)
368 match = TRUE;
369 } else
370 match = TRUE;
371 }
372 } else
373 if(osm_node_has_value(node, elemstyle->condition.value))
374 match = TRUE;
375
376
377 if(match && elemstyle->icon) {
378 char *name = g_strdup_printf("styles/%s/%s",
379 style->icon.path_prefix,
380 elemstyle->icon->filename);
381
382 /* free old icon if there's one present */
383 if(node->icon_buf)
384 icon_free(style->iconP, node->icon_buf);
385
386 node->icon_buf = icon_load(style->iconP, name);
387 g_free(name);
388
389 if (elemstyle->icon->zoom_max > 0) {
390 node->zoom_max = elemstyle->icon->zoom_max;
391 }
392 }
393
394 elemstyle = elemstyle->next;
395 }
396 }
397
398 static void line_mod_apply(gint *width, elemstyle_width_mod_t *mod) {
399 switch(mod->mod) {
400 case ES_MOD_NONE:
401 break;
402
403 case ES_MOD_ADD:
404 *width += mod->width;
405 break;
406
407 case ES_MOD_SUB:
408 *width -= mod->width;
409 break;
410
411 case ES_MOD_PERCENT:
412 *width = 100 * *width / mod->width;
413 break;
414 }
415 }
416
417 void josm_elemstyles_colorize_way(style_t *style, way_t *way) {
418 elemstyle_t *elemstyle = style->elemstyles;
419
420 /* use dark grey/no stroke/not filled for everything unknown */
421 way->draw.color = RGB2CANVAS(style->way.color);
422 way->draw.width = style->way.width;
423 way->draw.flags = 0;
424 way->draw.zoom_max = 0; // draw at all zoom levels
425
426 /* during the elemstyle search a line_mod may be found. save it here */
427 elemstyle_line_mod_t *line_mod = NULL;
428
429 gboolean way_processed = FALSE;
430 gboolean way_is_closed =
431 (osm_way_get_last_node(way) == osm_way_get_first_node(way));
432
433 while(elemstyle) {
434 // printf("a %s %s\n", elemstyle->condition.key,
435 // elemstyle->condition.value);
436
437 gboolean match = FALSE;
438
439 if(elemstyle->condition.key) {
440 char *value = osm_way_get_value(way, elemstyle->condition.key);
441 if(value) {
442 if(elemstyle->condition.value) {
443 if(strcasecmp(value, elemstyle->condition.value) == 0)
444 match = TRUE;
445 } else
446 match = TRUE;
447 }
448 } else
449 if(osm_way_has_value(way, elemstyle->condition.value))
450 match = TRUE;
451
452 if(match) {
453 switch(elemstyle->type) {
454 case ES_TYPE_NONE:
455 /* this entry does not contain line or area descriptions and is */
456 /* likely just an icon. ignore this as it doesn't make much sense */
457 /* for a way */
458 break;
459
460 case ES_TYPE_LINE:
461 if(!way_processed) {
462 way->draw.color = (elemstyle->line->color << 8) | 0xff;
463 way->draw.width = WIDTH_SCALE * elemstyle->line->width;
464 if(elemstyle->line->bg.valid) {
465 way->draw.flags |= OSM_DRAW_FLAG_BG;
466 way->draw.bg.color = (elemstyle->line->bg.color << 8) | 0xff;
467 way->draw.bg.width = WIDTH_SCALE * elemstyle->line->bg.width;
468 }
469 if (elemstyle->line->zoom_max > 0) {
470 way->draw.zoom_max = elemstyle->line->zoom_max;
471 }
472 else {
473 way->draw.zoom_max = style->way.zoom_max;
474 }
475 way->draw.dashed = elemstyle->line->dashed;
476 way_processed = TRUE;
477 }
478 break;
479
480 case ES_TYPE_LINE_MOD:
481 /* just save the fact that a line mod was found for later */
482 line_mod = elemstyle->line_mod;
483 break;
484
485 case ES_TYPE_AREA:
486 if(way_is_closed && !way_processed) {
487 way->draw.flags |= OSM_DRAW_FLAG_AREA;
488 /* comment the following line for grey border around all areas */
489 /* (potlatch style) */
490
491 if(style->area.has_border_color)
492 way->draw.color = (style->area.border_color << 8) | 0xff;
493 else
494 way->draw.color = (elemstyle->area->color << 8) | 0xff;
495
496 way->draw.width = WIDTH_SCALE * style->area.border_width;
497 way->draw.area.color = (elemstyle->area->color << 8) |
498 style->area.opaque;
499 if (elemstyle->area->zoom_max > 0) {
500 way->draw.zoom_max = elemstyle->area->zoom_max;
501 }
502 else {
503 way->draw.zoom_max = style->area.zoom_max;
504 }
505 way_processed = TRUE;
506 }
507 break;
508 }
509 }
510 elemstyle = elemstyle->next;
511 }
512
513 /* apply the last line mod entry that has been found during search */
514 if(line_mod) {
515 printf("applying last matching line mod to way #%ld\n", way->id);
516 line_mod_apply(&way->draw.width, &line_mod->line);
517
518 /* special case: the way does not have a background, but it is to */
519 /* be modified */
520 if((line_mod->bg.mod != ES_MOD_NONE) &&
521 (!(way->draw.flags & OSM_DRAW_FLAG_BG))) {
522 printf("forcing background\n");
523
524 /* add a background in black color */
525 way->draw.flags |= OSM_DRAW_FLAG_BG;
526 way->draw.bg.color = (0) | 0xff;
527 way->draw.bg.width = way->draw.width;
528 }
529
530 line_mod_apply(&way->draw.bg.width, &line_mod->bg);
531 }
532 }
533
534 void josm_elemstyles_colorize_world(style_t *styles, osm_t *osm) {
535
536 printf("preparing colors\n");
537
538 /* colorize ways */
539 way_t *way = osm->way;
540 while(way) {
541 josm_elemstyles_colorize_way(styles, way);
542 way = way->next;
543 }
544
545 /* icons */
546 node_t *node = osm->node;
547 while(node) {
548 josm_elemstyles_colorize_node(styles, node);
549 node = node->next;
550 }
551 }
552
553 // vim:et:ts=8:sw=2:sts=2:ai