From: Martin Mares Date: Sun, 15 Jun 2014 18:34:29 +0000 (+0200) Subject: Imported experimental version from poskole2014.git X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;ds=sidebyside;h=44b0ec8816c3cf1a5858fec0c51e075cedefc3eb;p=leo.git Imported experimental version from poskole2014.git --- 44b0ec8816c3cf1a5858fec0c51e075cedefc3eb diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c0b1642 --- /dev/null +++ b/Makefile @@ -0,0 +1,63 @@ +LIBUCW:=$(shell cd /home/mj/src/libucw/run && pwd) +UCWCF:=$(shell PKG_CONFIG_PATH=$(LIBUCW)/lib/pkgconfig pkg-config --cflags libucw libucw-charset libucw-xml) +UCWLF:=$(shell PKG_CONFIG_PATH=$(LIBUCW)/lib/pkgconfig pkg-config --libs libucw libucw-charset libucw-xml) +FTCF:=$(shell freetype-config --cflags) +FTLF:=$(shell freetype-config --libs) +PANGOCF:=$(shell pkg-config pangoft2 --cflags) +PANGOLF:=$(shell pkg-config pangoft2 --libs) + +CC=gcc +LD=gcc +COPT=-O2 +CFLAGS=$(COPT) -Wall -W -Wno-parentheses -Wstrict-prototypes -Wmissing-prototypes -Wundef -Wredundant-decls -Wno-missing-field-initializers -std=gnu99 $(UCWCF) -ggdb -DDEBUG_ASSERTS +LDLIBS+=$(FTLF) $(PANGOLF) $(UCWLF) -lproj + +all: leo + +leo: leo.o xml.o osm.o svg.o svg-icon.o css-parse.o css-lex.o style.o css.o dict.o sym.o sym-point.o sym-line.o sym-text.o map.o shp.o + +INC=leo.h dict-keys.h dict-values.h dict-props.h osm.h svg.h style.h css.h dict.h map.h sym.h + +leo.o: leo.c $(INC) +xml.o: xml.c $(INC) +osm.o: osm.c $(INC) +svg.o: svg.c $(INC) +svg-icon.o: svg-icon.c $(INC) +style.o: style.c $(INC) +css.o: css.c $(INC) +dict.o: dict.c $(INC) +sym.o: sym.c $(INC) +sym-point.o: sym-point.c $(INC) +sym-line.o: sym-line.c $(INC) +sym-text.o: sym-text.c $(INC) +css-parse.o: css-parse.c $(INC) +css-lex.o: css-lex.c $(INC) css-parse.c +map.o: map.c $(INC) +shp.o: shp.c $(INC) + +sym-text.o: CFLAGS+=$(FTCF) $(PANGOCF) + +css-parse.c: css-parse.y + bison --name-prefix=css_ --token-table --verbose --defines -o $@ $^ + +dict-%.h: dict-%.t gen-dict + ./gen-dict <$< >$@ + +clean: + rm -f leo *.o css-parse.c css-parse.h css-parse.output tags + rm -f dict-keys.h dict-values.h dict-props.h + +tags: + ctags *.[chy] + +upload: + rs output.pdf ps:poskole-beta/www/tmp/2014/mapa.pdf + +backup: + rs . camelot:a/priv/poskole/map/new/$$(date '+%Y%m%d-%H%M%S')/ + +output.svg: leo dump.osm poskole.css + ./leo + +output.pdf: output.svg + inkscape --export-pdf=output.pdf output.svg diff --git a/TODO b/TODO new file mode 100644 index 0000000..a291aa1 --- /dev/null +++ b/TODO @@ -0,0 +1,20 @@ +- bring CSS lexer to the CSS2.1 spec (http://www.w3.org/TR/CSS2/syndata.html) +- CSS override rules are broken. For example, text-offset-y and text-offset + should override each other, but currently they are mixed upon rendering. + Does it matter? +- Fixing of label positions should include some margin +- _BRUM_XXX_H -> _LEO_XXX_H + +- Interesting tags: +amenity = kindergarten +bridge = yes +historic = archaeological_site +place = islet +railway = crossing +railway = level_crossing +tunnel = culvert +tunnel = yes + +=== Opravit v OSM === + +* Rozlišovat kostely a kaple diff --git a/css-lex.c b/css-lex.c new file mode 100644 index 0000000..754c526 --- /dev/null +++ b/css-lex.c @@ -0,0 +1,251 @@ +/* + * Experimenta lMai Renderer -- MapCSS Lexer + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include +#include + +#include + +#include "leo.h" +#include "style.h" +#include "css.h" +#include "css-parse.h" + +static struct fastbuf *fb; +static int lino; + +void css_error(char *err, ...) +{ + va_list args; + va_start(args, err); + char *msg = mp_vprintf(css_this->pool, err, args); + die("Error in %s, line %d: %s", css_this->filename, lino, msg); +} + +void css_lex_open(void) +{ + fb = bopen_file(css_this->filename, O_RDONLY, NULL); + lino = 1; +} + +void css_lex_close(void) +{ + bclose(fb); +} + +int css_lex(void) +{ + struct mempool *mp = css_this->pool; + char *p; + int c, next, len; + + for (;;) + { + c = bgetc(fb); + if (c < 0) + return 0; + if (c == '\n') + { + lino++; + continue; + } + if (c == ' ' || c == '\t') + continue; + + next = bpeekc(fb); + + if (c >= '0' && c <= '9' || c == '-' && next >= '0' && next <= '9') + { + // Number + p = mp_start(mp, 1); + if (c == '-') + { + p = mp_append_char(mp, p, c); + c = bgetc(fb); + } + while (c >= '0' && c <= '9') + { + p = mp_append_char(mp, p, c); + c = bgetc(fb); + } + if (c == '.') + { + p = mp_append_char(mp, p, c); + c = bgetc(fb); + while (c >= '0' && c <= '9') + { + p = mp_append_char(mp, p, c); + c = bgetc(fb); + } + } + p = mp_end_string(mp, p); + if (c >= 0) + bungetc(fb); + css_lval.s = p; + return NUMBER; + } + + if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c == '_') + { + // Alphabetical identifier + // FIXME: Identifiers starting with "-" are not supported + // FIXME: Unquoted identifiers should be always case-insensitive + p = mp_start(mp, 1); + while (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || c >= '0' && c <= '9' || c == '-' || c == '_') + { + p = mp_append_char(mp, p, c); + c = bgetc(fb); + } + p = mp_end_string(mp, p); + if (c >= 0) + bungetc(fb); + css_lval.s = p; + return IDENT; + } + + switch (c) + { + case '"': + // Quoted string + p = mp_start(mp, 1); + c = bgetc(fb); + while (c != '"') + { + if (c < 0) + css_error("Unterminated string literal"); + if (c == '\\') + { + c = bgetc(fb); + if (c == '\\' || c == '"') + ; + else if (c == 'n') + c = '\n'; + else + css_error("Unknown backslash sequence \"\\%c\" in string literal", c); + } + p = mp_append_char(mp, p, c); + c = bgetc(fb); + } + css_lval.s = mp_end_string(mp, p); + return QUOTED; + + case '/': + if (next == '*') + { + // C comment + bgetc(fb); + c = bgetc(fb); + for (;;) + { + if (c < 0) + css_error("Unterminated comment"); + if (c == '\n') + { + lino++; + c = bgetc(fb); + } + else if (c == '*') + { + c = bgetc(fb); + if (c == '/') + break; + } + else + c = bgetc(fb); + } + continue; + } + else if (next == '/') + { + // C++ comment + do + c = bgetc(fb); + while (c >= 0 && c != '\n'); + lino++; + continue; + } + return '/'; + + case '#': + p = mp_start(mp, 1); + while (next >= '0' && next <= '9' || next >= 'a' && next <= 'f' || next >= 'A' && next <= 'F') + { + p = mp_append_char(mp, p, bgetc(fb)); + next = bpeekc(fb); + } + p = mp_end_string(mp, p); + len = strlen(p); + if (len != 3 && len != 6) + css_error("Invalid RGB literal"); + css_lval.s = p; + return RGB; + + // One-character operators + case '{': + case '}': + case '[': + case ']': + case ';': + case ',': + case '*': + case '.': + case '=': + case '?': + return c; + + // Two-character operators + case '!': + if (next == '=') + { + bgetc(fb); + return NE; + } + return '!'; + case '<': + if (next == '=') + { + bgetc(fb); + return LE; + } + return '<'; + case '>': + if (next == '=') + { + bgetc(fb); + return GE; + } + return '>'; + case ':': + if (next == ':') + { + bgetc(fb); + return CC; + } + return ':'; + + default: + css_error("Invalid character \"%c\"", c); + } + } +} + +color_t css_rgb_to_color(const char *rgb) +{ + uns n = strlen(rgb); + ASSERT(n == 3 || n == 6); + color_t color = 0; + for (uns i=0; i + */ + +%{ +#include +#include + +#include +#include + +#include "leo.h" +#include "css.h" + +static void *css_alloc(size_t n) +{ + return mp_alloc_zero(css_this->pool, n); +} + +static struct css_path *css_new_path(void) +{ + struct css_path *p = css_alloc(sizeof(struct css_path)); + clist_init(&p->conditions); + p->layer = STYLE_LAYER_DEFAULT; + return p; +} + +static void css_add_to_val_list(struct style_prop *list, struct style_prop *elt) +{ + struct style_val_list_entry *e = css_alloc(sizeof(*e)); + clist_add_tail(list->val.list, &e->n); + e->val = *elt; +} + +%} + +%union { + char *s; + struct css_rule *rule; + struct css_selector *selector; + struct css_path *path; + struct css_condition *condition; + struct css_action *action; + struct style_prop prop; + enum css_cond_op cond_op; +} + +%token LE GE NE CC +%token NUMBER IDENT QUOTED RGB + +%type ident_or_quoted +%type rule rule_start rule_selectors rule_start_actions rule_actions +%type selector selector_start +%type path path_start path_conditions +%type condition +%type action +%type prop_value prop_value_single prop_value_list +%type binary_op + +%% + +input: + /* empty */ + | input rule { clist_add_tail(&css_this->rules, &$2->n); } + ; + +rule_start: + /* empty */ + { + $$ = css_alloc(sizeof(struct css_rule)); + clist_init(&$$->selectors); + clist_init(&$$->actions); + } + ; + +rule_selectors: + rule_start selector + { + clist_add_tail(&$1->selectors, &$2->n); + $$ = $1; + } + | rule_selectors ',' selector + { + clist_add_tail(&$1->selectors, &$3->n); + $$ = $1; + } + ; + +rule_start_actions: + rule_start + | rule_selectors + ; + +rule_actions: + rule_start_actions '{' + { + $$ = $1; + } + | rule_actions action ';' + { + $$ = $1; + clist_add_tail(&$$->actions, &$2->n); + } + ; + +rule: rule_actions '}' ; + +selector_start: + /* empty */ + { + $$ = css_alloc(sizeof(struct css_selector)); + clist_init(&$$->path); + } + ; + +selector: + selector_start path + { + clist_add_tail(&$1->path, &$2->n); + $$ = $1; + } + | selector path + { + // FIXME: Descendant operator + clist_add_tail(&$1->path, &$2->n); + $$ = $1; + } + ; + +path_start: + IDENT + { + $$ = css_new_path(); + if (!strcmp($1, "node")) + $$->type = CSS_TYPE_NODE; + else if (!strcmp($1, "way")) + $$->type = CSS_TYPE_WAY; + else if (!strcmp($1, "relation")) + $$->type = CSS_TYPE_RELATION; + else if (!strcmp($1, "area")) + $$->type = CSS_TYPE_AREA; + else if (!strcmp($1, "meta")) + $$->type = CSS_TYPE_META; + else + die("Invalid selector %s", $1); + } + | '*' + { + $$ = css_new_path(); + $$->type = CSS_TYPE_ANY; + } + ; + +path_conditions: + path_start + | path_conditions '[' condition ']' + { + $$ = $1; + clist_add_tail(&$$->conditions, &$3->n); + } + ; + +path: + path_conditions + | path ':' IDENT + { + // So far, no modifiers are supported, so a rule with modifiers never matches + $$ = $1; + $$->modifiers |= PATH_MOD_NEVER; + } + | path CC IDENT + { + // Layer + $$ = $1; + $$->layer = style_layer_encode($3); + } + | path CC '*' + { + // All layers + $$ = $1; + $$->layer = STYLE_LAYER_ALL; + } + ; + +condition: + ident_or_quoted binary_op ident_or_quoted + { + $$ = css_alloc(sizeof(struct css_condition)); + $$->op = $2; + $$->key = osm_key_encode($1); + $$->val = osm_val_encode($3); + } + | ident_or_quoted + { + $$ = css_alloc(sizeof(struct css_condition)); + $$->op = COND_OP_IS_SET; + $$->key = osm_key_encode($1); + } + | '!' ident_or_quoted + { + $$ = css_alloc(sizeof(struct css_condition)); + $$->op = COND_OP_IS_UNSET; + $$->key = osm_key_encode($2); + } + | ident_or_quoted '?' + { + $$ = css_alloc(sizeof(struct css_condition)); + $$->op = COND_OP_IS_TRUE; + $$->key = osm_key_encode($1); + } + | ident_or_quoted '?' '!' + { + $$ = css_alloc(sizeof(struct css_condition)); + $$->op = COND_OP_IS_FALSE; + $$->key = osm_key_encode($1); + } + ; + +binary_op: + '=' { $$ = COND_OP_EQ; } + | '<' { $$ = COND_OP_LT; } + | '>' { $$ = COND_OP_GT; } + | LE { $$ = COND_OP_LE; } + | GE { $$ = COND_OP_GE; } + | NE { $$ = COND_OP_NE; } + ; + +action: + ident_or_quoted ':' prop_value + { + $$ = css_alloc(sizeof(struct css_action)); + $$->prop = $3; + $$->prop.key = style_prop_encode($1); + } + ; + +prop_value: + prop_value_single + | prop_value_list + ; + +prop_value_single: + IDENT + { + $$.type = PROP_TYPE_IDENT; + $$.val.id = osm_val_encode($1); + } + | QUOTED + { + $$.type = PROP_TYPE_STRING; + $$.val.id = osm_val_encode($1); + } + | NUMBER + { + $$.type = PROP_TYPE_NUMBER; + $$.val.number = atof($1); + } + | RGB + { + $$.type = PROP_TYPE_COLOR; + $$.val.color = css_rgb_to_color($1); + } + ; + +prop_value_list: + prop_value_single ',' prop_value_single + { + $$.type = PROP_TYPE_LIST; + $$.val.list = css_alloc(sizeof(clist)); + clist_init($$.val.list); + css_add_to_val_list(&$$, &$1); + css_add_to_val_list(&$$, &$3); + } + | prop_value_list ',' prop_value_single + { + $$ = $1; + css_add_to_val_list(&$$, &$3); + } + ; + +ident_or_quoted: IDENT | QUOTED ; + +%% + +struct css_sheet *css_this; + +struct css_sheet *css_load(char *filename) +{ + struct mempool *mp = mp_new(4096); + struct css_sheet *ss = mp_alloc_zero(mp, sizeof(*ss)); + ss->pool = mp; + clist_init(&ss->rules); + ss->filename = mp_strdup(mp, filename); + + css_this = ss; + css_lex_open(); + css_parse(); + + css_lex_close(); + css_this = NULL; + return ss; +} diff --git a/css.c b/css.c new file mode 100644 index 0000000..bfff47d --- /dev/null +++ b/css.c @@ -0,0 +1,178 @@ +/* + * Hic Est Leo -- MapCSS Stylesheets + * + * (c) 2014 Martin Mares + */ + +#include +#include + +#include + +#include "leo.h" +#include "style.h" +#include "css.h" + +void css_dump(struct css_sheet *ss) +{ + printf("Style sheet <%s>:\n", ss->filename); + CLIST_FOR_EACH(struct css_rule *, r, ss->rules) + css_dump_rule(r); +} + +void css_dump_rule(struct css_rule *r) +{ + printf("Rule:\n"); + CLIST_FOR_EACH(struct css_selector *, s, r->selectors) + css_dump_selector(s); + CLIST_FOR_EACH(struct css_action *, a, r->actions) + css_dump_action(a); +} + +void css_dump_selector(struct css_selector *s) +{ + printf("\tSelector:\n"); + CLIST_FOR_EACH(struct css_path *, p, s->path) + { + printf("\t\tMatch type=%u layer=%u role=%u mod=%u\n", p->type, p->layer, p->role, p->modifiers); + CLIST_FOR_EACH(struct css_condition *, c, p->conditions) + { + printf("\t\t\tCondition %u key=%s val=%s\n", c->op, osm_key_decode(c->key), osm_val_decode(c->val)); + } + } +} + +void css_dump_action(struct css_action *a) +{ + printf("\tAction: "); + style_dump_prop(&a->prop); +} + +static bool css_match_condition(struct css_condition *c, struct osm_object *o) +{ + osm_val_t val = osm_obj_find_tag(o, c->key); + + switch (c->op) + { + case COND_OP_EQ: + return (val == c->val); // FIXME: Case sensitivity? + case COND_OP_NE: + return (val != c->val); + case COND_OP_LT: + // FIXME + return 0; + case COND_OP_GT: + // FIXME + return 0; + case COND_OP_LE: + // FIXME + return 0; + case COND_OP_GE: + // FIXME + return 0; + case COND_OP_IS_SET: + return val; + case COND_OP_IS_UNSET: + return !val; + case COND_OP_IS_TRUE: + return (val == VALUE_1 || val == VALUE_YES || val == VALUE_TRUE); + case COND_OP_IS_FALSE: + return !(val == VALUE_1 || val == VALUE_YES || val == VALUE_TRUE); + default: + ASSERT(0); + } +} + +static bool css_match_path_component(struct css_path *p, struct osm_object *o) +{ + switch (p->type) + { + case CSS_TYPE_ANY: + break; + case CSS_TYPE_NODE: + case CSS_TYPE_WAY: + case CSS_TYPE_RELATION: + if (o->type != p->type) + return 0; + break; + case CSS_TYPE_AREA: + if (o->type == OSM_TYPE_WAY && osm_way_cyclic_p((struct osm_way *) o)) + break; + if (o->type == OSM_TYPE_MULTIPOLYGON) + break; + return 0; + default: + return 0; + } + + if (p->modifiers & PATH_MOD_NEVER) + return 0; + + CLIST_FOR_EACH(struct css_condition *, c, p->conditions) + if (!css_match_condition(c, o)) + return 0; + + return 1; +} + +static bool css_match_path(struct css_selector *s, struct css_path *p, struct osm_object *o); + +static bool css_match_path_subtree(struct css_selector *s, struct css_path *p, struct osm_object *o) +{ + CLIST_FOR_EACH(struct osm_ref *, ref, o->backrefs) + if (css_match_path(s, p, ref->o)) + return 1; + + CLIST_FOR_EACH(struct osm_ref *, ref, o->backrefs) + if (css_match_path_subtree(s, p, ref->o)) + return 1; + + return 0; +} + +static bool css_match_path(struct css_selector *s, struct css_path *p, struct osm_object *o) +{ + if (!css_match_path_component(p, o)) + return 0; + + p = clist_prev(&s->path, &p->n); + if (!p) + return 1; + + return css_match_path_subtree(s, p, o); +} + +static bool css_match_selector(struct css_selector *s, struct style_results *r) +{ + struct css_path *p = clist_tail(&s->path); + ASSERT(p); + return css_match_path(s, p, r->obj); +} + +static void css_apply_action(struct css_action *sa, struct style_results *r, layer_t layer) +{ + style_set_by_layer(r, layer, &sa->prop); +} + +void css_apply(struct css_sheet *ss, struct style_results *r) +{ + CLIST_FOR_EACH(struct css_rule *, sr, ss->rules) + { + layer_t last_layer = ~0U; + CLIST_FOR_EACH(struct css_selector *, sel, sr->selectors) + { + if (css_match_selector(sel, r)) + { + // XXX: Rules with selectors in multiple layers could be further optimized + struct css_path *cp = clist_tail(&sel->path); + layer_t layer = cp ? cp->layer : STYLE_LAYER_DEFAULT; + if (layer != last_layer) + { + last_layer = layer; + CLIST_FOR_EACH(struct css_action *, sa, sr->actions) + css_apply_action(sa, r, layer); + } + } + } + } +} diff --git a/css.h b/css.h new file mode 100644 index 0000000..d35c0b3 --- /dev/null +++ b/css.h @@ -0,0 +1,101 @@ +/* + * Hic Est Leo -- MapCSS Stylesheets + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_CSS_H +#define _BRUM_CSS_H + +#include "osm.h" +#include "style.h" + +struct css_sheet { + struct mempool *pool; + clist rules; + char *filename; +}; + +struct css_rule { + cnode n; // In the list of rules + clist selectors; // Alternative selectors + clist actions; +}; + +struct css_selector { + cnode n; + clist path; // Element path +}; + +enum css_object_type { + CSS_TYPE_ANY = OSM_TYPE_INVALID, + CSS_TYPE_NODE = OSM_TYPE_NODE, + CSS_TYPE_WAY = OSM_TYPE_WAY, + CSS_TYPE_RELATION = OSM_TYPE_RELATION, + CSS_TYPE_AREA, + CSS_TYPE_META, +}; + +struct css_path { + cnode n; + enum osm_object_type type; // Object type or OSM_TYPE_ANY + clist conditions; // Attribute conditions + layer_t layer; // STYLE_LAYER_xxx or more + osm_val_t role; + uns modifiers; +}; + +enum css_path_modifier { + PATH_MOD_NEVER = 1, // Unknown modifier, never match +}; + +enum css_cond_op { + COND_OP_EQ, + COND_OP_NE, + COND_OP_LT, + COND_OP_GT, + COND_OP_LE, + COND_OP_GE, + COND_OP_IS_SET, + COND_OP_IS_UNSET, + COND_OP_IS_TRUE, + COND_OP_IS_FALSE, +}; + +struct css_condition { + cnode n; + enum css_cond_op op; + osm_key_t key; + osm_val_t val; +}; + +struct css_action { + cnode n; + struct style_prop prop; +}; + +/* css-parse.y */ + +extern struct css_sheet *css_this; + +struct css_sheet *css_load(char *filename); + +/* css-lex.c */ + +void css_error(char *err, ...); +int css_lex(void); +void css_lex_open(void); +void css_lex_close(void); + +color_t css_rgb_to_color(const char *rgb); + +/* css.c */ + +void css_dump(struct css_sheet *ss); +void css_dump_rule(struct css_rule *r); +void css_dump_selector(struct css_selector *s); +void css_dump_action(struct css_action *a); + +void css_apply(struct css_sheet *ss, struct style_results *r); + +#endif diff --git a/cut b/cut new file mode 100644 index 0000000..36a0eee --- /dev/null +++ b/cut @@ -0,0 +1,2 @@ +osmosis --read-xml file=czech_republic-2014-05-18.osm.gz --log-progress --bounding-box top=50.17 bottom=50.12 left=14.50 right=14.60 completeWays=yes --write-xml +# maybe also completeRelations=yes diff --git a/dict-keys.t b/dict-keys.t new file mode 100644 index 0000000..8cef811 --- /dev/null +++ b/dict-keys.t @@ -0,0 +1,12 @@ +# List of OSM tag keys, parsed by ./gen-dict +# Please keep sorted + +addr:housenumber +brand +highway +leisure +name +name:cz +operator +ref +type diff --git a/dict-props.t b/dict-props.t new file mode 100644 index 0000000..a164805 --- /dev/null +++ b/dict-props.t @@ -0,0 +1,77 @@ +# List of style property keys, parsed by ./gen-dict +# Please keep sorted + +casing-major-z-index +fill-color +fill-image +fill-opacity +fill-pattern +fill-pattern-width +fill-pattern-height +fill-position +font-family +font-size +font-style +font-weight +icon-height +icon-image +icon-opacity +icon-width +major-z-index +object-z-index +repeat-image +repeat-image-align +repeat-image-height +repeat-image-offset +repeat-image-spacing +repeat-image-phase +repeat-image-width +symbol-fill-color +symbol-fill-opacity +symbol-shape +symbol-size +symbol-stroke-color +symbol-stroke-opacity +symbol-stroke-width +text +text-anchor-horizontal +text-anchor-vertical +text-color +text-dup-threshold +text-halo-color +text-halo-opacity +text-halo-radius +text-major-z-index +text-offset +text-offset-x +text-offset-y +text-opacity +text-position +z-index + +# The following properties come in couples, which must not be split +# (for reasons, see their use in sym-gen.c) + +color +casing-color + +dashes +casing-dashes + +dashes-offset +casing-dashes-offset + +linecap +casing-linecap + +linejoin +casing-linejoin + +miterlimit +casing-miterlimit + +opacity +casing-opacity + +width +casing-width diff --git a/dict-values.t b/dict-values.t new file mode 100644 index 0000000..1d30763 --- /dev/null +++ b/dict-values.t @@ -0,0 +1,30 @@ +# List of OSM tag values, parsed by ./gen-dict +# Please keep sorted + +0 +1 +above +auto +below +bevel +bold +bottom +center +circle +false +inner +italic +left +line +miter +multipolygon +no +none +normal +outer +right +round +square +top +true +yes diff --git a/dict.c b/dict.c new file mode 100644 index 0000000..a3679e7 --- /dev/null +++ b/dict.c @@ -0,0 +1,63 @@ +/* + * Hic Est Leo -- Universal Dictionaries + * + * (c) 2014 Martin Mares + */ + +#include +#include + +#include "leo.h" +#include "dict.h" + +struct kv_map { + u32 id; + char *name; +}; + +#define HASH_NODE struct kv_map +#define HASH_PREFIX(x) kv_map_##x +#define HASH_KEY_STRING name +#define HASH_WANT_LOOKUP +#define HASH_AUTO_POOL 4096 +#define HASH_ZERO_FILL +#define HASH_TABLE_DYNAMIC +#define HASH_GIVE_ALLOC +#define HASH_TABLE_VARS struct mempool *kv_pool; +static void *kv_map_alloc(struct kv_map_table *table, uns size); +#include + +static void *kv_map_alloc(struct kv_map_table *table, uns size) +{ + return mp_alloc(table->kv_pool, size); +} + +void dict_init(struct dict *dict, const char * const *init) +{ + GARY_INIT(dict->names, 1); + dict->pool = mp_new(4096); + dict->hash = mp_alloc_zero(dict->pool, sizeof(struct kv_map_table)); + dict->hash->kv_pool = dict->pool; + kv_map_init(dict->hash); + + if (init) + { + for (uns i=1; init[i]; i++) + { + u32 id = dict_encode(dict, init[i]); + ASSERT(id == i); + } + } +} + +u32 dict_encode(struct dict *d, const char *key) +{ + struct kv_map *k = kv_map_lookup(d->hash, (char *) key); + if (k->id) + return k->id; + + k->id = GARY_SIZE(d->names); + k->name = mp_strdup(d->pool, key); + *GARY_PUSH(d->names) = k->name; + return k->id; +} diff --git a/dict.h b/dict.h new file mode 100644 index 0000000..57b94b1 --- /dev/null +++ b/dict.h @@ -0,0 +1,33 @@ +/* + * Hic Est Leo -- Universal Dictionaries + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_DICT_H +#define _BRUM_DICT_H + +#include + +struct dict { + const char **names; // Growing array of names + struct kv_map_table *hash; // Hash table mapping name -> ID + struct mempool *pool; +}; + +void dict_init(struct dict *dict, const char * const * init); + +static inline const char *dict_decode(struct dict *d, u32 id) +{ + ASSERT(id < GARY_SIZE(d->names)); + return d->names[id]; +} + +u32 dict_encode(struct dict *d, const char *key); + +static inline u32 dict_size(struct dict *d) +{ + return GARY_SIZE(d->names); +} + +#endif diff --git a/gen-dict b/gen-dict new file mode 100755 index 0000000..a221ec4 --- /dev/null +++ b/gen-dict @@ -0,0 +1,16 @@ +#!/usr/bin/perl +# Generate dict-*.h from dict-*.t + +use strict; +use warnings; + +print "// Auto-generated by gen-dict\n"; +while () { + chomp; + next if /^$/ or /^#/; + my $prop = $_; + my $sym = $_; + $sym =~ s{[^A-Za-z0-9_]}{_}g; + $sym =~ tr{a-z}{A-Z}; + print "P($sym, \"$prop\")\n"; +} diff --git a/icons/bus_stop.svg b/icons/bus_stop.svg new file mode 100644 index 0000000..2694b46 --- /dev/null +++ b/icons/bus_stop.svg @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/bus_stop2.svg b/icons/bus_stop2.svg new file mode 100644 index 0000000..629a141 --- /dev/null +++ b/icons/bus_stop2.svg @@ -0,0 +1,35 @@ + + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/icons/cave.svg b/icons/cave.svg new file mode 100644 index 0000000..ca69d24 --- /dev/null +++ b/icons/cave.svg @@ -0,0 +1,130 @@ + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/cave2.svg b/icons/cave2.svg new file mode 100644 index 0000000..27f01f9 --- /dev/null +++ b/icons/cave2.svg @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/icons/cemetery.svg b/icons/cemetery.svg new file mode 100644 index 0000000..576935b --- /dev/null +++ b/icons/cemetery.svg @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/icons/church.svg b/icons/church.svg new file mode 100644 index 0000000..0680a16 --- /dev/null +++ b/icons/church.svg @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/icons/cliff.svg b/icons/cliff.svg new file mode 100644 index 0000000..b773f92 --- /dev/null +++ b/icons/cliff.svg @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/icons/cross.svg b/icons/cross.svg new file mode 100644 index 0000000..e0468e0 --- /dev/null +++ b/icons/cross.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/icons/deciduous.svg b/icons/deciduous.svg new file mode 100644 index 0000000..300b30d --- /dev/null +++ b/icons/deciduous.svg @@ -0,0 +1,318 @@ + + + + + + +image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/fuel.svg b/icons/fuel.svg new file mode 100644 index 0000000..c225750 --- /dev/null +++ b/icons/fuel.svg @@ -0,0 +1,195 @@ + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/gate.svg b/icons/gate.svg new file mode 100644 index 0000000..ad79d21 --- /dev/null +++ b/icons/gate.svg @@ -0,0 +1,231 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + diff --git a/icons/guidepost.svg b/icons/guidepost.svg new file mode 100644 index 0000000..50751b5 --- /dev/null +++ b/icons/guidepost.svg @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/icons/logo.svg b/icons/logo.svg new file mode 100644 index 0000000..10b0164 --- /dev/null +++ b/icons/logo.svg @@ -0,0 +1,287 @@ + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/memorial.svg b/icons/memorial.svg new file mode 100644 index 0000000..ea1fa4d --- /dev/null +++ b/icons/memorial.svg @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + diff --git a/icons/meritko.svg b/icons/meritko.svg new file mode 100644 index 0000000..c9ac553 --- /dev/null +++ b/icons/meritko.svg @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icons/power_pole.svg b/icons/power_pole.svg new file mode 100644 index 0000000..559010c --- /dev/null +++ b/icons/power_pole.svg @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/icons/train.svg b/icons/train.svg new file mode 100644 index 0000000..c4dde63 --- /dev/null +++ b/icons/train.svg @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + + + diff --git a/icons/view_point.svg b/icons/view_point.svg new file mode 100644 index 0000000..846886f --- /dev/null +++ b/icons/view_point.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + +image/svg+xml + + + +en + + + + + + + + + + + + + + + + + + + + diff --git a/icons/wetland.svg b/icons/wetland.svg new file mode 100644 index 0000000..de6e907 --- /dev/null +++ b/icons/wetland.svg @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/leo.c b/leo.c new file mode 100644 index 0000000..10092d6 --- /dev/null +++ b/leo.c @@ -0,0 +1,253 @@ +/* + * Hic Est Leo -- Main Program + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include + +#include + +#include "leo.h" +#include "osm.h" +#include "svg.h" +#include "style.h" +#include "css.h" +#include "sym.h" +#include "map.h" + +#undef ROTATE + +uns debug_dump_source, debug_dump_after_proj, debug_dump_after_scaling; +uns debug_dump_multipolygons, debug_dump_css, debug_dump_styling, debug_dump_symbols; + +static struct cf_section debug_cf = { + CF_ITEMS { + CF_UNS("DumpSource", &debug_dump_source), + CF_UNS("DumpAfterProj", &debug_dump_after_proj), + CF_UNS("DumpAfterScaling", &debug_dump_after_scaling), + CF_UNS("DumpMultipolygons", &debug_dump_multipolygons), + CF_UNS("DumpCSS", &debug_dump_css), + CF_UNS("DumpStyling", &debug_dump_styling), + CF_UNS("DumpSymbols", &debug_dump_symbols), + CF_END + } +}; + +static const struct opt_section options = { + OPT_ITEMS { + OPT_HELP("Hic Est Leo -- Experimental Map Renderer"), + OPT_HELP(""), + OPT_HELP("Options:"), + OPT_HELP_OPTION, + OPT_CONF_OPTIONS, + OPT_END + } +}; + +// FIXME: Make generic +static void draw_scale(struct svg *svg) +{ + double dist = 1000; + double width = dist * map_scale; + double x = page_width - 10 - width; + double y = 50; + + svg_push_element(svg, "g"); + svg_set_attr(svg, "id", "scale"); + svg_set_attr_format(svg, "transform", "translate(%.6g,%.6g)", x * svg->scale, y * svg->scale); + + for (int outline=1; outline>=0; outline--) + { + svg_push_element(svg, "g"); + svg_set_attr(svg, "stroke-linecap", "square"); + if (outline) + { + svg_set_attr_dimen(svg, "stroke-width", 1.5); + svg_set_attr_color(svg, "stroke", 0xffffff); + } + else + { + svg_set_attr_dimen(svg, "stroke-width", 0.5); + svg_set_attr_color(svg, "stroke", 0); + } + + svg_push_element(svg, "line"); + svg_set_attr_dimen(svg, "x1", 0); + svg_set_attr_dimen(svg, "y1", 0); + svg_set_attr_dimen(svg, "x2", width); + svg_set_attr_dimen(svg, "y2", 0); + svg_pop(svg); + + for (int i=0; i<=10; i++) + { + double tick; + switch (i) + { + case 0: + case 10: + tick = 3; + break; + case 5: + tick = 2; + break; + default: + tick = 1; + } + svg_push_element(svg, "line"); + svg_set_attr_dimen(svg, "x1", width * i/10); + svg_set_attr_dimen(svg, "y1", 0); + svg_set_attr_dimen(svg, "x2", width * i/10); + svg_set_attr_dimen(svg, "y1", -tick); + svg_pop(svg); + } + + svg_pop(svg); + } + + scale_text(svg, 0, 5, osm_val_encode("0")); + scale_text(svg, width, 5, osm_val_encode("1 km")); + svg_pop(svg); +} + +int main(int argc UNUSED, char **argv) +{ + cf_def_file = "map.cf"; + cf_declare_section("Debug", &debug_cf, 0); + opt_parse(&options, argv+1); + + osm_init(); + styles_init(); + + msg(L_INFO, "Parsing OSM"); + osm_xml_parse(map_xml_input); + if (debug_dump_source) + { + puts("=== Source data ==="); + osm_dump(); + } + osm_make_multipolygons(); + + msg(L_INFO, "Projecting"); + osm_project(map_projection); + if (debug_dump_after_proj) + { + puts("=== Map after projection ==="); + osm_dump(); + } + + map_set_scale(); + if (debug_dump_after_scaling) + { + puts("=== Map after scaling ==="); + osm_dump(); + } + + struct css_sheet *ss = css_load(map_style_sheet); + if (debug_dump_css) + { + puts("=== Stylesheet ==="); + css_dump(ss); + } + + struct svg *svg = svg_open(map_svg_output); +#ifndef ROTATE + svg_set_attr_dimen(svg, "width", page_width); + svg_set_attr_dimen(svg, "height", page_height); +#else + svg_set_attr_dimen(svg, "width", page_height); + svg_set_attr_dimen(svg, "height", page_width); +#endif + + struct style_results r; + style_init(&r); + sym_init(); + + msg(L_INFO, "Applying stylesheet"); + for (uns i = OSM_TYPE_NODE; i <= OSM_TYPE_MULTIPOLYGON; i++) + CLIST_FOR_EACH(struct osm_object *, o, osm_obj_list[i]) + { + if (debug_dump_styling) + { + puts("==============================="); + osm_obj_dump(o); + } + if (!map_object_visible_p(o)) + { + if (debug_dump_styling) + printf("--> invisible\n"); + continue; + } + style_begin(&r, o); + css_apply(ss, &r); + if (debug_dump_styling) + style_dump(&r); + sym_from_style(o, &r, svg); + style_end(&r); + } + + if (map_clip) + { + svg_push_element(svg, "defs"); + svg_push_element(svg, "clipPath"); + svg_set_attr(svg, "id", "boundary"); + svg_push_path(svg); + svg_path_move_to(svg, page_offset_x, page_offset_y); + svg_path_line_to_rel(svg, page_map_width, 0); + svg_path_line_to_rel(svg, 0, page_map_height); + svg_path_line_to_rel(svg, -page_map_width, 0); + svg_path_close(svg); + svg_path_end(svg); + svg_pop(svg); + svg_pop(svg); + svg_pop(svg); + + svg_push_element(svg, "g"); + svg_set_attr_format(svg, "clip-path", "url(#boundary)"); +#ifdef ROTATE + svg_set_attr_format(svg, "transform", "translate(%.6g,0) rotate(90)", page_height * svg->scale); +#endif + } + + // FIXME: Replace by generic logo drawing facility + struct svg_icon *logo = svg_icon_load(svg, "../logo/kocka-s-okrajem.svg"); + + sym_draw_all(svg); + + // Draw logo + double logo_width = 36.12; + double logo_height = 36.12 / logo->width * logo->height; + struct svg_icon_request sir = { + .icon = logo, + .x = page_width - 12 - logo_width / 2, + .y = 10 + logo_height / 2, + .width = logo_width, + .height = logo_height, + }; + svg_icon_put(svg, &sir); + + draw_scale(svg); + + if (map_clip) + svg_pop(svg); + + if (map_draw_border) + { + svg_push_element(svg, "rect"); + svg_set_attr_dimen(svg, "x", page_offset_x); + svg_set_attr_dimen(svg, "y", page_offset_y); + svg_set_attr_dimen(svg, "width", page_map_width); + svg_set_attr_dimen(svg, "height", page_map_height); + svg_set_attr(svg, "fill", "none"); + svg_set_attr(svg, "stroke", "blue"); + svg_set_attr_dimen(svg, "stroke-width", 0.2); + svg_pop(svg); + } + + svg_close(svg); + + msg(L_INFO, "Finished"); + return 0; +} diff --git a/leo.h b/leo.h new file mode 100644 index 0000000..fa4123d --- /dev/null +++ b/leo.h @@ -0,0 +1,20 @@ +/* + * Hic Est Leo + * + * (c) 2014 Martin Mares + */ + +#include + +typedef u32 color_t; // 0x00RRGGBB + +#define COLOR_NONE 0xffffffff + +/* Debug options */ + +extern uns debug_dump_source, debug_dump_after_proj, debug_dump_after_scaling; +extern uns debug_dump_multipolygons, debug_dump_css, debug_dump_styling, debug_dump_symbols; + +/* xml.c */ + +void osm_xml_parse(const char *name); diff --git a/map.c b/map.c new file mode 100644 index 0000000..57d7861 --- /dev/null +++ b/map.c @@ -0,0 +1,135 @@ +/* + * Hic Est Leo -- Global Map Operations + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include +#include + +#include +#include + +#include "leo.h" +#include "osm.h" +#include "map.h" + +double map_min_x, map_min_y; +double map_max_x, map_max_y; +double page_width, page_height; +uns map_clip, map_draw_border; +char *map_xml_input; +char *map_projection; +char *map_style_sheet; +char *map_svg_output; + +static struct cf_section map_cf = { + CF_ITEMS { + CF_DOUBLE("MinX", &map_min_x), + CF_DOUBLE("MinY", &map_min_y), + CF_DOUBLE("MaxX", &map_max_x), + CF_DOUBLE("MaxY", &map_max_y), + CF_DOUBLE("PageWidth", &page_width), + CF_DOUBLE("PageHeight", &page_height), + CF_UNS("Clip", &map_clip), + CF_UNS("DrawBorder", &map_draw_border), + CF_STRING("XMLInput", &map_xml_input), + CF_STRING("Projection", &map_projection), + CF_STRING("StyleSheet", &map_style_sheet), + CF_STRING("SVGOutput", &map_svg_output), + CF_END + } +}; + +static void CONSTRUCTOR map_preinit(void) +{ + cf_declare_section("Map", &map_cf, 0); +} + +// Calculated +double map_scale; +double page_offset_x, page_offset_y; +double page_map_width, page_map_height; + +void map_set_scale(void) +{ + double x_range = map_max_x - map_min_x; + double y_range = map_max_y - map_min_y; + double x_scale = page_width / x_range; + double y_scale = page_height / y_range; + map_scale = MIN(x_scale, y_scale); + page_map_width = x_range * map_scale; + page_map_height = y_range * map_scale; + page_offset_x = (page_width - page_map_width) / 2; + page_offset_y = (page_height - page_map_height) / 2; + + msg(L_INFO, "Setting scale %.3g (orig window [%.6g,%.6g], page window [%.6g,%.6g]+[%.6g,%.6g] on [%.6g,%.6g])", + map_scale, + x_range, y_range, + page_map_width, page_map_height, + page_offset_x, page_offset_y, + page_width, page_height); + + double pmin_x = INFINITY, pmax_x = -INFINITY; + double pmin_y = INFINITY, pmax_y = -INFINITY; + double rmin_x = INFINITY, rmax_x = -INFINITY; + double rmin_y = INFINITY, rmax_y = -INFINITY; + CLIST_FOR_EACH(struct osm_node *, n, osm_obj_list[OSM_TYPE_NODE]) + { + pmin_x = MIN(pmin_x, n->x); + pmax_x = MAX(pmax_x, n->x); + pmin_y = MIN(pmin_y, n->y); + pmax_y = MAX(pmax_y, n->y); + n->x = (n->x - map_min_x) * map_scale + page_offset_x; + n->y = page_height - page_offset_y - (n->y - map_min_y) * map_scale; + rmin_x = MIN(rmin_x, n->x); + rmax_x = MAX(rmax_x, n->x); + rmin_y = MIN(rmin_y, n->y); + rmax_y = MAX(rmax_y, n->y); + } + msg(L_INFO, "Bounds before scaling: [%.10g,%.10g] x [%.10g,%.10g]", pmin_x, pmax_x, pmin_y, pmax_y); + msg(L_INFO, "Bounds after scaling: [%.10g,%.10g] x [%.10g,%.10g]", rmin_x, rmax_x, rmin_y, rmax_y); +} + +bool map_object_visible_p(struct osm_object *o) +{ + double margin = 10; + + switch (o->type) + { + case OSM_TYPE_NODE: + { + struct osm_node *n = (struct osm_node *) o; + return (n->x >= page_offset_x - margin && n->x <= page_offset_x + page_map_width + margin && + n->y >= page_offset_y - margin && n->y <= page_offset_y + page_map_height + margin); + } + case OSM_TYPE_WAY: + { + struct osm_way *w = (struct osm_way *) o; + bool ok = 0; + OSM_FOR_EACH_BEGIN(struct osm_object *, n, w->nodes) + { + ok |= map_object_visible_p(n); + } + OSM_FOR_EACH_END; + return ok; + } + case OSM_TYPE_RELATION: + { + struct osm_relation *r = (struct osm_relation *) o; + bool ok = 0; + OSM_FOR_EACH_BEGIN(struct osm_object *, n, r->members) + { + ok |= map_object_visible_p(n); + } + OSM_FOR_EACH_END; + return ok; + } + case OSM_TYPE_MULTIPOLYGON: + return map_object_visible_p(&((struct osm_multipolygon *) o)->rel->o); + default: + ASSERT(0); + } +} diff --git a/map.cf b/map.cf new file mode 100644 index 0000000..e605403 --- /dev/null +++ b/map.cf @@ -0,0 +1,56 @@ +Map { + # Source file with XML map of OSM data + XMLInput dump.osm + + # Projection of our map + Projection "+proj=utm +zone=33 +ellps=WGS84" + + # Which part of the map should drawn (in projected coordinates) + MinX 464737 + MaxX 471140 + MinY 5552849 + MaxY 5557376 + + # Draw on A3 paper + PageWidth 420 + PageHeight 297 + + # Draw on A4 paper + # PageWidth 297 + # PageHeight 210 + + # Clip output to the requested rectangle + Clip 1 + + # Draw blue border around the requested rectangle + DrawBorder 0 + + # MapCSS stylesheet to apply + StyleSheet poskole.css + + # Write SVG output here + SVGOutput output.svg +} + +Debug { + # Dump map data exactly as parsed + DumpSource 0 + + # Dump map data after projection to Map.Projection + DumpAfterProj 0 + + # Dump map data after conversion to on-paper coordinates + DumpAfterScaling 0 + + # Dump intermediate representations of multipolygons + DumpMultipolygons 0 + + # Dump stylesheet as parsed + DumpCSS 0 + + # Dump styling decisions + DumpStyling 0 + + # Dump planning and drawing of symbols + DumpSymbols 0 +} diff --git a/map.h b/map.h new file mode 100644 index 0000000..632e8d5 --- /dev/null +++ b/map.h @@ -0,0 +1,30 @@ +/* + * Hic Est Leo -- Global Map Operations + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_MAP_H +#define _BRUM_MAP_H + +/* Map configuration */ + +extern double map_min_x, map_min_y; +extern double map_max_x, map_max_y; +extern double page_width, page_height; +extern uns map_clip, map_draw_border; +extern char *map_xml_input; +extern char *map_projection; +extern char *map_style_sheet; +extern char *map_svg_output; + +/* Calculated by map_set_scale() */ + +extern double map_scale; +extern double page_offset_x, page_offset_y; +extern double page_map_width, page_map_height; + +void map_set_scale(void); +bool map_object_visible_p(struct osm_object *o); + +#endif diff --git a/osm.c b/osm.c new file mode 100644 index 0000000..12529f6 --- /dev/null +++ b/osm.c @@ -0,0 +1,581 @@ +/* + * Hic Est Leo -- OSM Data Representation + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "leo.h" +#include "osm.h" + +static struct mempool *osm_pool; + +/*** Generic objects ***/ + +struct osm_id_to_obj { + osm_id_t id; + struct osm_object *o; +}; + +clist osm_obj_list[OSM_TYPE_MAX]; + +#define P(x) [OSM_TYPE_##x] = #x, +const char * const osm_obj_type_names[OSM_TYPE_MAX] = { + [OSM_TYPE_INVALID] = "Invalid", + [OSM_TYPE_NODE] = "Node", + [OSM_TYPE_WAY] = "Way", + [OSM_TYPE_RELATION] = "Relation", + [OSM_TYPE_MULTIPOLYGON] = "Multipolygon", +}; +#undef P + +#define HASH_NODE struct osm_id_to_obj +#define HASH_PREFIX(x) osm_id_hash_##x +#define HASH_KEY_ATOMIC id +#define HASH_ATOMIC_TYPE osm_id_t +#define HASH_WANT_FIND +#define HASH_WANT_LOOKUP +#define HASH_USE_POOL osm_pool +#define HASH_ZERO_FILL +#define HASH_TABLE_DYNAMIC +#include + +static struct osm_id_hash_table osm_id_hash[OSM_TYPE_MAX]; + +osm_id_t osm_parse_id(const char *str) +{ + uintmax_t id; + const char *err = str_to_uintmax(&id, str, NULL, STN_WHOLE | STN_MINUS | 10); + if (err || id != (osm_id_t)id) + return OSM_INVALID_ID; + else + return id; +} + +struct osm_object *osm_obj_find_by_id(enum osm_object_type type, osm_id_t id) +{ + ASSERT(type != OSM_TYPE_INVALID && type < OSM_TYPE_MAX); + struct osm_id_to_obj *ii = osm_id_hash_find(&osm_id_hash[type], id); + return ii ? ii->o : NULL; +} + +static void *osm_obj_new(enum osm_object_type type, osm_id_t id, uns size) +{ + ASSERT(type != OSM_TYPE_INVALID && type < OSM_TYPE_MAX); + struct osm_id_to_obj *ii = osm_id_hash_lookup(&osm_id_hash[type], id); + if (ii->o) + die("Id %ju for type %s defined twice", (uintmax_t) id, osm_obj_type_names[type]); + + struct osm_object *o = mp_alloc_zero(osm_pool, size); + clist_add_tail(&osm_obj_list[type], &o->n); + o->type = type; + o->id = id; + clist_init(&o->tags); + clist_init(&o->backrefs); + ii->o = o; + return o; +} + +void osm_ref_add(struct osm_object *parent, clist *list, struct osm_object *son, osm_val_t role) +{ + ASSERT(parent != son); + + struct osm_ref *ref = mp_alloc(osm_pool, sizeof(*ref)); + ref->o = son; + ref->role = role; + clist_add_tail(list, &ref->n); + + struct osm_ref *backref = mp_alloc(osm_pool, sizeof(*ref)); + backref->o = parent; + backref->role = 0; + clist_add_tail(&son->backrefs, &backref->n); +} + +void osm_obj_dump(struct osm_object *o) +{ + switch (o->type) + { + case OSM_TYPE_NODE: + osm_node_dump((struct osm_node *) o); + break; + case OSM_TYPE_WAY: + osm_way_dump((struct osm_way *) o); + break; + case OSM_TYPE_RELATION: + osm_relation_dump((struct osm_relation *) o); + break; + case OSM_TYPE_MULTIPOLYGON: + osm_multipolygon_dump((struct osm_multipolygon *) o); + break; + default: + ASSERT(0); + } +} + +void osm_obj_warn(struct osm_object *o, const char *mesg, ...) +{ + va_list args; + va_start(args, mesg); + msg(L_WARN, "%s: %s", STK_OSM_NAME(o), stk_vprintf(mesg, args)); + va_end(args); +} + +/*** Tags ***/ + +struct dict osm_key_dict, osm_value_dict; + +void osm_obj_add_tag_raw(struct osm_object *o, osm_key_t key, osm_val_t val) +{ + struct osm_tag *t = mp_alloc(osm_pool, sizeof(*t)); + t->key = key; + t->val = val; + clist_add_tail(&o->tags, &t->n); +} + +void osm_obj_add_tag(struct osm_object *o, const char *key, const char *val) +{ + osm_obj_add_tag_raw(o, osm_key_encode(key), osm_val_encode(val)); +} + +osm_val_t osm_obj_find_tag(struct osm_object *o, osm_key_t key) +{ + CLIST_FOR_EACH(struct osm_tag *, t, o->tags) + if (t->key == key) + return t->val; + return 0; +} + +void osm_tag_dump(struct osm_tag *t) +{ + printf("\t%s = %s\n", osm_key_decode(t->key), osm_val_decode(t->val)); +} + +static const char * const osm_wk_keys[] = { + NULL, +#define P(x,y) [KEY_##x] = y, +#include "dict-keys.h" +#undef P + NULL +}; + +static const char * const osm_wk_values[] = { + NULL, +#define P(x,y) [VALUE_##x] = y, +#include "dict-values.h" +#undef P + NULL +}; + +static void osm_tag_init(void) +{ + dict_init(&osm_key_dict, osm_wk_keys); + dict_init(&osm_value_dict, osm_wk_values); +} + +/*** Nodes ***/ + +struct osm_node *osm_node_new(osm_id_t id) +{ + struct osm_node *n = osm_obj_new(OSM_TYPE_NODE, id, sizeof(*n)); + return n; +} + +void osm_node_dump(struct osm_node *n) +{ + printf("Node %ju: (%f,%f)\n", (uintmax_t) n->o.id, n->x, n->y); + CLIST_FOR_EACH(struct osm_tag *, t, n->o.tags) + osm_tag_dump(t); +} + +void osm_node_dump_all(void) +{ + printf("Known nodes:\n"); + CLIST_FOR_EACH(struct osm_node *, n, osm_obj_list[OSM_TYPE_NODE]) + osm_node_dump(n); + putchar('\n'); +} + +/*** Ways ***/ + +struct osm_way *osm_way_new(osm_id_t id) +{ + struct osm_way *w = osm_obj_new(OSM_TYPE_WAY, id, sizeof(*w)); + clist_init(&w->nodes); + return w; +} + +void osm_way_add_node(struct osm_way *w, struct osm_node *n) +{ + osm_ref_add(&w->o, &w->nodes, &n->o, 0); +} + +void osm_way_dump(struct osm_way *w) +{ + printf("Way %ju%s\n", (uintmax_t) w->o.id, (osm_way_cyclic_p(w) ? " (cyclic)" : "")); + CLIST_FOR_EACH(struct osm_tag *, t, w->o.tags) + osm_tag_dump(t); + OSM_FOR_EACH_BEGIN(struct osm_node *, n, w->nodes) + { + printf("\tNode %ju\n", (uintmax_t) n->o.id); + } + OSM_FOR_EACH_END; +} + +void osm_way_dump_all(void) +{ + printf("Known ways:\n"); + CLIST_FOR_EACH(struct osm_way *, w, osm_obj_list[OSM_TYPE_WAY]) + osm_way_dump(w); + putchar('\n'); +} + +bool osm_way_cyclic_p(struct osm_way *w) +{ + struct osm_ref *first_ref = clist_head(&w->nodes); + struct osm_ref *last_ref = clist_tail(&w->nodes); + return (first_ref && last_ref && + first_ref != last_ref && + first_ref->o == last_ref->o); +} + +/*** Relations ***/ + +struct osm_relation *osm_relation_new(osm_id_t id) +{ + struct osm_relation *r = osm_obj_new(OSM_TYPE_RELATION, id, sizeof(*r)); + clist_init(&r->members); + return r; +} + +void osm_relation_add_member(struct osm_relation *r, struct osm_object *o, const char *role) +{ + osm_ref_add(&r->o, &r->members, o, (role && role[0] ? osm_val_encode(role) : 0)); +} + +void osm_relation_dump(struct osm_relation *r) +{ + printf("Relation %ju\n", (uintmax_t) r->o.id); + CLIST_FOR_EACH(struct osm_tag *, t, r->o.tags) + osm_tag_dump(t); + CLIST_FOR_EACH(struct osm_ref *, f, r->members) + printf("\tRole %s: %s %ju\n", (f->role ? osm_val_decode(f->role) : ""), osm_obj_type_names[f->o->type], (uintmax_t) f->o->id); +} + +void osm_relation_dump_all(void) +{ + printf("Known relations:\n"); + CLIST_FOR_EACH(struct osm_relation *, r, osm_obj_list[OSM_TYPE_RELATION]) + osm_relation_dump(r); + putchar('\n'); +} + +/*** Multipolygons ***/ + +void osm_multipolygon_dump(struct osm_multipolygon *m) +{ + printf("Multipolygon %ju\n", (uintmax_t) m->o.id); + CLIST_FOR_EACH(struct osm_tag *, t, m->o.tags) + osm_tag_dump(t); + CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries) + { + printf("\tBoundary inner=%u\n", b->inner); + CLIST_FOR_EACH(struct osm_ref *, f, b->nodes) + printf("\t\tNode %ju\n", (uintmax_t) f->o->id); + } +} + +void osm_multipolygon_dump_all(void) +{ + printf("Known multipolygons:\n"); + CLIST_FOR_EACH(struct osm_multipolygon *, r, osm_obj_list[OSM_TYPE_MULTIPOLYGON]) + osm_multipolygon_dump(r); + putchar('\n'); +} + +static struct mempool *mpg_pool; +static struct osm_multipolygon *mpg_current; + +struct mpg_vertex { + osm_id_t id; + struct osm_object *o; + clist edges; // of mpg_edge's + bool visited; +}; + +struct mpg_edge { + cnode n; + struct mpg_edge *twin; + struct mpg_vertex *dest; + bool used; +}; + +#define HASH_NODE struct mpg_vertex +#define HASH_PREFIX(x) mpg_vertex_hash_##x +#define HASH_KEY_ATOMIC id +#define HASH_ATOMIC_TYPE osm_id_t +#define HASH_USE_POOL mpg_pool +#define HASH_TABLE_ALLOC +#define HASH_WANT_LOOKUP +#define HASH_LOOKUP_DETECT_NEW +#include + +static void mpg_insert_way(struct osm_way *w) +{ + struct mpg_vertex *prev = NULL; + OSM_FOR_EACH_BEGIN(struct osm_node *, n, w->nodes) + { + int new = 0; + struct mpg_vertex *v = mpg_vertex_hash_lookup(n->o.id, &new); + if (new) + { + clist_init(&v->edges); + v->visited = 0; + v->o = &n->o; + } + if (prev) + { + struct mpg_edge *e1 = mp_alloc_zero(mpg_pool, sizeof(*e1)); + struct mpg_edge *e2 = mp_alloc_zero(mpg_pool, sizeof(*e2)); + e1->twin = e2; + e1->dest = v; + clist_add_tail(&prev->edges, &e1->n); + e2->twin = e1; + e2->dest = prev; + clist_add_tail(&v->edges, &e2->n); + } + prev = v; + } + OSM_FOR_EACH_END; +} + +static void UNUSED mpg_dump(void) +{ + printf("=== Multipolygon graph for relation %ju ===\n", (uintmax_t) mpg_current->o.id); + HASH_FOR_ALL(mpg_vertex_hash, v) + { + printf("Vertex %ju\n", (uintmax_t) v->id); + CLIST_FOR_EACH(struct mpg_edge *, e, v->edges) + printf("\t-> %ju\n", (uintmax_t) e->dest->id); + } + HASH_END_FOR; + puts("=== End ==="); +} + +static void mpg_walk_boundary(struct osm_multipolygon *m, bool inner) +{ + // FIXME: Replace by a better algorithm, which respects geometry + // and is able to cope with higher degree vertices. + HASH_FOR_ALL(mpg_vertex_hash, v) + { + if (!v->visited) + { + struct osm_mpg_boundary *b = mp_alloc(osm_pool, sizeof(*b)); + clist_add_tail(&m->boundaries, &b->n); + b->inner = inner; + clist_init(&b->nodes); + + struct mpg_vertex *w = v; + while (!w->visited) + { + w->visited = 1; + struct osm_ref *f = mp_alloc(osm_pool, sizeof(*f)); + clist_add_tail(&b->nodes, &f->n); + f->o = w->o; + f->role = 0; + + struct mpg_vertex *dest = NULL; + CLIST_FOR_EACH(struct mpg_edge *, e, w->edges) + { + if (e->used) + continue; + if (dest) + { + if (w != v) + osm_obj_warn(&mpg_current->o, "Suspicious vertex degree of node %ju", (uintmax_t) w->o->id); + break; + } + else + { + dest = e->dest; + e->used = 1; + e->twin->used = 1; + } + } + + w = dest; + } + + if (w != v) + osm_obj_warn(&mpg_current->o, "Boundary not closed at node %ju", (uintmax_t) w->o->id); + + struct osm_ref *f = mp_alloc(osm_pool, sizeof(*f)); + clist_add_tail(&b->nodes, &f->n); + f->o = v->o; + f->role = 0; + } + } + HASH_END_FOR; +} + +static void osm_multipolygon_make(struct osm_relation *r) +{ + struct osm_multipolygon *m = osm_obj_new(OSM_TYPE_MULTIPOLYGON, r->o.id, sizeof(*m)); + mpg_current = m; + m->rel = r; + CLIST_FOR_EACH(struct osm_tag *, t, r->o.tags) + osm_obj_add_tag_raw(&m->o, t->key, t->val); + clist_init(&m->boundaries); + + for (int inner=0; inner<2; inner++) + { + if (!mpg_pool) + mpg_pool = mp_new(4096); + mpg_vertex_hash_init(); + + CLIST_FOR_EACH(struct osm_ref *, f, r->members) + { + struct osm_object *o = f->o; + if (f->role != VALUE_INNER && f->role != VALUE_OUTER) + { + if (!inner) + osm_obj_warn(o, "Unknown role %s in multipolygon relation", osm_val_decode(f->role)); + continue; + } + if ((f->role == VALUE_INNER) != inner) + continue; + if (o->type == OSM_TYPE_WAY) + mpg_insert_way((struct osm_way *) o); + else + osm_obj_warn(o, "Only ways are supported in multipolygon boundaries"); + } + + if (debug_dump_multipolygons) + mpg_dump(); + + mpg_walk_boundary(m, inner); + mp_flush(mpg_pool); + } +} + +void osm_make_multipolygons(void) +{ + uns mpg_cnt = 0; + + CLIST_FOR_EACH(struct osm_relation *, r, osm_obj_list[OSM_TYPE_RELATION]) + if (osm_obj_find_tag(&r->o, KEY_TYPE) == VALUE_MULTIPOLYGON) + { + osm_multipolygon_make(r); + mpg_cnt++; + } + msg(L_INFO, "Converted %u relations to multipolygons", mpg_cnt); +} + +/*** Projection ***/ + +static projPJ osm_pj; + +void osm_project(const char *spec) +{ + osm_pj = pj_init_plus(spec); + if (!osm_pj) + die("Unable to initialize projection %s", spec); + + CLIST_FOR_EACH(struct osm_node *, n, osm_obj_list[OSM_TYPE_NODE]) + { + projUV p; + p.u = n->x * DEG_TO_RAD; + p.v = n->y * DEG_TO_RAD; + p = pj_fwd(p, osm_pj); + n->x = p.u; + n->y = p.v; + } +} + +/*** Object centers ***/ + +bool osm_obj_center(struct osm_object *o, double *xp, double *yp) +{ + switch (o->type) + { + case OSM_TYPE_NODE: + { + struct osm_node *n = (struct osm_node *) o; + *xp = n->x; + *yp = n->y; + return 1; + } + case OSM_TYPE_WAY: + { + struct osm_way *w = (struct osm_way *) o; + double sx = 0, sy = 0; + uns nn = 0; + OSM_FOR_EACH_BEGIN(struct osm_node *, n, w->nodes) + { + sx += n->x; + sy += n->y; + nn++; + } + OSM_FOR_EACH_END; + if (!nn) + return 0; + *xp = sx / nn; + *yp = sy / nn; + return 1; + } + case OSM_TYPE_MULTIPOLYGON: + { + struct osm_multipolygon *m = (struct osm_multipolygon *) o; + double sx = 0, sy = 0; + uns nn = 0; + CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries) + { + if (b->inner) + continue; + OSM_FOR_EACH_BEGIN(struct osm_node *, n, b->nodes) + { + sx += n->x; + sy += n->y; + nn++; + } + OSM_FOR_EACH_END; + } + if (!nn) + return 0; + *xp = sx / nn; + *yp = sy / nn; + return 1; + } + default: + return 0; + } +} + +/*** Globals ***/ + +void osm_init(void) +{ + osm_pool = mp_new(65536); + for (int i=0; i + */ + +#ifndef _BRUM_OSM_H +#define _BRUM_OSM_H + +#include "dict.h" + +typedef u64 osm_id_t; +#define OSM_INVALID_ID ~(osm_id_t)0 + +typedef u32 osm_key_t, osm_val_t; + +struct osm_object { + cnode n; // In the per-type list + byte type; // OSM_TYPE_xxx + byte pad[3]; + osm_id_t id; + clist tags; + clist backrefs; // Objects pointing to this one +}; + +enum osm_object_type { + OSM_TYPE_INVALID, + OSM_TYPE_NODE, + OSM_TYPE_WAY, + OSM_TYPE_RELATION, + OSM_TYPE_MULTIPOLYGON, + // Remember to update osm_obj_type_names[], too + OSM_TYPE_MAX, +}; + +struct osm_tag { + cnode n; // In object->tags + osm_key_t key; + osm_val_t val; +}; + +struct osm_ref { + cnode n; + struct osm_object *o; + osm_val_t role; +}; + +struct osm_node { + struct osm_object o; + double x, y; + /* + * For WGS84 coordinates, x is longitude and y latitude. + * For UTM, x is easting and y northing. + * After map scaling, x is horizontal (left-to-right) and y vertical (top-to-bottom) on the paper. + */ +}; + +struct osm_way { + struct osm_object o; + clist nodes; // List of osm_ref's +}; + +struct osm_relation { + struct osm_object o; + clist members; // List of osm_ref's +}; + +struct osm_multipolygon { // Virtual object constructed from multipolygon relations + struct osm_object o; + struct osm_relation *rel; // Original relation + clist boundaries; // List of osm_mpg_boundary's +}; + +struct osm_mpg_boundary { + cnode n; + bool inner; + clist nodes; // List of osm_ref's (without back-references, since the boundary is not a regular object) +}; + +void osm_init(void); +void osm_dump(void); + +extern clist osm_obj_list[OSM_TYPE_MAX]; + +osm_id_t osm_parse_id(const char *str); +struct osm_object *osm_obj_find_by_id(enum osm_object_type type, osm_id_t id); +void osm_obj_add_tag(struct osm_object *o, const char *key, const char *val); +void osm_obj_add_tag_raw(struct osm_object *o, osm_key_t key, osm_val_t val); +osm_val_t osm_obj_find_tag(struct osm_object *o, osm_val_t key); +void osm_obj_dump(struct osm_object *o); +void osm_obj_warn(struct osm_object *o, const char *msg, ...); +extern const char * const osm_obj_type_names[OSM_TYPE_MAX]; +#define STK_OSM_NAME(o) stk_printf("%s:%ju", osm_obj_type_names[(o)->type], (uintmax_t) (o)->id) + +void osm_ref_add(struct osm_object *parent, clist *list, struct osm_object *son, osm_val_t role); +#define OSM_FOR_EACH_BEGIN(_type, _var, _list) CLIST_FOR_EACH(struct osm_ref *, _ref, _list) { _type _var = (_type) _ref->o; +#define OSM_FOR_EACH_END } + +struct osm_node *osm_node_new(osm_id_t id); +void osm_node_dump(struct osm_node *n); +void osm_node_dump_all(void); + +struct osm_way *osm_way_new(osm_id_t id); +void osm_way_add_node(struct osm_way *w, struct osm_node *n); +void osm_way_dump(struct osm_way *w); +void osm_way_dump_all(void); +bool osm_way_cyclic_p(struct osm_way *w); + +struct osm_relation *osm_relation_new(osm_id_t id); +void osm_relation_add_member(struct osm_relation *r, struct osm_object *o, const char *role); +void osm_relation_dump(struct osm_relation *w); +void osm_relation_dump_all(void); + +void osm_multipolygon_dump(struct osm_multipolygon *w); +void osm_multipolygon_dump_all(void); +void osm_make_multipolygons(void); + +void osm_project(const char *spec); + +bool osm_obj_center(struct osm_object *o, double *xp, double *yp); + +/* Tags */ + +extern struct dict osm_key_dict, osm_value_dict; + +static inline osm_key_t osm_key_encode(const char *key) +{ + return dict_encode(&osm_key_dict, key); +} + +static inline const char *osm_key_decode(osm_key_t key) +{ + return dict_decode(&osm_key_dict, key); +} + +static inline osm_val_t osm_val_encode(const char *val) +{ + return dict_encode(&osm_value_dict, val); +} + +static inline const char *osm_val_decode(osm_val_t key) +{ + return dict_decode(&osm_value_dict, key); +} + +void osm_tag_dump(struct osm_tag *t); + +/* Well-known tags and values */ + +enum tag_keys { + KEY_NULL, +#define P(x,y) KEY_##x, +#include "dict-keys.h" +#undef P +}; + +enum value_names { + VALUE_NULL, +#define P(x,y) VALUE_##x, +#include "dict-values.h" +#undef P +}; + +#endif diff --git a/poskole.css b/poskole.css new file mode 100644 index 0000000..30631f5 --- /dev/null +++ b/poskole.css @@ -0,0 +1,707 @@ +/*** General settings ***/ + +way::* { + linejoin: round; + linecap: round; + casing-linejoin: round; + casing-linecap: round; +} + +/* + * Assignment of z-indices: + * + * 0 default + * 0.1 subtypes of landuse + * 1.x highways + * 2.x railways + * 4 tourist routes + * 5 icons + * 9 hack:front + * 10 power lines and similar overhead objects + * + * Assignment of major-z-indices: + * + * 1 (default for areas} + * 1.1 water way casing + * 1.2 water ways + * 1.3 water areas + * 2 (default for casing) + * 2.9 (default for line patterns) + * 3 (default for lines) + * 4 (default for points) + * 4.9 (default for line text) + * 5 (default for point text) + */ + +/*** Landuse ***/ + +/* +area[landuse], area[leisure] { + fill-color: #f00; +} +*/ + +area[leisure=swimming_pool][access!=private] { + fill-color: #b5d0d0; + color: #00f; + width: 0.5; +} + +area[leisure=playground] { + fill-color: #ccfff1; + color: #666; + width: 0.3; +} + +area[leisure=attraction] { + fill-color: #f2caea; + // width: 0.3; +} + +area[landuse=cemetery], area[landuse=grave_yard], area[amenity=grave_yard] { + fill-color: #aacbaf; + fill-pattern: "icons/cemetery.svg"; + fill-pattern-width: 2; + // FIXME: Switch patterns according to religion=* ? +} + +/* +area[landuse=residential] { + fill-color: #ddd; +} +*/ + +area[landuse=meadow], +area[landuse=grass], +area[leisure=common], +area[leisure=garden], +area[landuse=recreation_ground], +area[landuse=orchard], +area[landuse=plant_nursery], +area[landuse=village_green] { + fill-color: #cfeca8; +} + +area[natural=scrub], +area[leisure=golf_course] { + fill-color: #b5e3b5; + z-index: 0.1; +} + +area[leisure=park], +area[leisure=recreation_ground] { + fill-color: #b6fdb6; + z-index: 0.1; +} + +area[landuse=forest] { + fill-color: #8dc56c; + // FIXME: Pattern fill +} + +area[landuse=allotments] { + fill-color: #e5c7ab; + // FIXME: Pattern fill +} + +area[landuse=retail] { + fill-color: #f1dada; + color: #f00; + width: 0.3; +} + +area[landuse=industrial], +area[landuse=railway] { + fill-color: #dfd1d6; +} + +area[landuse=farm], +area[landuse=farmland] { + fill-color: #ead8bd; +} + +area[landuse=brownfield], +area[landuse=landfill], +area[landuse=greenfield], +area[landuse=construction] { + fill-color: #9d9d6c; +} + +area[landuse=military] { + fill-color: #65841b; + color: #f55; + width: 0.3; + // FIXME: Pattern +} + +area[landuse=parking] { + fill-color: #f7efb7; + color: #eeeed1; + width: 0.3; +} + +area[aeroway=apron] { + fill-color: #e9d1ff; +} + +area[aeroway=aerodrome] { + fill-color: #ccc; + fill-opacity: 0.2; + color: #666; + width: 0.2; +} + +area[landuse=commercial], +area[highway=services], +area[highway=rest_area] { + fill-color: #efc8c8; +} + +area[landuse=garages] { + fill-color: #996; + fill-opacity: 0.2; +} + +/* +area[leisure=nature_reserve] { + fill-color: #6c3; + color: #6c3; + width: 0.3; + // FIXME: Pattern? +} +*/ + +area[leisure=pitch] { + fill-color: #8ad3af; + color: #888; + width: 0.3; + z-index: 0.1; +} + +area[leisure=track] { + fill-color: #74dcba; + color: #888; + width: 0.3; + z-index: 0.1; +} + +way[barrier] { + color: #666; + width: 0.2; +} + +/*** Water ***/ + +/* +way[waterway] { + color: #f00; +} +*/ + +area[landuse=basin], +area[landuse=reservoir], +area[landuse=water], +area[natural=bay], +area[natural=lake], +area[natural=water], +area[waterway=riverbank] { + fill-color: #b5d0d0; + major-z-index: 1.3; // Above normal areas and waterways + text: name; + text-halo-radius: 0.5; + text-color: #3030a5; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-position: center; + font-family: Times; + font-size: 2.5; + font-style: italic; +} + +area[natural=marsh], +area[natural=wetland] { + fill-pattern: "icons/wetland.svg"; + fill-pattern-width: 2; + fill-pattern-height: 1.5; + major-z-index: 1.3; +} + +way[waterway=stream], +way[waterway=ditch], +way[waterway=drain] { + color: #b5d0d0; + width: 0.3; + casing-width: 0.2; + casing-color: #fff; + major-z-index: 1.2; // Above normal areas, below water areas + casing-major-z-index: 1.1; + casing-opacity: 0.5; +} + +way[waterway=river] { + color: #b5d0d0; + width: 0.6; + casing-color: #fff; + casing-width: 1; + major-z-index: 1.2; // Above normal areas, below water areas + casing-major-z-index: 1.1; + casing-opacity: 0.5; +} + +// FIXME: waterway=river && tunnel=yes + +/*** Power lines ***/ + +way[power=line] { + color: #777; + width: 0.3; + z-index: 10; +} + +way[power=minor_line] { + color: #777; + width: 0.15; + z-index: 10; +} + +node[power=tower], +node[power=pole] { + symbol-shape: circle; // FIXME + symbol-size: 0.7; + symbol-stroke-color: #777; + symbol-stroke-width: 0.3; + z-index: 10; +} + +/*** Highways ***/ + +/* +way[highway][area?!] { + color: #f00; + width: 0.3; +} +*/ + +area[highway=residential], +area[highway=unclassified], +area[highway=service] { + fill-color: #fff; +} + +area[highway=pedestrian], +area[highway=footway], +area[highway=path] { + fill-color: #ededed; +} + +area[highway=track] { + fill-color: #dfcc66; +} + +area[highway=platform], +area[railway=platform] { + fill-color: #bbbbbb; +} + +area[aeroway=runway], +area[aeroway=taxiway], +area[aeroway=helipad] { + fill-color: #bbc; + z-index: 1; +} + +way[highway=motorway_link] { + color: #809bc0; + width: 5; + casing-color: #506077; + casing-width: 1; + z-index: 1.9; +} + +way[highway=trunk_link] { + color: #a9dba9; + width: 2.5; + casing-color: #477147; + casing-width: 0.8; + z-index: 1.8; +} + +way[highway=primary_link] { + color: #ec989a; + width: 1.5; + casing-color: #8d4346; + casing-width: 0.5; + z-index: 1.7; +} + +way[highway=secondary_link] { + color: #fed7a5; + width: 1; + casing-color: #a37b48; + casing-width: 0.5; + z-index: 1.6; +} + +way[highway=tertiary_link] { + color: #ffffb3; + width: 0.7; + casing-color: #bbb; + casing-width: 0.5; + z-index: 1.5; +} + +way[highway=motorway] { + color: #809bc0; + width: 5; + casing-color: #506077; + casing-width: 1; + z-index: 1.9; +} + +way[highway=trunk] { + color: #a9dba9; + width: 2.5; + casing-color: #477147; + casing-width: 0.8; + z-index: 1.8; +} + +way[highway=primary] { + color: #ec989a; + width: 1.5; + casing-color: #8d4346; + casing-width: 0.5; + z-index: 1.7; +} + +way[highway=secondary] { + color: #fed7a5; + width: 1; + casing-color: #a37b48; + casing-width: 0.5; + z-index: 1.6; +} + +way[highway=tertiary] { + color: #ffffb3; + width: 0.7; + casing-color: #bbb; + casing-width: 0.5; + z-index: 1.5; +} + +way[highway=road] { + color: #ddd; + width: 0.7; + casing-color: #999; + casing-width: 0.5; + z-index: 1; +} + +way[highway=residential], +way[highway=unclassified], +way[highway=road], +way[highway=living_street] { + color: #fff; + width: 0.7; + casing-color: #999; + casing-width: 0.5; + z-index: 1; +} + +way[highway=service] { + color: #fff; + width: 0.5; + casing-color: #999; + casing-width: 0.3; + z-index: 1; +} + +way[highway=pedestrian] { + color: #ededed; + width: 0.3; + casing-color: #808080; + casing-width: 0.3; +} + +// In this scale, steps cannot be displayed nicely +/* +way[highway=steps] { + color: #fa8072; + width: 0.8; + dashes: 0.2, 0.2; +} +*/ + +way[highway=footway], +way[highway=path][foot=designated] { +// color: #888; + color: #996600; + width: 0.3; + casing-color: #fff; + casing-width: 0.1; + casing-opacity: 0.5; +} + +way[highway=track], +way[highway=path], +way[highway=bridleway] { + color: #996600; + width: 0.3; + dashes: 5, 3; + casing-color: #fff; + casing-width: 0.1; + casing-opacity: 0.5; +} + +way[highway=cycleway], +way[highway=path][bicycle=designated] { +/* + color: #5e5eff; + width: 0.3; + dashes: 5, 3; +*/ +// color: #888; + color: #996600; + width: 0.3; + casing-color: #fff; + casing-width: 0.1; + casing-opacity: 0.5; +} + +way[highway=proposed] { + width: 0; +} + +way[aeroway=runway], +way[aeroway=taxiway] { + color: #bbc; + width: 0.3; +} + +/*** Railway ***/ + +/* +way[railway][area?!] { + color: #f0f; + width: 0.3; +} +*/ + +way[railway=rail] { + color: #999; + width: 0.6; + casing-color: #444; + casing-width: 0.2; + z-index: 2.5; +} + +way[railway=rail]::rwint { + color: #fff; + width: 0.5; + dashes: 9, 9; + z-index: 2.6; +} + +way[railway=rail][service=spur], +way[railway=rail][service=yard] { + width: 0.3; + z-index: 2; +} + +way[railway=rail][service=spur]::rwint, +way[railway=rail][service=yard]::rwint { + width: 0.2; + z-index: 2.1; +} + +/*** Buildings ***/ + +area[building] { + fill-color: #bca9a9; + color: #330066; + width: 0.1; +} + +/*** Tourist routes ***/ + +relation[type=route][route=foot][kct_yellow] way::routes { + color: #ee3; + width: 0.5; + z-index: 4; +} + +relation[type=route][route=foot][kct_red] way::routes { + color: #e33; + width: 0.5; + z-index: 4; +} + +way[highway=footway]::routes, +way[highway=path]::routes, +way[highway=cycleway]::routes, +way[highway=bridleway]::routes, +way[highway=track]::routes { + opacity: 0.7; +} + +/*** Icons ***/ + +node[tourism=viewpoint] { + icon-image: "icons/view_point.svg"; + icon-width: 4; + z-index: 5; +} + +node[historic=wayside_cross][religion=christian], +node[amenity=place_of_worship][religion=christian], +node[amenity=crucifix], +area[amenity=place_of_worship][religion=christian] { + icon-image: "icons/cross.svg"; + icon-width: 1.5; + z-index: 5; +} + +area[building=church][amenity=place_of_worship][religion=christian], +area[historic=wayside_shrine][amenity=place_of_worship][religion=christian] { + icon-image: "icons/church.svg"; + icon-width: 2; + z-index: 5; + text: name; + text-halo-radius: 0.8; + text-color: #000000; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-position: center; + text-offset: -3; + font-family: Times; + font-size: 2.5; + font-style: italic; +} + +node[natural=cave_entrance] { + icon-image: "icons/cave2.svg"; + icon-width: 3; + z-index: 5; +} + +node[natural=tree][monument=yes][type=broad_leaved] { + icon-image: "icons/deciduous.svg"; + icon-width: 3; + z-index: 5; + text: name; + text-halo-radius: 0.5; + text-color: #006322; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-anchor-horizontal: center; + text-anchor-vertical: bottom; + text-offset: -1; + font-family: Times; + font-size: 2.5; + font-style: italic; +} + +node[hack=parkname] { + text: name; + text-halo-radius: 0.5; + text-color: #006322; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-anchor-horizontal: center; + text-anchor-vertical: center; + text-offset: -1; + font-family: Times; + font-size: 2.5; + font-style: italic; +} + +node[hack=suburbname] { + text: name; + text-halo-radius: 1; + text-color: #888888; + text-halo-opacity: 0.8; + text-halo-color: #ffffff; + text-anchor-horizontal: center; + text-anchor-vertical: center; + font-family: Helvetica; + font-size: 6; + font-style: italic; +} + + +node[barrier=gate] { + icon-image: "icons/gate.svg"; + icon-width: 1.5; + z-index: 5; +} + +node[railway=station], +node[railway=halt] { + icon-image: "icons/train.svg"; + icon-width: 3; + z-index: 5; + text: name; + text-halo-radius: 0.5; + text-color: #000000; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-anchor-horizontal: center; + text-anchor-vertical: bottom; + text-offset: -2; + font-family: Times; + font-size: 2.5; + font-style: italic; +} + +area[railway=station], +area[railway=halt] { + icon-image: "icons/train.svg"; + icon-width: 3; + z-index: 5; + text: name; + text-halo-radius: 0.5; + text-color: #000000; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-position: center; + text-offset: -4; + font-family: Times; + font-size: 2.5; + font-style: italic; +} + +node[highway=bus_stop] { +// icon-image: "icons/bus_stop2.svg"; +// icon-width: 3; + symbol-shape: circle; + symbol-size: 0.8; + symbol-fill-color: #f55; + z-index: 5; +} + +node[historic=memorial], +node[tourism=artwork][artwork_type=statue] { + icon-image: "icons/memorial.svg"; + icon-width: 1; + z-index: 5; +} + +way[natural=tree_row] { + repeat-image: "icons/deciduous.svg"; + repeat-image-width: 2; + repeat-image-spacing: 2; + z-index: 5; +} + +/*** Hacks ***/ + +node[hack=front], +way[hack=front] { + z-index: 9; +} + +node[hack=raisetext], +way[hack=raisetext] { + text-offset: 3; +} diff --git a/shp.c b/shp.c new file mode 100644 index 0000000..383525e --- /dev/null +++ b/shp.c @@ -0,0 +1,113 @@ +/* + * Hic Est Leo -- Reading ESRI Shape Files + * + * (c) 2014 Martin Mares + * + * FIXME: Currently, this parser handles only the subset + * of shape file syntax which is used by gdal_contours. + */ + +#undef LOCAL_DEBUG + +#include +#include +#include +#include +#include + +#include +#include + +#include "leo.h" +#include "osm.h" +#include "map.h" +#include "shp.h" + +// FIXME: Hack +static osm_id_t shp_id_counter = 1000000000000; + +static double shp_get_double(byte *p) +{ + // FIXME: This is non-portable! + double x = 0; + memcpy(&x, p, 8); + return x; +} + +static void shp_record_polyline(byte *buf, u32 len) +{ + if (len < 44) + die("Polyline record too short: %u bytes", len); + + u32 num_parts = get_u32_le(buf+36); + u32 num_points = get_u32_le(buf+40); + DBG("%u parts, %u points", num_parts, num_points); + if (num_parts != 1) + die("Polylines with multiple parts are not supported yet"); + + if (len < 44 + 4*num_parts + 16*num_points) + die("Polyline record too short for %u parts and %u points: %u bytes", num_parts, num_points, len); + + struct osm_way *w = osm_way_new(shp_id_counter++); + osm_obj_add_tag(&w->o, "shape", "contour"); + + byte *p = buf + 44 + 4*num_parts; + for (uint i=0; i < num_points; i++) + { + double x = shp_get_double(p); + double y = shp_get_double(p+8); + DBG(">>> x=%.10g y=%.10g", x, y); + struct osm_node *n = osm_node_new(shp_id_counter++); + n->x = x; + n->y = y; + osm_way_add_node(w, n); + p += 16; + } +} + +void shp_parse(const char *name) +{ + msg(L_INFO, "Loading shape file %s", name); + struct fastbuf *fb = bopen_file(name, O_RDONLY, NULL); + + byte *buf; + GARY_INIT(buf, 100); + + uns len = bread(fb, buf, 100); + if (len != 100 || get_u32_be(buf) != 9994) + die("Invalid shape file header"); + + u32 version = get_u32_le(buf+28); + if (version != 1000) + die("Unknown shape file version %u", version); + + u32 type = get_u32_le(buf+32); + if (type != 3) + die("Unsupported shape file type %u", type); + + u32 last_recno = 0; + while (len = bread(fb, buf, 8)) + { + if (len != 8) + die("Truncated record header"); + u32 recno = get_u32_be(buf); + u32 reclen = get_u32_be(buf+4) * 2; + DBG("@%ju: recno %u len %u", (uintmax_t) btell(fb) - 8, recno, reclen); + if (recno != ++last_recno) + die("Unexpected record #%u, should be #%u", recno, last_recno); + if (reclen > GARY_SIZE(buf)) + GARY_RESIZE(buf, reclen); + len = bread(fb, buf, reclen); + if (len != reclen) + die("Truncated record: %u < %u", len, reclen); + if (len < 4) + die("Record too short: %u bytes", reclen); + + u32 rectype = get_u32_le(buf); + if (rectype == 3) + shp_record_polyline(buf, len); + } + + GARY_FREE(buf); + bclose(fb); +} diff --git a/shp.h b/shp.h new file mode 100644 index 0000000..febd395 --- /dev/null +++ b/shp.h @@ -0,0 +1,12 @@ +/* + * Hic Est Leo -- Reading ESRI Shape Files + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_SHP_H +#define _BRUM_SHP_H + +void shp_parse(const char *name); + +#endif diff --git a/style.c b/style.c new file mode 100644 index 0000000..d82c712 --- /dev/null +++ b/style.c @@ -0,0 +1,271 @@ +/* + * Hic Est Leo -- Styling + * + * (c) 2014 Martin Mares + */ + +#include +#include + +#include + +#include "leo.h" +#include "osm.h" +#include "style.h" + +struct dict style_prop_dict, style_layer_dict; + +static const char * const style_wk_props[] = { + NULL, +#define P(x,y) [PROP_##x] = y, +#include "dict-props.h" +#undef P + NULL +}; + +static const char * const style_wk_layers[] = { + NULL, + "default", + NULL +}; + +void styles_init(void) +{ + dict_init(&style_prop_dict, style_wk_props); + dict_init(&style_layer_dict, style_wk_layers); +} + +#define HASH_NODE struct style_prop +#define HASH_PREFIX(x) style_prop_##x +#define HASH_KEY_ATOMIC key +#define HASH_WANT_FIND +#define HASH_WANT_LOOKUP +#define HASH_GIVE_ALLOC +// FIXME: Make accept our pool +#define HASH_ZERO_FILL +#define HASH_TABLE_DYNAMIC +#define HASH_TABLE_ALLOC +#define HASH_TABLE_VARS struct mempool *pool; +static void *style_prop_alloc(struct style_prop_table *table, uns size); +static inline void style_prop_free(struct style_prop_table *table UNUSED, void *x UNUSED) { } +#include + +static void *style_prop_alloc(struct style_prop_table *table, uns size) +{ + return mp_alloc_fast(table->pool, size); +} + +void style_init(struct style_results *r) +{ + r->pool = mp_new(4096); + r->num_layers = dict_size(&style_layer_dict); + r->layers = mp_alloc_zero(r->pool, r->num_layers * sizeof(struct style_info *)); + r->num_active_layers = 0; + r->active_layers = mp_alloc_zero(r->pool, r->num_layers * sizeof(layer_t)); +} + +void style_begin(struct style_results *r, struct osm_object *o) +{ + ASSERT(!r->num_active_layers); + mp_push(r->pool); + r->obj = o; +} + +void style_end(struct style_results *r) +{ + for (uns i=0; inum_active_layers; i++) + r->layers[r->active_layers[i]] = NULL; + r->layers[0] = NULL; + r->num_active_layers = 0; + mp_pop(r->pool); +} + +static inline void style_assign(struct style_prop *dest, struct style_prop *src) +{ + dest->type = src->type; + dest->val = src->val; +} + +static struct style_info *style_get_info(struct style_results *r, layer_t layer) +{ + ASSERT(layer < r->num_layers); + if (!r->layers[layer]) + { + struct style_info *si = mp_alloc_zero(r->pool, sizeof(*si)); + r->layers[layer] = si; + si->hash = mp_alloc_zero(r->pool, sizeof(struct style_prop_table)); + si->hash->pool = r->pool; + style_prop_init(si->hash); + + if (layer != STYLE_LAYER_ALL) + { + r->active_layers[r->num_active_layers++] = layer; + + // Copy all properties which have been set for all layers + struct style_info *si_all = r->layers[STYLE_LAYER_ALL]; + if (si_all) + { + /* + * CAVEAT: This is probably wrong. When no properties are set explicitly + * set for a layer, all-layer properties are not propagated. Hopefully harmless. + */ + HASH_FOR_ALL_DYNAMIC(style_prop, si_all->hash, s) + { + style_assign(style_prop_lookup(si->hash, s->key), s); + } + HASH_END_FOR; + } + } + } + return r->layers[layer]; +} + +void style_set_by_layer(struct style_results *r, layer_t layer, struct style_prop *p) +{ + if (layer == STYLE_LAYER_ALL) + { + // Set in all existing layers + for (uns i=0; i < r->num_active_layers; i++) + style_assign(style_prop_lookup(r->layers[r->active_layers[i]]->hash, p->key), p); + // ... and let it propagate to STYLE_LAYER_ALL + } + + struct style_info *si = style_get_info(r, layer); + style_assign(style_prop_lookup(si->hash, p->key), p); +} + +void style_set(struct style_info *si, struct style_prop *p) +{ + style_assign(style_prop_lookup(si->hash, p->key), p); +} + +struct style_prop *style_get(struct style_info *si, prop_t key) +{ + return style_prop_find(si->hash, key); +} + +struct style_prop *style_get_and_check(struct style_info *si, prop_t key, uns allowed_types) +{ + struct style_prop *p = style_prop_find(si->hash, key); + if (!p) + return NULL; + if (!(allowed_types & (1 << p->type))) + { + // XXX: Better diagnostics? + msg(L_WARN, "Style property %s set to invalid type #%u", style_prop_decode(p->key), p->type); + return NULL; + } + return p; +} + +osm_val_t style_get_ident(struct style_info *si, prop_t key) +{ + struct style_prop *p = style_get_and_check(si, key, 1 << PROP_TYPE_IDENT); + return p ? p->val.id : 0; +} + +osm_val_t style_get_string(struct style_info *si, prop_t key) +{ + struct style_prop *p = style_get_and_check(si, key, (1 << PROP_TYPE_STRING) | (1 << PROP_TYPE_IDENT)); + return p ? p->val.id : 0; +} + +bool style_get_number(struct style_info *si, prop_t key, double *dp) +{ + struct style_prop *p = style_get_and_check(si, key, 1 << PROP_TYPE_NUMBER); + if (!p) + return 0; + *dp = p->val.number; + return 1; +} + +bool style_get_color(struct style_info *si, prop_t key, color_t *colorp) +{ + struct style_prop *p = style_get_and_check(si, key, 1 << PROP_TYPE_COLOR); + if (!p) + return 0; + *colorp = p->val.color; + return 1; +} + +void style_dump(struct style_results *r) +{ + for (uns i=0; i < r->num_active_layers; i++) + { + layer_t layer = r->active_layers[i]; + printf("Layer %s (%u)\n", style_layer_decode(layer), layer); + struct style_info *si = r->layers[layer]; + HASH_FOR_ALL_DYNAMIC(style_prop, si->hash, s) + { + printf("\t"); + style_dump_prop(s); + } + HASH_END_FOR; + } +} + +static void style_dump_val(struct style_prop *s) +{ + uns cnt = 0; + + switch (s->type) + { + case PROP_TYPE_STRING: + printf("%s [string]", osm_val_decode(s->val.id)); + break; + case PROP_TYPE_IDENT: + printf("%s [ident]", osm_val_decode(s->val.id)); + break; + case PROP_TYPE_NUMBER: + printf("%.6g [number]", s->val.number); + break; + case PROP_TYPE_COLOR: + printf("%06x [color]", s->val.color); + break; + case PROP_TYPE_LIST: + putchar('('); + CLIST_FOR_EACH(struct style_val_list_entry *, e, *s->val.list) + { + if (cnt++) + printf(", "); + style_dump_val(&e->val); + } + printf(") [list]"); + break; + default: + printf("[unknown type %u]", s->type); + } +} + +void style_dump_prop(struct style_prop *s) +{ + printf("%s = ", style_prop_decode(s->key)); + style_dump_val(s); + putchar('\n'); +} + +void style_scale(struct style_info *si, double *wp, double *hp, prop_t width_prop, prop_t height_prop) +{ + double w, h; + bool got_width = style_get_number(si, width_prop, &w); + bool got_height = style_get_number(si, height_prop, &h); + + if (got_width + got_height == 2) + { + *wp = w; + *hp = h; + } + else if (got_width + got_height == 1) + { + if (got_width) + { + *hp *= w / *wp; + *wp = w; + } + else + { + *wp *= h / *hp; + *hp = h; + } + } +} diff --git a/style.h b/style.h new file mode 100644 index 0000000..b8509f1 --- /dev/null +++ b/style.h @@ -0,0 +1,108 @@ +/* + * Hic Est Leo -- Styling + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_STYLE_H +#define _BRUM_STYLE_H + +#include "osm.h" +#include "dict.h" + +typedef u32 prop_t, layer_t; // See dictionaries below + +enum style_layer { + STYLE_LAYER_ALL, + STYLE_LAYER_DEFAULT, + // The other style are defined by style_layer_dict +}; + +struct style_results { // Computed style information for a given object in all layers + struct osm_object *obj; + uns num_layers; + struct style_info **layers; + uns num_active_layers; + layer_t *active_layers; + struct mempool *pool; +}; + +struct style_info { // Computed style information per layer + struct style_prop_table *hash; // Hash table of style properties +}; + +enum style_prop_type { + PROP_TYPE_INVALID, + PROP_TYPE_STRING, // Quoted string hashed in osm_value_dict + PROP_TYPE_IDENT, // Unquoted string hashed in osm_value_dict + PROP_TYPE_NUMBER, // Floating-point number + PROP_TYPE_COLOR, // Color represented in RGB + PROP_TYPE_LIST, // List of values +}; + +struct style_prop { + prop_t key; // From style_prop_dict + enum style_prop_type type; // PROP_TYPE_xxx + union { + osm_val_t id; + color_t color; + double number; + clist *list; // Of style_val_list_entry + } val; +}; + +struct style_val_list_entry { + cnode n; + struct style_prop val; // val.key is unused +}; + +enum prop_keys { // Well-known properties + PROP_NULL, +#define P(x,y) PROP_##x, +#include "dict-props.h" +#undef P +}; + +void styles_init(void); +void style_init(struct style_results *r); +void style_begin(struct style_results *r, struct osm_object *o); +void style_end(struct style_results *r); + +void style_set_by_layer(struct style_results *r, layer_t layer, struct style_prop *p); + +void style_set(struct style_info *si, struct style_prop *p); +struct style_prop *style_get(struct style_info *si, prop_t key); +osm_val_t style_get_ident(struct style_info *si, prop_t key); +osm_val_t style_get_string(struct style_info *si, prop_t key); +bool style_get_number(struct style_info *si, prop_t key, double *dp); +bool style_get_color(struct style_info *si, prop_t key, color_t *colorp); +struct style_prop *style_get_and_check(struct style_info *si, prop_t key, uns allowed_types); + +extern struct dict style_prop_dict, style_layer_dict; + +static inline prop_t style_prop_encode(const char *key) +{ + return dict_encode(&style_prop_dict, key); +} + +static inline const char *style_prop_decode(prop_t id) +{ + return dict_decode(&style_prop_dict, id); +} + +static inline layer_t style_layer_encode(const char *key) +{ + return dict_encode(&style_layer_dict, key); +} + +static inline const char *style_layer_decode(layer_t id) +{ + return dict_decode(&style_layer_dict, id); +} + +void style_dump(struct style_results *r); +void style_dump_prop(struct style_prop *p); + +void style_scale(struct style_info *si, double *wp, double *hp, prop_t width_prop, prop_t height_prop); + +#endif diff --git a/svg-icon.c b/svg-icon.c new file mode 100644 index 0000000..34555e7 --- /dev/null +++ b/svg-icon.c @@ -0,0 +1,237 @@ +/* + * Hic Est Leo -- SVG Icon Embedder + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "leo.h" +#include "svg.h" + +#define HASH_NODE struct svg_icon +#define HASH_PREFIX(x) icon_##x +#define HASH_KEY_ENDSTRING name +#define HASH_WANT_LOOKUP +#define HASH_WANT_CLEANUP +#define HASH_ZERO_FILL +#define HASH_TABLE_DYNAMIC +#include + +static void svg_icon_error(struct xml_context *ctx) +{ + fprintf(stderr, "%s at %u: %s\n", (ctx->err_code < XML_ERR_ERROR) ? "warn" : "error", xml_row(ctx), ctx->err_msg); +} + +struct svg_icon *svg_icon_load(struct svg *svg, const char *name) +{ + struct svg_icon *icon = icon_lookup(svg->icon_hash, (char *) name); + if (icon->id) + return icon; + icon->id = ++svg->icon_counter; + clist_init(&icon->patterns); + + struct xml_context *ctx = xmalloc(sizeof(*ctx)); + xml_init(ctx); + ctx->h_warn = ctx->h_error = ctx->h_fatal = svg_icon_error; + xml_push_fastbuf(ctx, bopen_file(name, O_RDONLY, NULL)); + + ctx->flags |= XML_ALLOC_TAGS | XML_ALLOC_CHARS; + xml_parse(ctx); + if (ctx->err_code) + die("Error reading icon %s: Fatal error in XML parser", name); + + icon->ctx = ctx; + icon->root = ctx->dom; + + struct xml_node *root = icon->root; + ASSERT(root); + if (strcmp(root->name, "svg")) + die("Error reading icon %s: root element is <%s>, not ", name, root->name); + + struct xml_attr *a_width = xml_attr_find(ctx, root, "width"); + struct xml_attr *a_height = xml_attr_find(ctx, root, "height"); + if (!a_width || !a_height) + die("Error reading icon %s: cannot determine image dimensions", name); + icon->width = atof(a_width->val) / svg->scale; + icon->height = atof(a_height->val) / svg->scale; + if (icon->width < 0.01 || icon->height < 0.01) + die("Error reading icon %s: invalid icon dimensions", name); + // FIXME: What if dimensions are given in absolute units? + + msg(L_DEBUG, "Loaded icon %s (%.5g x %.5g)", name, icon->width, icon->height); + return icon; +} + +static void svg_icon_unload(struct svg_icon *icon) +{ + xml_cleanup(icon->ctx); + xfree(icon->ctx); +} + +static bool is_white(int c) +{ + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +static void normalize_white(char *r) +{ + char *w = r; + + while (is_white(*r)) + r++; + while (*r) + { + if (is_white(*r)) + { + while (is_white(*r)) + r++; + if (*r) + *w++ = *r++; + } + else + *w++ = *r++; + } + *w = 0; +} + +static void svg_embed(struct svg *svg, struct svg_icon *icon, struct xml_node *n) +{ + if (n->type == XML_NODE_CHARS) + { + char *t = mp_strdup(svg->pool, n->text); + normalize_white(t); + if (*t) + { + svg_push_chars(svg)->name = n->text; + svg_pop(svg); + } + return; + } + ASSERT(n->type == XML_NODE_ELEM); + + svg_push_element(svg, n->name); + XML_ATTR_FOR_EACH(a, n) + { + char *val = a->val; + if (!strcmp(a->name, "id")) + val = mp_printf(svg->pool, "i%u-%s", icon->id, val); + else if (!strcmp(a->name, "xlink:href") && val[0] == '#') + val = mp_printf(svg->pool, "#i%u-%s", icon->id, val+1); + // FIXME: Some attributes can have values "url(#id)" + svg_set_attr(svg, a->name, val); + } + XML_NODE_FOR_EACH(e, n) + svg_embed(svg, icon, e); + svg_pop(svg); +} + +static void svg_icon_embed(struct svg *svg, struct svg_icon *icon) +{ + svg_push_element(svg, "g"); + svg_set_attr_format(svg, "id", "icon%u", icon->id); + + svg_push_comment(svg)->name = mp_printf(svg->pool, "Embedded %s", icon->name); + svg_pop(svg); + + svg_embed(svg, icon, icon->root); + svg_pop(svg); +} + +void svg_icon_init(struct svg *svg) +{ + svg->icon_hash = mp_alloc_zero(svg->pool, sizeof(struct icon_table)); + icon_init(svg->icon_hash); +} + +void svg_icon_cleanup(struct svg *svg) +{ + HASH_FOR_ALL_DYNAMIC(icon, svg->icon_hash, icon) + { + svg_icon_unload(icon); + } + HASH_END_FOR; + icon_cleanup(svg->icon_hash); +} + +void svg_icon_put(struct svg *svg, struct svg_icon_request *sir) +{ + struct svg_icon *icon = sir->icon; + double scale_x = sir->width / icon->width; + double scale_y = sir->height / icon->height; + + // Center + // FIXME: More alignment modes? + double x = sir->x - sir->width / 2; + double y = sir->y - sir->height / 2; + + svg_push_element(svg, "use"); + svg_set_attr_format(svg, "xlink:href", "#icon%u", icon->id); + svg_set_attr_dimen(svg, "x", x / scale_x); + svg_set_attr_dimen(svg, "y", y / scale_y); + if (fabs(scale_x - 1) > 1e-10 || fabs(scale_y - 1) > 1e-10) + svg_set_attr_format(svg, "transform", "scale(%.5g %.5g)", scale_x, scale_y); + svg_pop(svg); +} + +struct svg_pattern *svg_icon_to_pattern(struct svg *svg, struct svg_pattern_request *spr) +{ + CLIST_FOR_EACH(struct svg_pattern *, patt, spr->icon->patterns) + { + if (fabs(patt->width - spr->width) < 1e-10 && + fabs(patt->height - spr->height) < 1e-10) + return patt; + } + + struct svg_pattern *patt = mp_alloc_zero(svg->pool, sizeof(*patt)); + clist_add_tail(&spr->icon->patterns, &patt->n); + patt->id = ++svg->pattern_counter; + patt->icon = spr->icon; + patt->width = spr->width; + patt->height = spr->height; + patt->paint_server = mp_printf(svg->pool, "url(#patt%u)", patt->id); + return patt; +} + +static void svg_pattern_embed(struct svg *svg, struct svg_pattern *patt) +{ + struct svg_icon *icon = patt->icon; + + svg_push_element(svg, "pattern"); + svg_set_attr_format(svg, "id", "patt%u", patt->id); + svg_set_attr(svg, "patternUnits", "userSpaceOnUse"); + svg_set_attr(svg, "patternContentUnits", "userSpaceOnUse"); + svg_set_attr_dimen(svg, "width", patt->width); + svg_set_attr_dimen(svg, "height", patt->height); + + struct svg_icon_request sir = { + .icon = icon, + .x = patt->width / 2, + .y = patt->height / 2, + .width = patt->width, + .height = patt->height, + }; + svg_icon_put(svg, &sir); + + svg_pop(svg); +} + +void svg_icon_dump_library(struct svg *svg) +{ + svg_push_element(svg, "defs"); + HASH_FOR_ALL_DYNAMIC(icon, svg->icon_hash, icon) + { + svg_icon_embed(svg, icon); + CLIST_FOR_EACH(struct svg_pattern *, patt, icon->patterns) + svg_pattern_embed(svg, patt); + } + HASH_END_FOR; + svg_pop(svg); +} diff --git a/svg.c b/svg.c new file mode 100644 index 0000000..ec3057a --- /dev/null +++ b/svg.c @@ -0,0 +1,365 @@ +/* + * Hic Est Leo -- SVG Generator + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include +#include + +#include +#include +#include + +#include "leo.h" +#include "svg.h" + +static void svg_start_tag(struct svg *svg, struct svg_element *e); +static void svg_escape_string(struct svg *svg, const char *str); + +struct svg *svg_open(char *filename) +{ + struct mempool *mp = mp_new(4096); + struct svg *svg = mp_alloc_zero(mp, sizeof(*svg)); + + svg->pool = mp; + svg->fb = bopen_file(filename, O_WRONLY | O_CREAT | O_TRUNC, NULL); + svg->scale = 90 / 25.4; + // FIXME: Use scale for all operations with dimensions? + GARY_INIT(svg->stack, 0); + + svg->fb_pool = mp_alloc_zero(svg->pool, sizeof(*svg->fb_pool)); + fbpool_init(svg->fb_pool); + + svg_icon_init(svg); + + bputsn(svg->fb, ""); + bputsn(svg->fb, ""); + + svg_push_element(svg, "svg"); + svg_set_attr(svg, "version", "1.1"); + svg_set_attr(svg, "xmlns", "http://www.w3.org/2000/svg"); + svg_set_attr(svg, "xmlns:xlink", "http://www.w3.org/1999/xlink"); + + return svg; +} + +void svg_close(struct svg *svg) +{ + ASSERT(GARY_SIZE(svg->stack) == 1); + svg_pop(svg); + + bclose(svg->fb); + GARY_FREE(svg->stack); + svg_icon_cleanup(svg); + mp_delete(svg->pool); +} + +static inline struct svg_element *svg_element_this_or_null(struct svg *svg) +{ + uns n = GARY_SIZE(svg->stack); + return n ? svg->stack[n-1] : NULL; +} + +static inline struct svg_element *svg_element_this(struct svg *svg) +{ + struct svg_element *e = svg_element_this_or_null(svg); + ASSERT(e); + return e; +} + +enum svg_indent_mode { + SVG_INDENT_OPEN = 1, + SVG_INDENT_CLOSE = 2, +}; + +static void svg_indent_start(struct svg *svg, struct svg_element *e, enum svg_indent_mode mode) +{ + if (e->indent < 0 || (e->flags & SVG_EF_FLAT) && mode != SVG_INDENT_OPEN) + return; + for (int i=0; iindent; i++) + bputc(svg->fb, '\t'); +} + +static void svg_indent_end(struct svg *svg, struct svg_element *e, enum svg_indent_mode mode) +{ + if (e->indent < 0 || (e->flags & SVG_EF_FLAT) && mode != SVG_INDENT_CLOSE) + return; + bputc(svg->fb, '\n'); +} + +static struct svg_element *svg_push_internal(struct svg *svg, uns type) +{ + ASSERT(!svg->str_fb); + + struct svg_element *parent = svg_element_this_or_null(svg); + if (parent) + { + ASSERT(parent->type == XML_NODE_ELEM); + parent->flags |= SVG_EF_HAS_CHILDREN; + if (!(parent->flags & SVG_EF_START_TAG)) + svg_start_tag(svg, parent); + } + + mp_push(svg->pool); + struct svg_element *e = mp_alloc(svg->pool, sizeof(*e)); + e->type = type; + e->flags = 0; + e->name = NULL; + clist_init(&e->attrs); + *GARY_PUSH(svg->stack) = e; + + if (parent) + { + if (parent->indent < 0 || (parent->flags & SVG_EF_FLAT)) + e->indent = -1; + else + e->indent = parent->indent + 1; + } + else + e->indent = 0; + + return e; +} + +struct svg_element *svg_push_element(struct svg *svg, const char *name) +{ + struct svg_element *e = svg_push_internal(svg, XML_NODE_ELEM); + e->name = mp_strdup(svg->pool, name); + return e; +} + +struct svg_element *svg_push_chars(struct svg *svg) +{ + return svg_push_internal(svg, XML_NODE_CHARS); +} + +struct svg_element *svg_push_comment(struct svg *svg) +{ + return svg_push_internal(svg, XML_NODE_COMMENT); +} + +void svg_pop(struct svg *svg) +{ + struct svg_element *e = svg_element_this(svg); + ASSERT(!svg->str_fb); + + switch (e->type) + { + case XML_NODE_ELEM: + if (!(e->flags & SVG_EF_START_TAG)) + { + // Will recognize that the element has no children and an auto-closing tag + svg_start_tag(svg, e); + } + else + { + svg_indent_start(svg, e, SVG_INDENT_CLOSE); + bprintf(svg->fb, "", e->name); + svg_indent_end(svg, e, SVG_INDENT_CLOSE); + } + break; + case XML_NODE_CHARS: + ASSERT(e->name); + svg_indent_start(svg, e, SVG_INDENT_OPEN); + svg_escape_string(svg, e->name); + svg_indent_end(svg, e, SVG_INDENT_CLOSE); + break; + case XML_NODE_COMMENT: + ASSERT(e->name); + svg_indent_start(svg, e, SVG_INDENT_OPEN); + bputs(svg->fb, ""); + svg_indent_end(svg, e, SVG_INDENT_CLOSE); + break; + default: + ASSERT(0); + } + + mp_pop(svg->pool); + GARY_POP(svg->stack); +} + +static void svg_escape_string(struct svg *svg, const char *str) +{ + for (const char *c = str; *c; c++) + switch (*c) + { + case '"': + bputs(svg->fb, """); + break; + case '\'': + bputs(svg->fb, "'"); + break; + case '<': + bputs(svg->fb, "<"); + break; + case '>': + bputs(svg->fb, ">"); + break; + case '&': + bputs(svg->fb, "&"); + break; + default: + bputc(svg->fb, *c); + } +} + +static void svg_start_tag(struct svg *svg, struct svg_element *e) +{ + ASSERT(!(e->flags & SVG_EF_START_TAG)); + e->flags |= SVG_EF_START_TAG; + + svg_indent_start(svg, e, SVG_INDENT_OPEN); + bputc(svg->fb, '<'); + bputs(svg->fb, e->name); + + CLIST_FOR_EACH(struct svg_attr *, a, e->attrs) + { + bputc(svg->fb, ' '); + bputs(svg->fb, a->key); + bputs(svg->fb, "=\""); + svg_escape_string(svg, a->val); + bputc(svg->fb, '"'); + } + + if (e->flags & SVG_EF_HAS_CHILDREN) + { + bputc(svg->fb, '>'); + svg_indent_end(svg, e, SVG_INDENT_OPEN); + } + else + { + bputs(svg->fb, " />"); + svg_indent_end(svg, e, SVG_INDENT_CLOSE); + } +} + +void svg_set_attr_ref(struct svg *svg, const char *key, const char *val) +{ + struct svg_element *e = svg_element_this(svg); + ASSERT(e->type == XML_NODE_ELEM); + ASSERT(!(e->flags & SVG_EF_START_TAG)); + ASSERT(!svg->str_fb); + + CLIST_FOR_EACH(struct svg_attr *, a, e->attrs) + if (!strcmp(a->key, key)) + { + a->val = val; + return; + } + + struct svg_attr *a = mp_alloc(svg->pool, sizeof(*a)); + a->key = mp_strdup(svg->pool, key); + a->val = val; + clist_add_tail(&e->attrs, &a->n); +} + +void svg_set_attr(struct svg *svg, const char *key, const char *val) +{ + svg_set_attr_ref(svg, key, mp_strdup(svg->pool, val)); +} + +void svg_set_attr_int(struct svg *svg, const char *key, int val) +{ + svg_set_attr_ref(svg, key, mp_printf(svg->pool, "%d", val)); +} + +void svg_set_attr_float(struct svg *svg, const char *key, double val) +{ + svg_set_attr_ref(svg, key, mp_printf(svg->pool, "%.6g", val)); +} + +void svg_set_attr_dimen(struct svg *svg, const char *key, double val) +{ + svg_set_attr_ref(svg, key, mp_printf(svg->pool, "%.6g", val * svg->scale)); +} + +void svg_set_attr_color(struct svg *svg, const char *key, color_t color) +{ + svg_set_attr_ref(svg, key, mp_printf(svg->pool, "#%06x", color)); +} + +void svg_set_attr_format(struct svg *svg, const char *key, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + svg_set_attr_ref(svg, key, mp_vprintf(svg->pool, fmt, args)); + va_end(args); +} + +struct fastbuf *svg_fb_open(struct svg *svg) +{ + ASSERT(!svg->str_fb); + fbpool_start(svg->fb_pool, svg->pool, 0); + svg->str_fb = (struct fastbuf *) svg->fb_pool; + return svg->str_fb; +} + +const char *svg_fb_close(struct svg *svg) +{ + ASSERT(svg->str_fb); + bputc(svg->str_fb, 0); + const char *str = fbpool_end(svg->fb_pool); + svg->str_fb = NULL; + return str; +} + +void svg_fb_close_as_attr(struct svg *svg, const char *key) +{ + const char *val = svg_fb_close(svg); + svg_set_attr_ref(svg, key, val); +} + +struct svg_element *svg_push_path(struct svg *svg) +{ + struct svg_element *e = svg_push_element(svg, "path"); + svg_fb_open(svg); + + // Construct the path, then call svg_path_end() and finally svg_pop() + // CAVEAT: Do not set attributes before calling svg_path_end() + return e; +} + +void svg_path_end(struct svg *svg) +{ + svg_fb_close_as_attr(svg, "d"); +} + +static void svg_path_printf(struct svg *svg, const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + if (btell(svg->str_fb)) + bputc(svg->str_fb, ' '); + vbprintf(svg->str_fb, fmt, args); + va_end(args); +} + +void svg_path_move_to(struct svg *svg, double x, double y) +{ + svg_path_printf(svg, "M %.6g %.6g", x * svg->scale, y * svg->scale); +} + +void svg_path_move_to_rel(struct svg *svg, double x, double y) +{ + svg_path_printf(svg, "m %.6g %.6g", x * svg->scale, y * svg->scale); +} + +void svg_path_line_to(struct svg *svg, double x, double y) +{ + svg_path_printf(svg, "L %.6g %.6g", x * svg->scale, y * svg->scale); +} + +void svg_path_line_to_rel(struct svg *svg, double x, double y) +{ + svg_path_printf(svg, "l %.6g %.6g", x * svg->scale, y * svg->scale); +} + +void svg_path_close(struct svg *svg) +{ + svg_path_printf(svg, "Z"); +} diff --git a/svg.h b/svg.h new file mode 100644 index 0000000..09f8bd3 --- /dev/null +++ b/svg.h @@ -0,0 +1,112 @@ +/* + * Hic Est Leo -- SVG Output + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_SVG_H +#define _BRUM_SVG_H + +// FIXME: Passing SVG pointers everywhere is ugly, using global context less so. + +struct svg { + struct fastbuf *fb; + struct svg_element **stack; + struct mempool *pool; + struct fbpool *fb_pool; + struct fastbuf *str_fb; // fb_pool cast to the right type + double scale; // 1mm is converted to this many px + uns icon_counter; + struct icon_table *icon_hash; + uns pattern_counter; +}; + +struct svg_element { + byte type; // XML_NODE_xxx + byte flags; // SVG_EF_xxx + byte rfu[2]; + int indent; // -1 = inline + const char *name; + clist attrs; +}; + +struct svg_attr { + cnode n; + const char *key, *val; +}; + +enum svg_element_flags { + SVG_EF_FLAT = 1, + SVG_EF_START_TAG = 2, // Internal: start tag has been already printed + SVG_EF_HAS_CHILDREN = 4, +}; + +struct svg *svg_open(char *filename); +void svg_close(struct svg *svg); + +struct svg_element *svg_push_element(struct svg *svg, const char *name); +struct svg_element *svg_push_chars(struct svg *svg); +struct svg_element *svg_push_comment(struct svg *svg); +void svg_pop(struct svg *svg); + +void svg_set_attr(struct svg *svg, const char *key, const char *val); +void svg_set_attr_ref(struct svg *svg, const char *key, const char *val); +void svg_set_attr_int(struct svg *svg, const char *key, int val); +void svg_set_attr_float(struct svg *svg, const char *key, double val); +void svg_set_attr_dimen(struct svg *svg, const char *key, double val); +void svg_set_attr_color(struct svg *svg, const char *key, color_t color); +void svg_set_attr_format(struct svg *svg, const char *key, const char *fmt, ...) FORMAT_CHECK(printf,3,4); + +struct fastbuf *svg_fb_open(struct svg *svg); +const char *svg_fb_close(struct svg *svg); +void svg_fb_close_as_attr(struct svg *svg, const char *key); + +struct svg_element *svg_push_path(struct svg *svg); +void svg_path_end(struct svg *svg); +void svg_path_move_to(struct svg *svg, double x, double y); +void svg_path_move_to_rel(struct svg *svg, double x, double y); +void svg_path_line_to(struct svg *svg, double x, double y); +void svg_path_line_to_rel(struct svg *svg, double x, double y); +void svg_path_close(struct svg *svg); + +/* svg-icon.c */ + +struct svg_icon { + uns id; + double width, height; + struct xml_context *ctx; + struct xml_node *root; + clist patterns; + char name[1]; +}; + +struct svg_icon *svg_icon_load(struct svg *svg, const char *name); +void svg_icon_dump_library(struct svg *svg); + +void svg_icon_init(struct svg *svg); // Called from svg_open() +void svg_icon_cleanup(struct svg *svg); // Called from svg_close() + +struct svg_icon_request { + struct svg_icon *icon; + double x, y; + double width, height; +}; + +void svg_icon_put(struct svg *svg, struct svg_icon_request *sir); + +struct svg_pattern_request { + struct svg_icon *icon; + double width, height; +}; + +struct svg_pattern { + cnode n; + uns id; + struct svg_icon *icon; + double width, height; + char *paint_server; +}; + +struct svg_pattern *svg_icon_to_pattern(struct svg *svg, struct svg_pattern_request *spr); + +#endif diff --git a/sym-line.c b/sym-line.c new file mode 100644 index 0000000..4944fcd --- /dev/null +++ b/sym-line.c @@ -0,0 +1,378 @@ +/* + * Hic Est Leo -- Line and Area Symbolizer + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include + +#include +#include + +#include "leo.h" +#include "osm.h" +#include "sym.h" + +static void sym_line_attrs(struct sym_line *l, struct svg *svg) +{ + svg_set_attr(svg, "fill", "none"); + svg_set_attr_color(svg, "stroke", l->color); + svg_set_attr_dimen(svg, "stroke-width", l->width); + if (l->opacity != 1) + svg_set_attr_float(svg, "stroke-opacity", l->opacity); + + switch (l->line_cap) + { + case VALUE_NONE: + // This is the default (butt) + break; + case VALUE_ROUND: + case VALUE_SQUARE: + svg_set_attr(svg, "stroke-linecap", osm_val_decode(l->line_cap)); + break; + default: + osm_obj_warn(l->s.o, "Unknown stroke-linecap: %s", osm_val_decode(l->line_cap)); + } + + switch (l->line_join) + { + case VALUE_MITER: + // This is the default + if (l->miter_limit != 4) + svg_set_attr_float(svg, "stroke-miterlimit", l->miter_limit); + break; + case VALUE_ROUND: + case VALUE_BEVEL: + svg_set_attr(svg, "stroke-linejoin", osm_val_decode(l->line_join)); + break; + default: + osm_obj_warn(l->s.o, "Unknown stroke-linejoin: %s", osm_val_decode(l->line_join)); + } + + if (l->dash_pattern) + { + struct fastbuf *fb = svg_fb_open(svg); + for (uns i=0; i < GARY_SIZE(l->dash_pattern); i++) + { + if (i) + bputc(fb, ','); + // FIXME: This is dimension-sensitive + // FIXME: Also, inkscape doesn't handle units in dash lengths + bprintf(fb, "%.6g", l->dash_pattern[i]); + } + svg_fb_close_as_attr(svg, "stroke-dasharray"); + if (l->dash_offset) + svg_set_attr_float(svg, "stroke-dashoffset", l->dash_offset); + } +} + +static void append_node_list(struct svg *svg, clist *list) +{ + struct osm_node *first = NULL; + OSM_FOR_EACH_BEGIN(struct osm_node *, n, *list) + { + if (!first) + { + first = n; + svg_path_move_to(svg, n->x, n->y); + } + else if (n == first) + svg_path_close(svg); + else + svg_path_line_to(svg, n->x, n->y); + } + OSM_FOR_EACH_END; +} + +static void make_path(struct osm_object *o, struct svg *svg) +{ + svg_push_path(svg); + switch (o->type) + { + case OSM_TYPE_WAY: + { + struct osm_way *w = (struct osm_way *) o; + append_node_list(svg, &w->nodes); + break; + } + case OSM_TYPE_MULTIPOLYGON: + { + struct osm_multipolygon *m = (struct osm_multipolygon *) o; + CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries) + append_node_list(svg, &b->nodes); + break; + } + default: + ASSERT(0); + } + svg_path_end(svg); +} + +static void sym_line_draw(struct symbol *sym, struct svg *svg) +{ + struct sym_line *l = (struct sym_line *) sym; + make_path(sym->o, svg); + sym_line_attrs(l, svg); + svg_pop(svg); +} + +static void sym_line_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED) +{ + if (o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON) + return; + + struct sym_line *main_sl = NULL; + + // Line and its casing are two similar objects + for (uns casing=0; casing<2; casing++) + { + double w; + if (!style_get_number(si, PROP_WIDTH + casing, &w)) + continue; + + struct sym_line *sl = sym_line_new(o); + sl->width = w; + + if (casing) + { + if (!main_sl) + { + osm_obj_warn(o, "Casing around no line"); + continue; + } + sl->width += main_sl->width; + } + else + main_sl = sl; + + sl->color = 0x808080; + style_get_color(si, PROP_FILL_COLOR, &sl->color); + style_get_color(si, PROP_COLOR + casing, &sl->color); + + sl->opacity = 1; + style_get_number(si, PROP_OPACITY + casing, &sl->opacity); + + sl->line_cap = style_get_ident(si, PROP_LINECAP + casing) ? : VALUE_NONE; + sl->line_join = style_get_ident(si, PROP_LINEJOIN + casing) ? : VALUE_ROUND; + sl->miter_limit = 10; + style_get_number(si, PROP_MITERLIMIT + casing, &sl->miter_limit); + + struct style_prop *dashes = style_get(si, PROP_DASHES + casing); + if (!dashes || dashes->type == PROP_TYPE_IDENT && dashes->val.id == VALUE_NONE) + ; + else if (dashes->type == PROP_TYPE_LIST) + { + GARY_INIT_ALLOC(sl->dash_pattern, 0, mp_get_allocator(sym_mp)); + CLIST_FOR_EACH(struct style_val_list_entry *, e, *dashes->val.list) + { + if (e->val.type == PROP_TYPE_NUMBER) + *GARY_PUSH(sl->dash_pattern) = e->val.val.number; + else + { + osm_obj_warn(o, "Invalid dash pattern"); + break; + } + } + style_get_number(si, PROP_DASHES_OFFSET + casing, &sl->dash_offset); + } + else + osm_obj_warn(o, "Invalid dash pattern"); + + sym_plan(&sl->s, sym_zindex(o, si, casing ? 2 : 3)); + } +} + +struct symbolizer symbolizer_line = { + .name = "line", + .draw = sym_line_draw, + .gen = sym_line_gen, +}; + +struct sym_line *sym_line_new(struct osm_object *o) +{ + return sym_new(SYMBOLIZER_LINE, o, sizeof(struct sym_line)); +} + +/*** Images along line ***/ + +static void lineimg_node_list(struct sym_lineimg *sli, clist *nodes, struct svg *svg) +{ + double phase = sli->phase; + double step = sli->sir.width + sli->spacing; + struct osm_node *prev = NULL; + OSM_FOR_EACH_BEGIN(struct osm_node *, n, *nodes) + { + if (prev) + { + double dx = n->x - prev->x; + double dy = n->y - prev->y; + double len = hypot(dx, dy); + double dist = 0; + while (len - dist > 1e-10) + { + // FIXME: Rotate images along the path? + double next = MIN(len - dist, step - phase); + dist += next; + phase += next; + if (step - phase < 1e-10) + { + struct svg_icon_request sir = sli->sir; + sir.x = prev->x + dx * dist / len; + sir.y = prev->y + dy * dist / len; + svg_icon_put(svg, &sir); + phase -= step; + } + } + } + prev = n; + } + OSM_FOR_EACH_END; +} + +static void sym_lineimg_draw(struct symbol *sym, struct svg *svg) +{ + struct sym_lineimg *li = (struct sym_lineimg *) sym; + struct osm_object *o = li->s.o; + + switch (o->type) + { + case OSM_TYPE_WAY: + { + struct osm_way *w = (struct osm_way *) o; + lineimg_node_list(li, &w->nodes, svg); + break; + } + case OSM_TYPE_MULTIPOLYGON: + { + struct osm_multipolygon *m = (struct osm_multipolygon *) o; + CLIST_FOR_EACH(struct osm_mpg_boundary *, b, m->boundaries) + lineimg_node_list(li, &b->nodes, svg); + break; + } + default: + ASSERT(0); + } +} + +static void sym_lineimg_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED) +{ + if (o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON) + return; + + osm_val_t icon_name = style_get_string(si, PROP_REPEAT_IMAGE); + if (!icon_name) + return; + + struct sym_lineimg *sli = sym_lineimg_new(o); + + struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(icon_name)); + sli->sir.icon = icon; + sli->sir.width = icon->width; + sli->sir.height = icon->height; + style_scale(si, &sli->sir.width, &sli->sir.height, PROP_REPEAT_IMAGE_WIDTH, PROP_REPEAT_IMAGE_HEIGHT); + + // FIXME: Better handling of defaults in style_get_number() +#if 0 + // FIXME: align and offset are not supported yet + sli->align = style_get_ident(si, PROP_REPEAT_IMAGE_ALIGN); + sli->offset = 0; + style_get_number(si, PROP_REPEAT_IMAGE_OFFSET, &sli->offset); +#endif + sli->spacing = 0; + style_get_number(si, PROP_REPEAT_IMAGE_SPACING, &sli->spacing); + sli->phase = 0; + style_get_number(si, PROP_REPEAT_IMAGE_PHASE, &sli->phase); + + sym_plan(&sli->s, sym_zindex(o, si, 3)); +} + +struct symbolizer symbolizer_lineimg = { + .name = "lineimg", + .draw = sym_lineimg_draw, + .gen = sym_lineimg_gen, +}; + +struct sym_lineimg *sym_lineimg_new(struct osm_object *o) +{ + return sym_new(SYMBOLIZER_LINEIMG, o, sizeof(struct sym_lineimg)); +} + +/*** Areas ***/ + +static void sym_area_draw(struct symbol *sym, struct svg *svg) +{ + struct sym_area *a = (struct sym_area *) sym; + + for (int i=0; i<2; i++) + { + if (!i) + { + if (a->fill_color == COLOR_NONE) + continue; + make_path(sym->o, svg); + svg_set_attr_color(svg, "fill", a->fill_color); + } + else + { + if (!a->fill_pattern) + continue; + make_path(sym->o, svg); + svg_set_attr(svg, "fill", a->fill_pattern->paint_server); + } + if (a->fill_opacity != 1) + svg_set_attr_float(svg, "opacity", a->fill_opacity); + if (sym->o->type == OSM_TYPE_MULTIPOLYGON) + svg_set_attr(svg, "fill-rule", "evenodd"); + svg_pop(svg); + } +} + +static void sym_area_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED) +{ + if (!(o->type == OSM_TYPE_WAY && osm_way_cyclic_p((struct osm_way *) o) || + o->type == OSM_TYPE_MULTIPOLYGON)) + return; + + color_t color; + if (!style_get_color(si, PROP_FILL_COLOR, &color)) + color = COLOR_NONE; + + osm_val_t pattern = style_get_string(si, PROP_FILL_PATTERN); + struct svg_pattern *patt = NULL; + if (pattern) + { + struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(pattern)); + struct svg_pattern_request spr = { + .icon = icon, + .width = icon->width, + .height = icon->height + }; + style_scale(si, &spr.width, &spr.height, PROP_FILL_PATTERN_WIDTH, PROP_FILL_PATTERN_HEIGHT); + patt = svg_icon_to_pattern(svg, &spr); + } + + if (color == COLOR_NONE && !patt) + return; + + struct sym_area *sa = sym_area_new(o); + sa->fill_color = color; + sa->fill_pattern = patt; + + sa->fill_opacity = 1; + style_get_number(si, PROP_FILL_OPACITY, &sa->fill_opacity); + + sym_plan(&sa->s, sym_zindex(o, si, 1)); +} + +struct symbolizer symbolizer_area = { + .name = "area", + .draw = sym_area_draw, + .gen = sym_area_gen, +}; + +struct sym_area *sym_area_new(struct osm_object *o) +{ + return sym_new(SYMBOLIZER_AREA, o, sizeof(struct sym_area)); +} diff --git a/sym-point.c b/sym-point.c new file mode 100644 index 0000000..c7c2be0 --- /dev/null +++ b/sym-point.c @@ -0,0 +1,153 @@ +/* + * Hic Est Leo -- Point and Icon Symbolizer + * + * (c) 2014 Martin Mares + */ + +#include + +#include + +#include "leo.h" +#include "osm.h" +#include "sym.h" +#include "svg.h" + +static void sym_point_draw(struct symbol *sym, struct svg *svg) +{ + struct sym_point *p = (struct sym_point *) sym; + struct osm_object *o = sym->o; + struct osm_node *n = (struct osm_node *) o; + + if (o->type != OSM_TYPE_NODE) + { + msg(L_ERROR, "Point symbolizer works only on nodes. It's a bug, isn't it?"); + return; + } + + switch (p->shape) + { + case VALUE_CIRCLE: + svg_push_element(svg, "circle"); + svg_set_attr_dimen(svg, "cx", n->x); + svg_set_attr_dimen(svg, "cy", n->y); + svg_set_attr_dimen(svg, "r", p->size / 2); + break; + // FIXME: Other shapes + default: + osm_obj_warn(o, "Unknown symbol-shape %s", osm_val_decode(p->shape)); + return; + } + + // FIMXE: Use COLOR_NONE instead of do_stroke and do_fill + if (p->do_stroke) + { + svg_set_attr_color(svg, "stroke", p->stroke_color); + svg_set_attr_dimen(svg, "stroke-width", p->stroke_width); + if (p->stroke_opacity != 1) + svg_set_attr_float(svg, "stroke-opacity", p->stroke_opacity); + } + if (p->do_fill) + { + svg_set_attr_color(svg, "fill", p->fill_color); + if (p->fill_opacity != 1) + svg_set_attr_float(svg, "fill-opacity", p->fill_opacity); + } + else + svg_set_attr(svg, "fill", "none"); + svg_pop(svg); +} + +static void sym_point_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED) +{ + if (o->type != OSM_TYPE_NODE) + return; + + osm_val_t shape = style_get_ident(si, PROP_SYMBOL_SHAPE); + if (!shape) + return; + + struct sym_point *sp = sym_point_new(o); + sp->shape = shape; + + sp->size = 1; + style_get_number(si, PROP_SYMBOL_SIZE, &sp->size); + + sp->stroke_width = 0.1; + sp->stroke_color = 0xffc800; + sp->stroke_opacity = 1; + if (style_get_color(si, PROP_SYMBOL_STROKE_COLOR, &sp->stroke_color) | + style_get_number(si, PROP_SYMBOL_STROKE_WIDTH, &sp->stroke_width)) + { + sp->do_stroke = 1; + style_get_number(si, PROP_SYMBOL_STROKE_OPACITY, &sp->stroke_opacity); + } + + sp->fill_color = 0x0000ff; + sp->fill_opacity = 1; + if (style_get_color(si, PROP_SYMBOL_FILL_COLOR, &sp->fill_color) || !sp->do_stroke) + { + sp->do_fill = 1; + style_get_number(si, PROP_SYMBOL_FILL_OPACITY, &sp->fill_opacity); + } + + sym_plan(&sp->s, sym_zindex(o, si, 4)); +} + +struct symbolizer symbolizer_point = { + .name = "point", + .draw = sym_point_draw, + .gen = sym_point_gen, +}; + +struct sym_point *sym_point_new(struct osm_object *o) +{ + return sym_new(SYMBOLIZER_POINT, o, sizeof(struct sym_point)); +} + +/*** Icons ***/ + +static void sym_icon_draw(struct symbol *sym, struct svg *svg) +{ + struct sym_icon *i = (struct sym_icon *) sym; + svg_icon_put(svg, &i->sir); +} + +static void sym_icon_gen(struct osm_object *o, struct style_info *si, struct svg *svg) +{ + if (o->type != OSM_TYPE_NODE && o->type != OSM_TYPE_WAY && o->type != OSM_TYPE_MULTIPOLYGON) + return; + + osm_val_t image = style_get_string(si, PROP_ICON_IMAGE); + if (!image) + return; + + struct sym_icon *sic = sym_icon_new(o); + struct svg_icon *icon = svg_icon_load(svg, osm_val_decode(image)); + struct svg_icon_request *sir = &sic->sir; + sir->icon = icon; + + if (!osm_obj_center(o, &sir->x, &sir->y)) + return; + + sir->width = icon->width; + sir->height = icon->height; + style_scale(si, &sir->width, &sir->height, PROP_ICON_WIDTH, PROP_ICON_HEIGHT); + + // FIXME + // sir->opacity = 1; + // style_get_number(si, PROP_ICON_OPACITY, &sir->opacity); + + sym_plan(&sic->s, sym_zindex(o, si, 4)); +} + +struct symbolizer symbolizer_icon = { + .name = "icon", + .draw = sym_icon_draw, + .gen = sym_icon_gen, +}; + +struct sym_icon *sym_icon_new(struct osm_object *o) +{ + return sym_new(SYMBOLIZER_ICON, o, sizeof(struct sym_icon)); +} diff --git a/sym-text.c b/sym-text.c new file mode 100644 index 0000000..2666acd --- /dev/null +++ b/sym-text.c @@ -0,0 +1,562 @@ +/* + * Hic Est Leo -- Text Symbolizer + * + * (c) 2014 Martin Mares + */ + +#include +#include + +#include +#include +#include FT_FREETYPE_H +#include + +#include "leo.h" +#include "osm.h" +#include "sym.h" +#include "map.h" + +/*** Fonts ***/ + +struct text_font { + const char *family; + double size; + osm_val_t weight; + osm_val_t style; + PangoFontDescription *pango_font_desc; + char key[1]; +}; + +#define HASH_NODE struct text_font +#define HASH_PREFIX(x) text_font_##x +#define HASH_KEY_ENDSTRING key +#define HASH_WANT_LOOKUP +#define HASH_LOOKUP_DETECT_NEW +#define HASH_USE_POOL sym_mp +#include + +static PangoFontMap *pango_font_map; +static PangoContext *pango_context; + +static double pt_to_mm(double pt) +{ + return pt / 72 * 25.4; +} + +static double mm_to_pt(double mm) +{ + return mm / 25.4 * 72; +} + +static double pango_to_mm(double pango) +{ + return pt_to_mm(pango / PANGO_SCALE); +} + +static double mm_to_pango(double mm) +{ + return mm_to_pt(mm) * PANGO_SCALE; +} + +/* + * Pango tries to do pixel-based optimizations in low resolutions. + * Work around it by scaling everything up. + */ +#define FONT_HACK_FACTOR 30 + +static struct text_font *font_get(struct text_font *req) +{ + char *key = stk_printf("%s:%.6g:%u:%u", req->family, req->size, req->weight, req->style); + int is_new = 0; + struct text_font *font = text_font_lookup(key, &is_new); + if (is_new) + { + msg(L_DEBUG, "Loading font %s (size %.6g, weight %s, style %s)", req->family, req->size, osm_val_decode(req->weight), osm_val_decode(req->style)); + font->family = mp_strdup(sym_mp, req->family); + font->size = req->size; + font->weight = req->weight; + font->style = req->style; + + PangoFontDescription *desc; + desc = pango_font_description_new(); + ASSERT(desc); + pango_font_description_set_family(desc, font->family); + if (font->weight != VALUE_NORMAL) + pango_font_description_set_weight(desc, PANGO_WEIGHT_BOLD); + if (font->style != VALUE_NORMAL) + pango_font_description_set_style(desc, PANGO_STYLE_ITALIC); + pango_font_description_set_size(desc, mm_to_pango(font->size)); + font->pango_font_desc = desc; + +#if 1 + // FIXME + PangoFont *pfont = pango_font_map_load_font(pango_font_map, pango_context, desc); + ASSERT(pfont); + PangoFontDescription *d2 = pango_font_describe(pfont); + ASSERT(d2); + msg(L_DEBUG, "Font desc: %s", pango_font_description_to_string(d2)); + + PangoFontMetrics *fm = pango_font_get_metrics(pfont, NULL); + ASSERT(fm); + msg(L_DEBUG, "Font metrics: asc=%.6g desc=%.6g", pango_to_mm(pango_font_metrics_get_ascent(fm)), pango_to_mm(pango_font_metrics_get_descent(fm))); +#endif + } + return font; +} + +static void font_init(void) +{ + text_font_init(); + + pango_font_map = pango_ft2_font_map_new(); + ASSERT(pango_font_map); + pango_ft2_font_map_set_resolution((PangoFT2FontMap *) pango_font_map, 72 * FONT_HACK_FACTOR, 72 * FONT_HACK_FACTOR); + pango_context = pango_font_map_create_context(PANGO_FONT_MAP(pango_font_map)); + ASSERT(pango_context); +} + +static void text_size(struct sym_text *st) +{ + PangoLayout *layout = pango_layout_new(pango_context); + pango_layout_set_font_description(layout, st->font->pango_font_desc); + pango_layout_set_text(layout, osm_val_decode(st->text), -1); + pango_layout_context_changed(layout); + + PangoRectangle ext; + pango_layout_get_extents(layout, NULL, &ext); + // st->tx = pango_to_mm(ext.x) / FONT_HACK_FACTOR; + // st->ty = pango_to_mm(ext.y) / FONT_HACK_FACTOR; + st->tw = pango_to_mm(ext.width) / FONT_HACK_FACTOR; + st->th = pango_to_mm(pango_layout_get_baseline(layout)) / FONT_HACK_FACTOR; + st->td = pango_to_mm(ext.height) / FONT_HACK_FACTOR - st->th; + + g_object_unref(layout); +} + +/*** Elimination of duplicate texts ***/ + +// FIXME: Get rid of globals +#define DUP_TILE_SIZE 10 +static int dup_tiles_w, dup_tiles_h; +static struct sym_text **dup_tiles; + +static void text_dup_init(void) +{ + dup_tiles_w = 1 + page_map_width / DUP_TILE_SIZE; + dup_tiles_h = 1 + page_map_height / DUP_TILE_SIZE; + dup_tiles = xmalloc_zero(sizeof(struct sym_text *) * dup_tiles_w * dup_tiles_h); + msg(L_DEBUG, "Allocated text tiles: %u x %u", dup_tiles_w, dup_tiles_h); +} + +static int text_dup_coord(int x, int y) +{ + ASSERT(x >= 0 && x < dup_tiles_w && y >= 0 && y < dup_tiles_h); + return x + y * dup_tiles_w; +} + +static double text_quad_dist(struct sym_text *a, struct sym_text *b) +{ + double dx = a->x - b->x; + double dy = a->y - b->y; + return dx*dx + dy*dy; +} + +static bool text_dup_detect(struct sym_text *t, struct style_info *si) +{ + // Out-of-frame texts are dropped immediately + double x = t->x - page_offset_x; + double y = t->y - page_offset_y; + if (x < 0 || x >= page_map_width || + y < 0 || y >= page_map_height) + return 0; + + int tile_x = x / DUP_TILE_SIZE; + int tile_y = y / DUP_TILE_SIZE; + + double radius = 0; + style_get_number(si, PROP_TEXT_DUP_THRESHOLD, &radius); + if (radius) + { + // A rather simple-minded algorithm, but believed to be efficient enough. + int r_tiles = 1 + radius / DUP_TILE_SIZE; + int x_start = MAX(0, tile_x - r_tiles); + int x_stop = MIN(dup_tiles_w - 1, tile_x + r_tiles); + int y_start = MAX(0, tile_y - r_tiles); + int y_stop = MIN(dup_tiles_h - 1, tile_y + r_tiles); + for (int x = x_start; x <= x_stop; x++) + for (int y = y_start; y <= y_stop; y++) + { + for (struct sym_text *d = dup_tiles[text_dup_coord(x, y)]; d; d = d->next_in_tile) + if (d->text == t->text && + text_quad_dist(d, t) <= radius*radius && + d->text_color == t->text_color && + d->font == t->font) + { + t->next_duplicate = d->next_duplicate; + d->next_duplicate = t; + return 0; + } + } + } + + int tile_i = text_dup_coord(tile_x, tile_y); + t->next_in_tile = dup_tiles[tile_i]; + dup_tiles[tile_i] = t; + return 1; +} + +/*** Core of the symbolizer ***/ + +static void prepare_text_element(struct sym_text *t, struct svg *svg) +{ + struct text_font *font = t->font; + svg_push_element(svg, "text"); + svg_set_attr_dimen(svg, "x", t->x); + svg_set_attr_dimen(svg, "y", t->y); + svg_set_attr(svg, "font-family", font->family); + svg_set_attr_dimen(svg, "font-size", font->size); + if (font->weight != VALUE_NORMAL) + svg_set_attr(svg, "font-weight", osm_val_decode(font->weight)); + if (font->style != VALUE_NORMAL) + svg_set_attr(svg, "font-style", osm_val_decode(font->style)); +} + +static void sym_text_draw(struct symbol *sym, struct svg *svg) +{ + struct sym_text *t = (struct sym_text *) sym; + + if (t->next_duplicate) + { + // If there is a cluster of duplicate texts, average their positions + double sx = 0, sy = 0; + uns nn = 0; + for (struct sym_text *u = t; u; u = u->next_duplicate) + { + sx += u->x; + sy += u->y; + nn++; + } + t->x = sx / nn; + t->y = sy / nn; + } + + if (t->opacity != 1) + { + svg_push_element(svg, "g"); + svg_set_attr_float(svg, "opacity", t->opacity); + } + + if (t->halo_radius) + { + prepare_text_element(t, svg); + svg_set_attr(svg, "fill", "none"); + svg_set_attr_color(svg, "stroke", t->halo_color); + svg_set_attr_dimen(svg, "stroke-width", t->halo_radius); + svg_set_attr(svg, "stroke-linecap", "round"); + svg_set_attr(svg, "stroke-linejoin", "round"); + if (t->halo_opacity != 1) + svg_set_attr_float(svg, "stroke-opacity", t->halo_opacity); + svg_push_chars(svg)->name = osm_val_decode(t->text); + svg_pop(svg); + svg_pop(svg); + } + + prepare_text_element(t, svg); + svg_set_attr_color(svg, "fill", t->text_color); + svg_set_attr(svg, "stroke", "none"); + svg_push_chars(svg)->name = osm_val_decode(t->text); + svg_pop(svg); + svg_pop(svg); + +#if 0 + // Draw bounding box for debugging + svg_push_element(svg, "rect"); + svg_set_attr(svg, "fill", "none"); + svg_set_attr_color(svg, "stroke", 0x0000ff); + svg_set_attr_dimen(svg, "stroke-width", 0.2); + svg_set_attr_dimen(svg, "x", t->x); + svg_set_attr_dimen(svg, "y", t->y - t->th); + svg_set_attr_dimen(svg, "width", t->tw); + svg_set_attr_dimen(svg, "height", t->th + t->td); + svg_pop(svg); + + svg_push_element(svg, "line"); + svg_set_attr(svg, "fill", "none"); + svg_set_attr_color(svg, "stroke", 0x0000ff); + svg_set_attr_dimen(svg, "stroke-width", 0.2); + svg_set_attr_dimen(svg, "x1", t->x); + svg_set_attr_dimen(svg, "y1", t->y); + svg_set_attr_dimen(svg, "x2", t->x + t->tw); + svg_set_attr_dimen(svg, "y2", t->y); + svg_pop(svg); +#endif + + if (t->opacity != 1) + svg_pop(svg); +} + +static osm_val_t get_text(struct osm_object *o, struct style_info *si) +{ + struct style_prop *prop = style_get_and_check(si, PROP_TEXT, (1 << PROP_TYPE_STRING) | (1 << PROP_TYPE_IDENT)); + if (!prop) + return 0; + + if (prop->type == PROP_TYPE_IDENT && prop->val.id == VALUE_AUTO) + { + static const osm_key_t auto_text_keys[] = { + KEY_NAME_CZ, // FIXME: This should be configurable + KEY_NAME, + KEY_REF, + KEY_OPERATOR, + KEY_BRAND, + KEY_ADDR_HOUSENUMBER, + }; + for (uns i=0; i < ARRAY_SIZE(auto_text_keys); i++) + { + osm_val_t val = osm_obj_find_tag(o, auto_text_keys[i]); + if (val) + return val; + } + return 0; + } + + return osm_obj_find_tag(o, osm_key_encode(osm_val_decode(prop->val.id))); +} + +static void get_text_attrs(struct sym_text *st, struct style_info *si) +{ + struct osm_object *o = st->s.o; + if (o->type == OSM_TYPE_WAY && osm_way_cyclic_p((struct osm_way *) o)) + st->text_color = 0xc0c0c0; // FIXME: This is an ugly hack, do we need it? + else + st->text_color = 0xffffff; + style_get_color(si, PROP_TEXT_COLOR, &st->text_color); + + struct text_font f = { + .family = "Helvetica", + .size = pt_to_mm(8), + }; + osm_val_t fam = style_get_string(si, PROP_FONT_FAMILY); + if (fam) + f.family = osm_val_decode(fam); + style_get_number(si, PROP_FONT_SIZE, &f.size); + f.weight = style_get_ident(si, PROP_FONT_WEIGHT); + if (!f.weight) + f.weight = VALUE_NORMAL; + if (f.weight != VALUE_NORMAL && f.weight != VALUE_BOLD) + { + osm_obj_warn(o, "Unknown font-weight %s", osm_val_decode(f.weight)); + f.weight = VALUE_NORMAL; + } + f.style = style_get_ident(si, PROP_FONT_STYLE); + if (!f.style) + f.style = VALUE_NORMAL; + if (f.style != VALUE_NORMAL && f.style != VALUE_ITALIC) + { + osm_obj_warn(o, "Unknown font-style %s", osm_val_decode(f.weight)); + f.style = VALUE_NORMAL; + } + st->font = font_get(&f); + + st->opacity = 1; + style_get_number(si, PROP_TEXT_OPACITY, &st->opacity); + + st->halo_color = st->text_color ^ 0xffffff; + style_get_color(si, PROP_TEXT_HALO_COLOR, &st->halo_color); + st->halo_radius = 0; + style_get_number(si, PROP_TEXT_HALO_RADIUS, &st->halo_radius); + st->halo_opacity = 1; + style_get_number(si, PROP_TEXT_HALO_OPACITY, &st->halo_opacity); + + double dx = 0, dy = 0; + style_get_number(si, PROP_TEXT_OFFSET_X, &dx); + style_get_number(si, PROP_TEXT_OFFSET_Y, &dy); + style_get_number(si, PROP_TEXT_OFFSET, &dy); + st->x += dx; + st->y -= dy; +} + +static void text_fix_placement(struct sym_text *st) +{ + // Fix texts which do not fit on the paper + st->x = MIN(st->x, page_offset_x + page_map_width - st->tw); + st->x = MAX(st->x, page_offset_x); + st->y = MIN(st->y, page_offset_y + page_map_height - st->th); + st->y = MAX(st->y, page_offset_y + st->td); +} + +static void sym_text_node(struct osm_object *o, struct style_info *si, osm_val_t text) +{ + struct osm_node *n = (struct osm_node *) o; + + struct sym_text *st = sym_text_new(o); + st->text = text; + st->x = n->x; + st->y = n->y; + + get_text_attrs(st, si); + text_size(st); + + osm_val_t ah = style_get_ident(si, PROP_TEXT_ANCHOR_HORIZONTAL); + switch (ah) + { + case VALUE_LEFT: + st->x -= st->tw; + break; + case VALUE_CENTER: + st->x -= st->tw / 2; + break; + case 0: + case VALUE_RIGHT: + break; + default: + osm_obj_warn(o, "Unknown text-anchor-horizontal: %s", osm_val_decode(ah)); + } + + osm_val_t av = style_get_ident(si, PROP_TEXT_ANCHOR_VERTICAL); + switch (av) + { + case VALUE_ABOVE: // FIXME: What's the difference between above and top? + case VALUE_TOP: + st->y -= st->td; + break; + case VALUE_CENTER: + st->y -= (st->th + st->td) / 2; + // Fall thru + case 0: + case VALUE_BOTTOM: + case VALUE_BELOW: + st->y += st->th; + break; + default: + osm_obj_warn(o, "Unknown text-anchor-vertical: %s", osm_val_decode(av)); + } + + text_fix_placement(st); + if (!text_dup_detect(st, si)) + { + msg(L_DEBUG, "Text <%s> dropped as duplicate", osm_val_decode(text)); + return; + } + + sym_plan(&st->s, sym_zindex(o, si, 5)); +} + +static void sym_text_center(struct osm_object *o, struct style_info *si, osm_val_t text, double x, double y) +{ + struct sym_text *st = sym_text_new(o); + st->text = text; + st->x = x; + st->y = y; + + get_text_attrs(st, si); + text_size(st); + st->x -= st->tw / 2; + st->y += st->th - (st->th + st->td) / 2; + text_fix_placement(st); + sym_plan(&st->s, sym_zindex(o, si, 4.9)); +} + +static void sym_text_way(struct osm_object *o, struct style_info *si, osm_val_t text) +{ + double x, y; + osm_val_t tp = style_get_ident(si, PROP_TEXT_POSITION); + + switch (tp) + { + case VALUE_CENTER: + if (osm_obj_center(o, &x, &y)) + sym_text_center(o, si, text, x, y); + break; + case VALUE_LINE: + // FIXME + default: + osm_obj_warn(o, "Unknown text-position: %s", osm_val_decode(tp)); + } +} + +static void sym_text_mpg(struct osm_object *o, struct style_info *si, osm_val_t text) +{ + double x, y; + osm_val_t tp = style_get_ident(si, PROP_TEXT_POSITION); + + switch (tp) + { + case VALUE_CENTER: + if (osm_obj_center(o, &x, &y)) + sym_text_center(o, si, text, x, y); + break; + case VALUE_LINE: + // FIXME + default: + osm_obj_warn(o, "Unknown text-position: %s", osm_val_decode(tp)); + } +} + +static void sym_text_gen(struct osm_object *o, struct style_info *si, struct svg *svg UNUSED) +{ + osm_val_t text = get_text(o, si); + if (!text) + return; + + switch (o->type) + { + case OSM_TYPE_NODE: + sym_text_node(o, si, text); + break; + case OSM_TYPE_WAY: + sym_text_way(o, si, text); + break; + case OSM_TYPE_MULTIPOLYGON: + sym_text_mpg(o, si, text); + break; + default: + osm_obj_warn(o, "Text symbolizer does not support this object type"); + return; + } +} + +static void sym_text_init(void) +{ + font_init(); + text_dup_init(); +} + +struct symbolizer symbolizer_text = { + .name = "text", + .draw = sym_text_draw, + .gen = sym_text_gen, + .init = sym_text_init, +}; + +struct sym_text *sym_text_new(struct osm_object *o) +{ + return sym_new(SYMBOLIZER_TEXT, o, sizeof(struct sym_text)); +} + +// FIXME: Hack +void scale_text(struct svg *svg, double x, double y, osm_val_t text) +{ + struct sym_text *st = sym_text_new(NULL); + + struct text_font f = { + .family = "Times", + .weight = VALUE_NORMAL, + .style = VALUE_NORMAL, + .size = pt_to_mm(10), + }; + + st->text = text; + st->text_color = 0; + st->x = x; + st->y = y; + st->font = font_get(&f); + st->opacity = 1; + st->halo_color = 0xffffff; + st->halo_radius = 0.8; + st->halo_opacity = 1; + text_size(st); + st->x -= st->tw / 2; + sym_text_draw(&st->s, svg); +} diff --git a/sym.c b/sym.c new file mode 100644 index 0000000..00d8941 --- /dev/null +++ b/sym.c @@ -0,0 +1,148 @@ +/* + * Hic Est Leo -- Symbols + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include +#include + +#include + +#include "leo.h" +#include "osm.h" +#include "style.h" +#include "sym.h" + +#undef CLAMP // FIXME: Fix in libucw? +#define CLAMP(x,min,max) ({ typeof(x) _t=x; (_t < min) ? min : (_t > max) ? max : _t; }) /** Clip a number @x to interval [@min,@max] **/ + +static struct symbolizer *symbolizers[] = { + [SYMBOLIZER_INVALID] = NULL, + [SYMBOLIZER_POINT] = &symbolizer_point, + [SYMBOLIZER_ICON] = &symbolizer_icon, + [SYMBOLIZER_LINE] = &symbolizer_line, + [SYMBOLIZER_AREA] = &symbolizer_area, + [SYMBOLIZER_TEXT] = &symbolizer_text, + [SYMBOLIZER_LINEIMG] = &symbolizer_lineimg, +}; + +struct mempool *sym_mp; + +struct sym_planned { + struct symbol *sym; + z_index_t zindex; +}; + +static struct sym_planned *sym_planned; + +static inline bool sym_before(struct sym_planned *x, struct sym_planned *y) +{ + COMPARE_LT(x->zindex, y->zindex); + COMPARE_LT(x->sym->o->type, y->sym->o->type); + COMPARE_LT(x->sym->o->id, y->sym->o->id); + return 0; +} + +#define ASORT_PREFIX(x) sym_##x +#define ASORT_KEY_TYPE struct sym_planned +#define ASORT_LT(_x,_y) sym_before(&(_x), &(_y)) +#include + +void sym_init(void) +{ + sym_mp = mp_new(65536); + GARY_INIT(sym_planned, 0); + + for (uns i = SYMBOLIZER_INVALID + 1; i < SYMBOLIZER_MAX; i++) + if (symbolizers[i]->init) + (*symbolizers[i]->init)(); +} + +void *sym_new(enum symbolizer_type type, struct osm_object *o, size_t size) +{ + struct symbol *s = mp_alloc_zero(sym_mp, size); + s->type = type; + s->o = o; + return s; +} + +void sym_plan(struct symbol *sym, z_index_t zindex) +{ + struct sym_planned *p = GARY_PUSH(sym_planned); + p->sym = sym; + p->zindex = zindex; + if (debug_dump_symbols) + printf("--> planning symbol <%s> with z-index %u for %s\n", + symbolizers[sym->type]->name, zindex, STK_OSM_NAME(sym->o)); +} + +z_index_t sym_zindex(struct osm_object *o, struct style_info *si, double default_mzi) +{ + double zi = 0; + style_get_number(si, PROP_Z_INDEX, &zi); + double zi2 = CLAMP(zi, -100, 100); + if (zi2 != zi) + osm_obj_warn(o, "z-index clipped from %.6g to %.6g", zi, zi2); + +#if 0 + // FIXME: object-z-index not used yet + double ozi = 0; + style_get_number(si, PROP_OBJECT_Z_INDEX, &ozi); + double ozi2 = CLAMP(ozi, -100, 100); + if (ozi2 != ozi) + osm_obj_warn(o, "object-z-index clipped from %.6g to %.6g", ozi, ozi2); +#endif + + double mzi = default_mzi; + if (mzi == 2) + { + // FIXME: This is a terrible hack. + style_get_number(si, PROP_CASING_MAJOR_Z_INDEX, &mzi); + } + else if (mzi == 4.9 || mzi == 5) + { + // FIXME: This is another terrible hack. + style_get_number(si, PROP_TEXT_MAJOR_Z_INDEX, &mzi); + } + else + style_get_number(si, PROP_MAJOR_Z_INDEX, &mzi); + double mzi2 = CLAMP(mzi, -100, 100); + if (mzi2 != mzi) + osm_obj_warn(o, "major-z-index clipped from %.6g to %.6g", mzi, mzi2); + + return (z_index_t)(10000 + (z_index_t)(zi2 * 100) + 10000*( 10000 + (z_index_t)(mzi2 * 100) )); +} + +void sym_from_style(struct osm_object *o, struct style_results *sr, struct svg *svg) +{ + for (uns i=0; i < sr->num_active_layers; i++) + { + for (uns j = SYMBOLIZER_INVALID + 1; j < SYMBOLIZER_MAX; j++) + if (symbolizers[j]->gen) + (symbolizers[j]->gen)(o, sr->layers[sr->active_layers[i]], svg); + } +} + +static void sym_draw(struct symbol *sym, z_index_t zindex, struct svg *svg) +{ + ASSERT(sym->type && sym->type < SYMBOLIZER_MAX); + if (debug_dump_symbols) + printf("Drawing symbol <%s> at z-index %u for %s\n", symbolizers[sym->type]->name, zindex, STK_OSM_NAME(sym->o)); + symbolizers[sym->type]->draw(sym, svg); +} + +void sym_draw_all(struct svg *svg) +{ + msg(L_INFO, "Sorting %u symbols by depth", (uns) GARY_SIZE(sym_planned)); + sym_sort(sym_planned, GARY_SIZE(sym_planned)); + + msg(L_INFO, "Dumping icon library"); + svg_icon_dump_library(svg); + + msg(L_INFO, "Drawing symbols"); + for (uns i = 0; i < GARY_SIZE(sym_planned); i++) + sym_draw(sym_planned[i].sym, sym_planned[i].zindex, svg); +} diff --git a/sym.h b/sym.h new file mode 100644 index 0000000..7c4e97b --- /dev/null +++ b/sym.h @@ -0,0 +1,150 @@ +/* + * Hic Est Leo -- Symbolizers + * + * (c) 2014 Martin Mares + */ + +#ifndef _BRUM_SYM_H +#define _BRUM_SYM_H + +#include "osm.h" +#include "style.h" +#include "svg.h" + +enum symbolizer_type { + SYMBOLIZER_INVALID, + SYMBOLIZER_POINT, + SYMBOLIZER_ICON, + SYMBOLIZER_LINE, + SYMBOLIZER_AREA, + SYMBOLIZER_TEXT, + SYMBOLIZER_LINEIMG, + SYMBOLIZER_MAX, +}; + +struct symbol { + enum symbolizer_type type; + struct osm_object *o; + // Symbolizer-dependent data follow +}; + +struct symbolizer { + const char *name; + void (*draw)(struct symbol *sym, struct svg *svg); + void (*gen)(struct osm_object *o, struct style_info *si, struct svg *svg); + void (*init)(void); +}; + +extern struct mempool *sym_mp; + +/* + * Default values for major-z-index: + * + * 1 area + * 2 casing + * 2.1 left/right casing + * 2.9 line pattern + * 3 line + * 4 point + * 4.9 line-text + * 5 point-text + */ +typedef u32 z_index_t; + +void sym_init(void); +void *sym_new(enum symbolizer_type type, struct osm_object *o, size_t size); +void sym_plan(struct symbol *sym, z_index_t zindex); +void sym_draw_all(struct svg *svg); +void sym_from_style(struct osm_object *o, struct style_results *sr, struct svg *svg); +z_index_t sym_zindex(struct osm_object *o, struct style_info *si, double default_mzi); + +/* sym-point.c handles point symbols and icons */ + +struct sym_point { + struct symbol s; + osm_val_t shape; + double size; + color_t stroke_color; + double stroke_width; + double stroke_opacity; + color_t fill_color; + double fill_opacity; + bool do_stroke; + bool do_fill; +}; + +// FIXME: Make sym_*_new() and symbolizer structs internal +extern struct symbolizer symbolizer_point; +struct sym_point *sym_point_new(struct osm_object *o); + +struct sym_icon { + struct symbol s; + struct svg_icon_request sir; +}; + +extern struct symbolizer symbolizer_icon; +struct sym_icon *sym_icon_new(struct osm_object *o); + +/* sym-line.c handles lines and areas */ + +struct sym_line { + struct symbol s; + double width; + color_t color; + double opacity; + osm_val_t line_cap; + osm_val_t line_join; + double miter_limit; + double *dash_pattern; // Growing array + double dash_offset; +}; + +extern struct symbolizer symbolizer_line; +struct sym_line *sym_line_new(struct osm_object *o); + +struct sym_area { + struct symbol s; + color_t fill_color; + double fill_opacity; + struct svg_pattern *fill_pattern; +}; + +extern struct symbolizer symbolizer_area; +struct sym_area *sym_area_new(struct osm_object *o); + +struct sym_lineimg { // Images along line + struct symbol s; + struct svg_icon_request sir; + osm_val_t align; + double offset; + double spacing; + double phase; +}; + +extern struct symbolizer symbolizer_lineimg; +struct sym_lineimg *sym_lineimg_new(struct osm_object *o); + +/* sym-text.c */ + +struct sym_text { + struct symbol s; + osm_val_t text; + color_t text_color; + double x; + double y; + struct text_font *font; + double opacity; + color_t halo_color; + double halo_radius; + double halo_opacity; + struct sym_text *next_in_tile; // See text_by_tile[] + struct sym_text *next_duplicate; + double tw, th, td; +}; + +extern struct symbolizer symbolizer_text; +struct sym_text *sym_text_new(struct osm_object *o); + +void scale_text(struct svg *svg, double x, double y, osm_val_t text); + +#endif diff --git a/tag-stats b/tag-stats new file mode 100755 index 0000000..c5374ce --- /dev/null +++ b/tag-stats @@ -0,0 +1,2 @@ +#!/bin/sh +./leo -SDebug.DumpSource=1 | sed 's@^[[:space:]]*\(.* = .*\)@\1@p;d' | sort -u diff --git a/test.css b/test.css new file mode 100644 index 0000000..ebf2243 --- /dev/null +++ b/test.css @@ -0,0 +1,70 @@ +// relation, area way[highway=path][magic?][!troll] { visibility: none; width: 1; } + +// *[water=river] { } + +node[highway=crossing] { +// symbol-shape: circle; +// symbol-stroke-width: 1; +// symbol-stroke-color: #ff0000; +// symbol-stroke-opacity: 0.5; +// symbol-fill-color: #00ff00; +// icon-image: "symbols/mj/view_point.svg"; +// icon-width: 2; +} + +way[highway] { + width: 0.6; + color: #fff; + linecap: round; + casing-width: 1; + casing-color: #888; + casing-linecap: round; + z-index: 1; +} + +way[highway=track], way[highway=footway] { + casing-width: 0; + width: 0.3; + color: #888; + z-index: 0; +} + +area { + fill-color: #cfc; + width: 0.2; + color: #080; +} + +area[natural=water] { + fill-color: #77f; + color: #00f; + width: 0.2; + z-index: 3; + text: name; + text-halo-radius: 1; + text-color: #000000; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-position: center; + font-family: Times; + font-size: 3; + font-style: italic; +} + +node[highway=bus_stop] { + text: name; + text-halo-radius: 1; + text-color: #000000; + text-halo-color: #ffffff; + text-halo-opacity: 0.8; + text-dup-threshold: 20; + text-anchor-horizontal: center; + text-anchor-vertical: center; + font-family: Times; + font-size: 3; + font-style: italic; +// icon-image: "symbols/mj/view_point.svg"; +// icon-width: 2; + symbol-shape: circle; + symbol-fill-color: #ff00ff; +} diff --git a/xml.c b/xml.c new file mode 100644 index 0000000..98f2f8b --- /dev/null +++ b/xml.c @@ -0,0 +1,229 @@ +/* + * Hic Est Leo -- OSM XML Parser + * + * (c) 2014 Martin Mares + */ + +#include +#include +#include + +#include +#include +#include + +#include "leo.h" +#include "osm.h" + +static void parse_tag(struct xml_context *ctx, struct osm_object *o, struct xml_node *t) +{ + char *tag_k = xml_attr_value(ctx, t, "k"); + char *tag_v = xml_attr_value(ctx, t, "v"); + if (!tag_k || !tag_v) + die("Object %ju has malformed tag", (uintmax_t) o->id); + osm_obj_add_tag(o, tag_k, tag_v); +} + +static void parse_node(struct xml_context *ctx, struct xml_node *e) +{ + char *attr_id = xml_attr_value(ctx, e, "id"); + if (!attr_id) + die("Node with no id"); + + char *attr_vis = xml_attr_value(ctx, e, "visible"); + if (attr_vis && strcmp(attr_vis, "true")) + { + msg(L_DEBUG, "Node %s invisible", attr_id); + return; + } + + char *attr_lat = xml_attr_value(ctx, e, "lat"); + char *attr_lon = xml_attr_value(ctx, e, "lon"); + if (!attr_lat || !attr_lon) + die("Node %s has mandatory attributes missing", attr_id); + + struct osm_node *n = osm_node_new(osm_parse_id(attr_id)); + n->x = atof(attr_lon); + n->y = atof(attr_lat); + + XML_NODE_FOR_EACH(t, e) + if (t->type == XML_NODE_ELEM && !strcmp(t->name, "tag")) + parse_tag(ctx, &n->o, t); +} + +static void parse_way(struct xml_context *ctx, struct xml_node *e) +{ + char *attr_id = xml_attr_value(ctx, e, "id"); + if (!attr_id) + die("Way with no id"); + + char *attr_vis = xml_attr_value(ctx, e, "visible"); + if (attr_vis && strcmp(attr_vis, "true")) + { + msg(L_DEBUG, "Way %s invisible", attr_id); + return; + } + + struct osm_way *w = osm_way_new(osm_parse_id(attr_id)); + + XML_NODE_FOR_EACH(t, e) + if (t->type == XML_NODE_ELEM) + { + if (!strcmp(t->name, "tag")) + parse_tag(ctx, &w->o, t); + else if (!strcmp(t->name, "nd")) + { + char *nd_ref = xml_attr_value(ctx, t, "ref"); + if (!nd_ref) + die("Way %s has malformed ref", attr_id); + struct osm_object *o = osm_obj_find_by_id(OSM_TYPE_NODE, osm_parse_id(nd_ref)); + if (!o) + die("Way %ju refers to unknown node %s", (uintmax_t) w->o.id, nd_ref); + osm_way_add_node(w, (struct osm_node *) o); + } + } +} + +static void parse_relation(struct xml_context *ctx, struct xml_node *e) +{ + char *attr_id = xml_attr_value(ctx, e, "id"); + if (!attr_id) + die("Relation with no id"); + + char *attr_vis = xml_attr_value(ctx, e, "visible"); + if (attr_vis && strcmp(attr_vis, "true")) + { + msg(L_DEBUG, "Relation %s invisible", attr_id); + return; + } + + struct osm_relation *r = osm_relation_new(osm_parse_id(attr_id)); + + XML_NODE_FOR_EACH(t, e) + if (t->type == XML_NODE_ELEM) + { + if (!strcmp(t->name, "tag")) + parse_tag(ctx, &r->o, t); + else if (!strcmp(t->name, "member")) + { + char *m_role = xml_attr_value(ctx, t, "role"); + char *m_ref = xml_attr_value(ctx, t, "ref"); + char *m_type = xml_attr_value(ctx, t, "type"); + if (!m_role || !m_ref || !m_type) + die("Relation %ju has malformed member", (uintmax_t) r->o.id); + osm_id_t ref = osm_parse_id(m_ref); + + enum osm_object_type type; + if (!strcmp(m_type, "node")) + type = OSM_TYPE_NODE; + else if (!strcmp(m_type, "way")) + type = OSM_TYPE_WAY; + else if (!strcmp(m_type, "relation")) + { + type = OSM_TYPE_RELATION; + // Since the order of objects is topological, we need not worry about cycles + // msg(L_DEBUG, "Relation inside relation (%ju inside %ju)", (uintmax_t) o->id, (uintmax_t) r->o.id); + } + else + { + msg(L_WARN, "Relation %ju refers to member %ju of unknown type %s", (uintmax_t) r->o.id, (uintmax_t) ref, m_type); + continue; + } + + struct osm_object *o = osm_obj_find_by_id(type, ref); + if (!o) + { + // This is a standard situation, so warn only when debugging + // msg(L_WARN, "Relation %ju refers to unknown member node %s", (uintmax_t) ref, m_ref); + continue; + } + osm_relation_add_member(r, o, m_role); + } + } +} + +static void h_error(struct xml_context *ctx) +{ + fprintf(stderr, "%s at %u: %s\n", (ctx->err_code < XML_ERR_ERROR) ? "warn" : "error", xml_row(ctx), ctx->err_msg); +} + +#if 0 + +static void h_stag(struct xml_context *ctx) +{ + printf("STAG %s\n", ctx->node->name); + if (!strcmp(ctx->node->name, "node")) + { + ctx->flags &= ~XML_REPORT_TAGS; + ctx->flags |= XML_ALLOC_ALL; + } +} + +static void h_etag(struct xml_context *ctx) +{ + printf("ETAG %s\n", ctx->node->name); +} + +#endif + +void osm_xml_parse(const char *name) +{ + msg(L_INFO, "Loading %s", name); + + struct xml_context ctx; + xml_init(&ctx); + ctx.h_warn = ctx.h_error = ctx.h_fatal = h_error; + xml_push_fastbuf(&ctx, bopen_file(name, O_RDONLY, NULL)); + +#if 0 + ctx.flags |= XML_REPORT_TAGS; + ctx.h_stag = h_stag; + ctx.h_etag = h_etag; + xml_parse(&ctx); +#endif + +#if 0 + uns state; + while (state = xml_next(&ctx)) + switch (state) + { + case XML_STATE_STAG: + printf("STAG %s\n", ctx.node->name); + if (!strcmp(ctx.node->name, "node")) + { + ctx.pull = XML_PULL_ETAG; + ctx.flags |= XML_ALLOC_CHARS | XML_ALLOC_TAGS; + } + break; + case XML_STATE_ETAG: + printf("ETAG %s\n", ctx.node->name); + if (!strcmp(ctx.node->name, "node")) + { + ctx.pull = XML_PULL_STAG | XML_PULL_ETAG; + ctx.flags &= ~(XML_ALLOC_CHARS | XML_ALLOC_TAGS); + } + break; + } +#endif + + ctx.flags |= XML_ALLOC_ALL; + xml_parse(&ctx); + + if (ctx.err_code) + die("Fatal error in XML parser"); + + struct xml_node *root = ctx.dom; + ASSERT(root); + XML_NODE_FOR_EACH(e, root) + if (e->type == XML_NODE_ELEM) + { + if (!strcmp(e->name, "node")) + parse_node(&ctx, e); + else if (!strcmp(e->name, "way")) + parse_way(&ctx, e); + else if (!strcmp(e->name, "relation")) + parse_relation(&ctx, e); + } + + xml_cleanup(&ctx); +}