From 44b0ec8816c3cf1a5858fec0c51e075cedefc3eb Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Sun, 15 Jun 2014 20:34:29 +0200 Subject: [PATCH] Imported experimental version from poskole2014.git --- Makefile | 63 ++++ TODO | 20 ++ css-lex.c | 251 +++++++++++++++ css-parse.y | 304 +++++++++++++++++++ css.c | 178 +++++++++++ css.h | 101 +++++++ cut | 2 + dict-keys.t | 12 + dict-props.t | 77 +++++ dict-values.t | 30 ++ dict.c | 63 ++++ dict.h | 33 ++ gen-dict | 16 + icons/bus_stop.svg | 176 +++++++++++ icons/bus_stop2.svg | 35 +++ icons/cave.svg | 130 ++++++++ icons/cave2.svg | 121 ++++++++ icons/cemetery.svg | 93 ++++++ icons/church.svg | 141 +++++++++ icons/cliff.svg | 94 ++++++ icons/cross.svg | 109 +++++++ icons/deciduous.svg | 318 +++++++++++++++++++ icons/fuel.svg | 195 ++++++++++++ icons/gate.svg | 231 ++++++++++++++ icons/guidepost.svg | 145 +++++++++ icons/logo.svg | 287 ++++++++++++++++++ icons/memorial.svg | 95 ++++++ icons/meritko.svg | 196 ++++++++++++ icons/power_pole.svg | 79 +++++ icons/train.svg | 131 ++++++++ icons/view_point.svg | 155 ++++++++++ icons/wetland.svg | 80 +++++ leo.c | 253 ++++++++++++++++ leo.h | 20 ++ map.c | 135 +++++++++ map.cf | 56 ++++ map.h | 30 ++ osm.c | 581 +++++++++++++++++++++++++++++++++++ osm.h | 164 ++++++++++ poskole.css | 707 +++++++++++++++++++++++++++++++++++++++++++ shp.c | 113 +++++++ shp.h | 12 + style.c | 271 +++++++++++++++++ style.h | 108 +++++++ svg-icon.c | 237 +++++++++++++++ svg.c | 365 ++++++++++++++++++++++ svg.h | 112 +++++++ sym-line.c | 378 +++++++++++++++++++++++ sym-point.c | 153 ++++++++++ sym-text.c | 562 ++++++++++++++++++++++++++++++++++ sym.c | 148 +++++++++ sym.h | 150 +++++++++ tag-stats | 2 + test.css | 70 +++++ xml.c | 229 ++++++++++++++ 55 files changed, 8817 insertions(+) create mode 100644 Makefile create mode 100644 TODO create mode 100644 css-lex.c create mode 100644 css-parse.y create mode 100644 css.c create mode 100644 css.h create mode 100644 cut create mode 100644 dict-keys.t create mode 100644 dict-props.t create mode 100644 dict-values.t create mode 100644 dict.c create mode 100644 dict.h create mode 100755 gen-dict create mode 100644 icons/bus_stop.svg create mode 100644 icons/bus_stop2.svg create mode 100644 icons/cave.svg create mode 100644 icons/cave2.svg create mode 100644 icons/cemetery.svg create mode 100644 icons/church.svg create mode 100644 icons/cliff.svg create mode 100644 icons/cross.svg create mode 100644 icons/deciduous.svg create mode 100644 icons/fuel.svg create mode 100644 icons/gate.svg create mode 100644 icons/guidepost.svg create mode 100644 icons/logo.svg create mode 100644 icons/memorial.svg create mode 100644 icons/meritko.svg create mode 100644 icons/power_pole.svg create mode 100644 icons/train.svg create mode 100644 icons/view_point.svg create mode 100644 icons/wetland.svg create mode 100644 leo.c create mode 100644 leo.h create mode 100644 map.c create mode 100644 map.cf create mode 100644 map.h create mode 100644 osm.c create mode 100644 osm.h create mode 100644 poskole.css create mode 100644 shp.c create mode 100644 shp.h create mode 100644 style.c create mode 100644 style.h create mode 100644 svg-icon.c create mode 100644 svg.c create mode 100644 svg.h create mode 100644 sym-line.c create mode 100644 sym-point.c create mode 100644 sym-text.c create mode 100644 sym.c create mode 100644 sym.h create mode 100755 tag-stats create mode 100644 test.css create mode 100644 xml.c 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); +} -- 2.39.2