]> mj.ucw.cz Git - leo.git/blob - sym-line.c
Old implementation of generalization on map input removed
[leo.git] / sym-line.c
1 /*
2  *      Hic Est Leo -- Line and Area Symbolizer
3  *
4  *      (c) 2014 Martin Mares <mj@ucw.cz>
5  */
6
7 #include "leo.h"
8
9 #include <ucw/fastbuf.h>
10 #include <ucw/mempool.h>
11
12 #include <math.h>
13 #include <stdio.h>
14
15 #include "osm.h"
16 #include "sym.h"
17
18 static void sym_line_attrs(struct sym_line *l, struct svg *svg)
19 {
20   svg_set_attr(svg, "fill", "none");
21   svg_set_attr_color(svg, "stroke", l->color);
22   svg_set_attr_dimen(svg, "stroke-width", l->width);
23   if (l->opacity != 1)
24     svg_set_attr_float(svg, "stroke-opacity", l->opacity);
25
26   switch (l->line_cap)
27     {
28     case VALUE_NONE:
29       // This is the default (butt)
30       break;
31     case VALUE_ROUND:
32     case VALUE_SQUARE:
33       svg_set_attr(svg, "stroke-linecap", osm_val_decode(l->line_cap));
34       break;
35     default:
36       osm_obj_warn(l->s.o, "Unknown stroke-linecap: %s", osm_val_decode(l->line_cap));
37     }
38
39   switch (l->line_join)
40     {
41     case VALUE_MITER:
42       // This is the default
43       if (l->miter_limit != 4)
44         svg_set_attr_float(svg, "stroke-miterlimit", l->miter_limit);
45       break;
46     case VALUE_ROUND:
47     case VALUE_BEVEL:
48       svg_set_attr(svg, "stroke-linejoin", osm_val_decode(l->line_join));
49       break;
50     default:
51       osm_obj_warn(l->s.o, "Unknown stroke-linejoin: %s", osm_val_decode(l->line_join));
52     }
53
54   if (l->dash_pattern)
55     {
56       struct fastbuf *fb = svg_fb_open(svg);
57       for (uns i=0; i < GARY_SIZE(l->dash_pattern); i++)
58         {
59           if (i)
60             bputc(fb, ',');
61           // FIXME: This is dimension-sensitive
62           // FIXME: Also, inkscape doesn't handle units in dash lengths
63           bprintf(fb, "%.6g", l->dash_pattern[i]);
64         }
65       svg_fb_close_as_attr(svg, "stroke-dasharray");
66       if (l->dash_offset)
67         svg_set_attr_float(svg, "stroke-dashoffset", l->dash_offset);
68     }
69 }
70
71 static void append_node_list(struct svg *svg, clist *list)
72 {
73   struct osm_node *first = NULL;
74   OSM_FOR_EACH_BEGIN(struct osm_node *, n, *list)
75     {
76       if (!first)
77         {
78           first = n;
79           svg_path_move_to(svg, n->x, n->y);
80         }
81       else if (n == first)
82         svg_path_close(svg);
83       else
84         svg_path_line_to(svg, n->x, n->y);
85     }
86   OSM_FOR_EACH_END;
87 }
88
89 static void make_path(struct osm_object *o, struct svg *svg)
90 {
91   svg_push_path(svg);
92   switch (o->type)
93     {
94     case OSM_TYPE_WAY:
95       {
96         struct osm_way *w = (struct osm_way *) o;
97         append_node_list(svg, &w->nodes);
98         break;
99       }
100     case OSM_TYPE_MULTIPOLYGON:
101       {
102         struct osm_multipolygon *m = (struct osm_multipolygon *) o;
103         CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries)
104           append_node_list(svg, &b->nodes);
105         break;
106       }
107     default:
108       ASSERT(0);
109     }
110   svg_path_end(svg);
111 }
112
113 static void sym_line_draw(struct symbol *sym, struct svg *svg)
114 {
115   struct sym_line *l = (struct sym_line *) sym;
116   make_path(sym->o, svg);
117   sym_line_attrs(l, svg);
118   svg_pop(svg);
119 }
120
121 static void sym_line_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
122 {
123   if (o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON)
124     return;
125
126   struct sym_line *main_sl = NULL;
127
128   // Line and its casing are two similar objects
129   for (uns casing=0; casing<2; casing++)
130     {
131       double w;
132       if (!style_get_number(si, PROP_WIDTH + casing, &w))
133         continue;
134
135       struct sym_line *sl = sym_line_new(o);
136       sl->width = w;
137
138       if (casing)
139         {
140           if (!main_sl)
141             {
142               osm_obj_warn(o, "Casing around no line");
143               continue;
144             }
145           sl->width += main_sl->width;
146         }
147       else
148         main_sl = sl;
149
150       sl->color = 0x808080;
151       style_get_color(si, PROP_FILL_COLOR, &sl->color);
152       style_get_color(si, PROP_COLOR + casing, &sl->color);
153
154       sl->opacity = 1;
155       style_get_number(si, PROP_OPACITY + casing, &sl->opacity);
156
157       sl->line_cap = style_get_ident(si, PROP_LINECAP + casing) ? : VALUE_NONE;
158       sl->line_join = style_get_ident(si, PROP_LINEJOIN + casing) ? : VALUE_ROUND;
159       sl->miter_limit = 10;
160       style_get_number(si, PROP_MITERLIMIT + casing, &sl->miter_limit);
161
162       struct style_prop *dashes = style_get(si, PROP_DASHES + casing);
163       if (!dashes || dashes->type == PROP_TYPE_IDENT && dashes->val.id == VALUE_NONE)
164         ;
165       else if (dashes->type == PROP_TYPE_LIST)
166         {
167           GARY_INIT_ALLOC(sl->dash_pattern, 0, mp_get_allocator(sym_mp));
168           CLIST_FOR_EACH(struct style_val_list_entry *, e, *dashes->val.list)
169             {
170               if (e->val.type == PROP_TYPE_NUMBER)
171                 *GARY_PUSH(sl->dash_pattern) = e->val.val.number;
172               else
173                 {
174                   osm_obj_warn(o, "Invalid dash pattern");
175                   break;
176                 }
177             }
178           style_get_number(si, PROP_DASHES_OFFSET + casing, &sl->dash_offset);
179         }
180       else
181         osm_obj_warn(o, "Invalid dash pattern");
182
183       sym_plan(&sl->s, sym_zindex(o, si, casing ? 2 : 3));
184     }
185 }
186
187 static bool sym_line_same_p(struct symbol *xx, struct symbol *yy)
188 {
189   struct sym_line *x = (struct sym_line *) xx;
190   struct sym_line *y = (struct sym_line *) yy;
191
192   if (!(x->width == y->width &&
193         x->color == y->color &&
194         x->opacity == y->opacity &&
195         x->line_cap == y->line_cap &&
196         x->line_join == y->line_join &&
197         x->miter_limit == y->miter_limit &&
198         x->dash_offset == y->dash_offset))
199     return 0;
200
201   if (x->dash_pattern || y->dash_pattern)
202     {
203       if (!x->dash_pattern || !y->dash_pattern)
204         return 0;
205
206       if (GARY_SIZE(x->dash_pattern) != GARY_SIZE(y->dash_pattern))
207         return 0;
208
209       uint dashes = GARY_SIZE(x->dash_pattern);
210       for (uint i=0; i < dashes; i++)
211         if (x->dash_pattern[i] != y->dash_pattern[i])
212           return 0;
213     }
214
215   return 1;
216 }
217
218 static struct symbol *sym_line_clone(struct symbol *src)
219 {
220   struct sym_line *s = (struct sym_line *) src;
221   struct sym_line *d = sym_line_new(s->s.o);
222   memcpy((byte *)d + sizeof(struct symbol), (byte *)s + sizeof(struct symbol), sizeof(struct sym_line) - sizeof(struct symbol));
223   return &d->s;
224 }
225
226 struct symbolizer symbolizer_line = {
227   .name = "line",
228   .draw = sym_line_draw,
229   .gen = sym_line_gen,
230   .same_p = sym_line_same_p,
231   .clone = sym_line_clone,
232 };
233
234 struct sym_line *sym_line_new(struct osm_object *o)
235 {
236   return sym_new(SYMBOLIZER_LINE, o, sizeof(struct sym_line));
237 }
238
239 /*** Images along line ***/
240
241 static void lineimg_node_list(struct sym_lineimg *sli, clist *nodes, struct svg *svg)
242 {
243   double phase = sli->phase;
244   double step = sli->sir.width + sli->spacing;
245   struct osm_node *prev = NULL;
246   OSM_FOR_EACH_BEGIN(struct osm_node *, n, *nodes)
247     {
248       if (prev)
249         {
250           double dx = n->x - prev->x;
251           double dy = n->y - prev->y;
252           double len = hypot(dx, dy);
253           double dist = 0;
254           while (len - dist > 1e-10)
255             {
256               // FIXME: Rotate images along the path?
257               double next = MIN(len - dist, step - phase);
258               dist += next;
259               phase += next;
260               if (step - phase < 1e-10)
261                 {
262                   struct svg_icon_request sir = sli->sir;
263                   sir.x = prev->x + dx * dist / len;
264                   sir.y = prev->y + dy * dist / len;
265                   svg_icon_put(svg, &sir);
266                   phase -= step;
267                 }
268             }
269         }
270       prev = n;
271     }
272   OSM_FOR_EACH_END;
273 }
274
275 static void sym_lineimg_draw(struct symbol *sym, struct svg *svg)
276 {
277   struct sym_lineimg *li = (struct sym_lineimg *) sym;
278   struct osm_object *o = li->s.o;
279
280   switch (o->type)
281     {
282     case OSM_TYPE_WAY:
283       {
284         struct osm_way *w = (struct osm_way *) o;
285         lineimg_node_list(li, &w->nodes, svg);
286         break;
287       }
288     case OSM_TYPE_MULTIPOLYGON:
289       {
290         struct osm_multipolygon *m = (struct osm_multipolygon *) o;
291         CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries)
292           lineimg_node_list(li, &b->nodes, svg);
293         break;
294       }
295     default:
296       ASSERT(0);
297     }
298 }
299
300 static void sym_lineimg_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
301 {
302   if (o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON)
303     return;
304
305   osm_val_t icon_name = style_get_string(si, PROP_REPEAT_IMAGE);
306   if (!icon_name)
307     return;
308
309   struct sym_lineimg *sli = sym_lineimg_new(o);
310
311   struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(icon_name));
312   sli->sir.icon = icon;
313   sli->sir.width = icon->width;
314   sli->sir.height = icon->height;
315   style_scale(si, &sli->sir.width, &sli->sir.height, PROP_REPEAT_IMAGE_WIDTH, PROP_REPEAT_IMAGE_HEIGHT);
316
317   // FIXME: Better handling of defaults in style_get_number()
318 #if 0
319   // FIXME: align and offset are not supported yet
320   sli->align = style_get_ident(si, PROP_REPEAT_IMAGE_ALIGN);
321   sli->offset = 0;
322   style_get_number(si, PROP_REPEAT_IMAGE_OFFSET, &sli->offset);
323 #endif
324   sli->spacing = 0;
325   style_get_number(si, PROP_REPEAT_IMAGE_SPACING, &sli->spacing);
326   sli->phase = 0;
327   style_get_number(si, PROP_REPEAT_IMAGE_PHASE, &sli->phase);
328
329   sym_plan(&sli->s, sym_zindex(o, si, 3));
330 }
331
332 struct symbolizer symbolizer_lineimg = {
333   .name = "lineimg",
334   .draw = sym_lineimg_draw,
335   .gen = sym_lineimg_gen,
336 };
337
338 struct sym_lineimg *sym_lineimg_new(struct osm_object *o)
339 {
340   return sym_new(SYMBOLIZER_LINEIMG, o, sizeof(struct sym_lineimg));
341 }
342
343 /*** Areas ***/
344
345 static void sym_area_draw(struct symbol *sym, struct svg *svg)
346 {
347   struct sym_area *a = (struct sym_area *) sym;
348
349   for (int i=0; i<2; i++)
350     {
351       if (!i)
352         {
353           if (a->fill_color == COLOR_NONE)
354             continue;
355           make_path(sym->o, svg);
356           svg_set_attr_color(svg, "fill", a->fill_color);
357         }
358       else
359         {
360           if (!a->fill_pattern)
361             continue;
362           make_path(sym->o, svg);
363           svg_set_attr(svg, "fill", a->fill_pattern->paint_server);
364         }
365       if (a->fill_opacity != 1)
366         svg_set_attr_float(svg, "opacity", a->fill_opacity);
367       if (sym->o->type == OSM_TYPE_MULTIPOLYGON)
368         svg_set_attr(svg, "fill-rule", "evenodd");
369       svg_pop(svg);
370     }
371 }
372
373 static void sym_area_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED)
374 {
375   if (!(o->type == OSM_TYPE_WAY && osm_way_cyclic_p((struct osm_way *) o) ||
376         o->type == OSM_TYPE_MULTIPOLYGON))
377     return;
378
379   color_t color;
380   if (!style_get_color(si, PROP_FILL_COLOR, &color))
381     color = COLOR_NONE;
382
383   osm_val_t pattern = style_get_string(si, PROP_FILL_PATTERN);
384   struct svg_pattern *patt = NULL;
385   if (pattern)
386     {
387       struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(pattern));
388       struct svg_pattern_request spr = {
389         .icon = icon,
390         .width = icon->width,
391         .height = icon->height
392       };
393       style_scale(si, &spr.width, &spr.height, PROP_FILL_PATTERN_WIDTH, PROP_FILL_PATTERN_HEIGHT);
394       patt = svg_icon_to_pattern(svg, &spr);
395     }
396
397   if (color == COLOR_NONE && !patt)
398     return;
399
400   struct sym_area *sa = sym_area_new(o);
401   sa->fill_color = color;
402   sa->fill_pattern = patt;
403
404   sa->fill_opacity = 1;
405   style_get_number(si, PROP_FILL_OPACITY, &sa->fill_opacity);
406
407   sym_plan(&sa->s, sym_zindex(o, si, 1));
408 }
409
410 struct symbolizer symbolizer_area = {
411   .name = "area",
412   .draw = sym_area_draw,
413   .gen = sym_area_gen,
414 };
415
416 struct sym_area *sym_area_new(struct osm_object *o)
417 {
418   return sym_new(SYMBOLIZER_AREA, o, sizeof(struct sym_area));
419 }