]> mj.ucw.cz Git - leo.git/commitdiff
Imported experimental version from poskole2014.git
authorMartin Mares <mj@ucw.cz>
Sun, 15 Jun 2014 18:34:29 +0000 (20:34 +0200)
committerMartin Mares <mj@ucw.cz>
Sun, 15 Jun 2014 18:34:29 +0000 (20:34 +0200)
55 files changed:
Makefile [new file with mode: 0644]
TODO [new file with mode: 0644]
css-lex.c [new file with mode: 0644]
css-parse.y [new file with mode: 0644]
css.c [new file with mode: 0644]
css.h [new file with mode: 0644]
cut [new file with mode: 0644]
dict-keys.t [new file with mode: 0644]
dict-props.t [new file with mode: 0644]
dict-values.t [new file with mode: 0644]
dict.c [new file with mode: 0644]
dict.h [new file with mode: 0644]
gen-dict [new file with mode: 0755]
icons/bus_stop.svg [new file with mode: 0644]
icons/bus_stop2.svg [new file with mode: 0644]
icons/cave.svg [new file with mode: 0644]
icons/cave2.svg [new file with mode: 0644]
icons/cemetery.svg [new file with mode: 0644]
icons/church.svg [new file with mode: 0644]
icons/cliff.svg [new file with mode: 0644]
icons/cross.svg [new file with mode: 0644]
icons/deciduous.svg [new file with mode: 0644]
icons/fuel.svg [new file with mode: 0644]
icons/gate.svg [new file with mode: 0644]
icons/guidepost.svg [new file with mode: 0644]
icons/logo.svg [new file with mode: 0644]
icons/memorial.svg [new file with mode: 0644]
icons/meritko.svg [new file with mode: 0644]
icons/power_pole.svg [new file with mode: 0644]
icons/train.svg [new file with mode: 0644]
icons/view_point.svg [new file with mode: 0644]
icons/wetland.svg [new file with mode: 0644]
leo.c [new file with mode: 0644]
leo.h [new file with mode: 0644]
map.c [new file with mode: 0644]
map.cf [new file with mode: 0644]
map.h [new file with mode: 0644]
osm.c [new file with mode: 0644]
osm.h [new file with mode: 0644]
poskole.css [new file with mode: 0644]
shp.c [new file with mode: 0644]
shp.h [new file with mode: 0644]
style.c [new file with mode: 0644]
style.h [new file with mode: 0644]
svg-icon.c [new file with mode: 0644]
svg.c [new file with mode: 0644]
svg.h [new file with mode: 0644]
sym-line.c [new file with mode: 0644]
sym-point.c [new file with mode: 0644]
sym-text.c [new file with mode: 0644]
sym.c [new file with mode: 0644]
sym.h [new file with mode: 0644]
tag-stats [new file with mode: 0755]
test.css [new file with mode: 0644]
xml.c [new file with mode: 0644]

diff --git a/Makefile b/Makefile
new file mode 100644 (file)
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 (file)
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 (file)
index 0000000..754c526
--- /dev/null
+++ b/css-lex.c
@@ -0,0 +1,251 @@
+/*
+ *     Experimenta lMai Renderer -- MapCSS Lexer
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/chartype.h>
+#include <ucw/fastbuf.h>
+#include <ucw/mempool.h>
+
+#include <fcntl.h>
+
+#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<n; i++)
+    {
+      uns j = Cxvalue(rgb[i]);
+      if (n == 6)
+       color = (color << 4) | j;
+      else
+       color = (color << 8) | (j * 17);
+    }
+  return color;
+}
diff --git a/css-parse.y b/css-parse.y
new file mode 100644 (file)
index 0000000..d5a7757
--- /dev/null
@@ -0,0 +1,304 @@
+/*
+ *     Hic Est Leo -- MapCSS Parser
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+%{
+#include <ucw/lib.h>
+#include <ucw/mempool.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+#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 <s> NUMBER IDENT QUOTED RGB
+
+%type <s> ident_or_quoted
+%type <rule> rule rule_start rule_selectors rule_start_actions rule_actions
+%type <selector> selector selector_start
+%type <path> path path_start path_conditions
+%type <condition> condition
+%type <action> action
+%type <prop> prop_value prop_value_single prop_value_list
+%type <cond_op> 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 (file)
index 0000000..bfff47d
--- /dev/null
+++ b/css.c
@@ -0,0 +1,178 @@
+/*
+ *     Hic Est Leo -- MapCSS Stylesheets
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/mempool.h>
+
+#include <stdio.h>
+
+#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 (file)
index 0000000..d35c0b3
--- /dev/null
+++ b/css.h
@@ -0,0 +1,101 @@
+/*
+ *     Hic Est Leo -- MapCSS Stylesheets
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (file)
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 (file)
index 0000000..8cef811
--- /dev/null
@@ -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 (file)
index 0000000..a164805
--- /dev/null
@@ -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 (file)
index 0000000..1d30763
--- /dev/null
@@ -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 (file)
index 0000000..a3679e7
--- /dev/null
+++ b/dict.c
@@ -0,0 +1,63 @@
+/*
+ *     Hic Est Leo -- Universal Dictionaries
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/mempool.h>
+
+#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 <ucw/hashtable.h>
+
+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 (file)
index 0000000..57b94b1
--- /dev/null
+++ b/dict.h
@@ -0,0 +1,33 @@
+/*
+ *     Hic Est Leo -- Universal Dictionaries
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#ifndef _BRUM_DICT_H
+#define _BRUM_DICT_H
+
+#include <ucw/gary.h>
+
+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 (executable)
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 (<STDIN>) {
+       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 (file)
index 0000000..2694b46
--- /dev/null
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="11.843085"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="bus_stop.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="6.7830434">
+  <g
+     id="wrapper-8"
+     transform="matrix(0.02,0,0,0.02,-2.4945105,0.12651049)">
+    <rect
+       height="323.03857"
+       id="rect3653-4"
+       ry="20.087805"
+       style="fill:#ffffff;fill-opacity:1;stroke:#ffffff;stroke-width:43.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       width="295.40216"
+       x="146.60052"
+       y="15.549476" />
+    <path
+       d="m 294.60317,339.19967 0,224.75409"
+       id="path4184-3"
+       style="fill:none;stroke:#ffffff;stroke-width:43.75;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 383.58469,551.291 -181.03183,0"
+       id="path4186-1"
+       style="fill:none;stroke:#ffffff;stroke-width:43.75;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+  </g>
+  <g
+     id="wrapper"
+     transform="matrix(0.02,0,0,0.02,-2.4873596,0.11766564)">
+    <path
+       d="m 295.63339,53.000277 c 0.32324,0 0.65906,0.01236 0.98567,0.01493 0.32889,-0.0026 0.66023,-0.01493 0.98569,-0.01493 l -1.97136,0 z m 0.98567,0.01493 c -22.95139,0.181844 -54.04733,6.644254 -67.02642,12.052208 -13.16508,5.48545 -21.94443,10.973538 -24.68688,24.686875 l -9.87176,76.03199 0,104.75121 17.0105,0 0,16.42804 c 0,20.03034 29.30166,20.03033 29.30166,0 l 0,-16.42804 54.12295,0 0.16428,0 56.25859,0 0,16.42804 c 0,20.03034 29.31659,20.03033 29.31659,0 l 0,-16.42804 17.0105,0 0,-104.75121 -9.87176,-76.03199 C 385.60486,76.040957 376.82552,70.552869 363.66043,65.067419 350.68008,59.658941 319.57112,53.195848 296.61906,53.015211 z m -41.78697,19.190945 40.8013,0 42.78759,0 c 8.22846,0 8.22846,12.335971 0,12.335971 l -42.8772,0 -40.71169,0 c -8.22846,0 -8.22846,-12.335971 0,-12.335971 z M 236.6393,97.0125 l 58.90448,0 59.05506,0 c 8.69202,0 10.96704,4.41206 12.02234,11.11134 l 7.69131,55.1385 c 0.71652,5.28515 -0.8225,10.52888 -8.12441,10.52888 l -70.55469,0 -70.58334,0 c -7.30191,0 -8.82599,-5.24374 -8.10948,-10.52888 l 7.69132,-55.1385 c 1.05531,-6.69928 3.31538,-11.11134 12.00741,-11.11134 z m -9.92904,110.562 c 7.7649,-1e-5 16.05345,8.30348 16.05345,16.06838 0,7.76491 -8.28855,16.05345 -16.05345,16.05345 -7.7649,1e-5 -16.06838,-8.28855 -16.06838,-16.05345 0,-7.7649 8.30348,-16.06838 16.06838,-16.06838 z m 139.83254,0 c 7.76491,0 16.05345,8.30348 16.05346,16.06838 0,7.7649 -8.28855,16.05346 -16.05346,16.05345 -7.76489,0 -16.06838,-8.28854 -16.06837,-16.05345 0,-7.7649 8.30348,-16.06839 16.06837,-16.06838 z"
+       id="path2115"
+       sodipodi:nodetypes="cscccsccccccccccccccccsccccccccccccccccccccsssccsssc"
+       style="fill:#000000;stroke:none;fill-opacity:1" />
+    <path
+       d="m 294.60317,339.19967 0,224.75409"
+       id="path4184"
+       style="fill:none;stroke:#0000e0;stroke-width:28.79948235000000167;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 383.58469,551.291 -181.03183,0"
+       id="path4186"
+       style="fill:none;stroke:#0000e0;stroke-width:30;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <rect
+       height="323.03857"
+       id="rect3653"
+       ry="20.087805"
+       style="fill:none;stroke:#0000d0;stroke-width:15;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       width="295.40216"
+       x="146.60052"
+       y="15.549476" />
+  </g>
+  <metadata
+     id="metadata1976">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+        <dc:language>
+en</dc:language>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="svg2"
+     inkscape:cx="0.80214163"
+     inkscape:cy="7.4724256"
+     inkscape:pageopacity="0.69411765"
+     inkscape:pageshadow="2"
+     inkscape:window-height="748"
+     inkscape:window-width="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:zoom="29.493668"
+     objecttolerance="10.0"
+     pagecolor="#2f51ff"
+     showgrid="false"
+     inkscape:snap-bbox="false"
+     inkscape:snap-bbox-midpoints="false"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:snap-grids="false"
+     inkscape:snap-global="false"
+     inkscape:window-maximized="1">
+    <sodipodi:guide
+       orientation="1,0"
+       position="2.8829365,10.53509"
+       id="guide3965" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="7.1550399,13.179726"
+       id="guide3967" />
+    <inkscape:grid
+       type="xygrid"
+       id="grid4491"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       id="perspective3653"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="ArrowStart"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 z"
+         id="path2111" />
+    </marker>
+    <marker
+       id="ArrowEnd"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 z"
+         id="path2108" />
+    </marker>
+    <inkscape:perspective
+       id="perspective2512"
+       inkscape:persp3d-origin="177.51199 : 145.02 : 1"
+       inkscape:vp_x="0 : 217.53 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="355.02399 : 217.53 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3939"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
diff --git a/icons/bus_stop2.svg b/icons/bus_stop2.svg
new file mode 100644 (file)
index 0000000..629a141
--- /dev/null
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg height="11.339" id="svg2" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:version="0.46" sodipodi:docbase="s:\Data\FacilityIcons" sodipodi:docname="highway_bus_stop.svg" sodipodi:version="0.32" version="1.0" width="11.339" xmlns="http://www.w3.org/2000/svg" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:svg="http://www.w3.org/2000/svg">
+       <metadata id="metadata1976">
+               <rdf:RDF >
+                       <cc:Work rdf:about="">
+                               <dc:format >
+image/svg+xml</dc:format>
+                               <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+                               <cc:license rdf:resource="http://web.resource.org/cc/PublicDomain" />
+                               <dc:language >
+en</dc:language>
+                       </cc:Work>
+               </rdf:RDF>
+       </metadata>
+       <sodipodi:namedview bordercolor="#666666" borderopacity="1.0" gridtolerance="10.0" guidetolerance="10.0" id="base" inkscape:current-layer="svg2" inkscape:cx="68.469786" inkscape:cy="186.81904" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:window-height="1003" inkscape:window-width="758" inkscape:window-x="12" inkscape:window-y="13" inkscape:zoom="0.65172414" objecttolerance="10.0" pagecolor="#ffffff" showgrid="false" />
+       <defs id="defs4">
+               <inkscape:perspective id="perspective3653" inkscape:persp3d-origin="290 : 193.33333 : 1" inkscape:vp_x="0 : 290 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="580 : 290 : 1" sodipodi:type="inkscape:persp3d" />
+               <marker id="ArrowStart" markerHeight="3" markerUnits="strokeWidth" markerWidth="4" orient="auto" refX="10" refY="5" viewBox="0 0 10 10">
+                       <path d="M 10 0 L 0 5 L 10 10 z" id="path2111" />
+               </marker>
+               <marker id="ArrowEnd" markerHeight="3" markerUnits="strokeWidth" markerWidth="4" orient="auto" refX="0" refY="5" viewBox="0 0 10 10">
+                       <path d="M 0 0 L 10 5 L 0 10 z" id="path2108" />
+               </marker>
+               <inkscape:perspective id="perspective2512" inkscape:persp3d-origin="177.51199 : 145.02 : 1" inkscape:vp_x="0 : 217.53 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_z="355.02399 : 217.53 : 1" sodipodi:type="inkscape:persp3d" />
+       </defs>
+       <g id="wrapper" transform="scale(0.020)">
+               <path d="M 290.625 34.5625 C 291.30137 34.5625 292.00406 34.588371 292.6875 34.59375 C 293.37567 34.588298 294.069 34.5625 294.75 34.5625 L 290.625 34.5625 z M 292.6875 34.59375 C 244.66265 34.974252 179.59571 48.496579 152.4375 59.8125 C 124.89013 71.290575 106.51969 82.774174 100.78125 111.46875 L 80.125 270.5625 L 80.125 489.75 L 115.71875 489.75 L 115.71875 524.125 C 115.71875 566.03764 177.03125 566.03762 177.03125 524.125 L 177.03125 489.75 L 290.28125 489.75 L 290.625 489.75 L 408.34375 489.75 L 408.34375 524.125 C 408.34375 566.03764 469.6875 566.03762 469.6875 524.125 L 469.6875 489.75 L 505.28125 489.75 L 505.28125 270.5625 L 484.625 111.46875 C 478.88656 82.774174 460.51613 71.290573 432.96875 59.8125 C 405.80789 48.495478 340.71371 34.971722 292.6875 34.59375 z M 205.25 74.75 L 290.625 74.75 L 380.15625 74.75 C 397.37395 74.750004 397.37396 100.5625 380.15625 100.5625 L 290.4375 100.5625 L 205.25 100.5625 C 188.0323 100.5625 188.03229 74.75 205.25 74.75 z M 158.8125 126.65625 L 290.4375 126.65625 L 426.5625 126.65625 C 444.75018 126.65625 449.51057 135.88827 451.71875 149.90625 L 467.8125 265.28125 C 469.31177 276.3402 466.09144 287.31249 450.8125 287.3125 L 290.625 287.3125 L 134.5625 287.3125 C 119.28355 287.3125 116.09448 276.34019 117.59375 265.28125 L 133.6875 149.90625 C 135.8957 135.88827 140.62481 126.65625 158.8125 126.65625 z M 146.40625 362.1875 C 162.65398 362.18749 175.8125 375.37727 175.8125 391.625 C 175.8125 407.87274 162.65398 421.03125 146.40625 421.03125 C 130.15852 421.03126 116.96875 407.87273 116.96875 391.625 C 116.96875 375.37728 130.15852 362.1875 146.40625 362.1875 z M 439 362.1875 C 455.24773 362.1875 468.40624 375.37728 468.40625 391.625 C 468.40625 407.87273 455.24773 421.03126 439 421.03125 C 422.75227 421.03125 409.56249 407.87274 409.5625 391.625 C 409.5625 375.37727 422.75228 362.18749 439 362.1875 z " id="path2115" style="fill: #ff0000; stroke: none" />
+       </g>
+       <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
\ No newline at end of file
diff --git a/icons/cave.svg b/icons/cave.svg
new file mode 100644 (file)
index 0000000..ca69d24
--- /dev/null
@@ -0,0 +1,130 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="11.339"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="cave.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="11.339">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+        <dc:language>
+en</dc:language>
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="wrapper"
+     inkscape:cx="7.3380697"
+     inkscape:cy="0.6565035"
+     inkscape:guide-bbox="true"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:window-height="1004"
+     inkscape:window-maximized="1"
+     inkscape:window-width="1272"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:zoom="11.313708"
+     objecttolerance="10.0"
+     pagecolor="#ffffff"
+     showgrid="false"
+     showguides="true" />
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <style
+       id="style6"
+       type="text/css">
+    .fil0 {fill:#EF7900}</style>
+    <inkscape:perspective
+       id="perspective6807"
+       inkscape:persp3d-origin="29.116032 : 19.410688 : 1"
+       inkscape:vp_x="0 : 29.116032 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="58.232063 : 29.116032 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6954"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2541"
+       inkscape:persp3d-origin="240 : 160 : 1"
+       inkscape:vp_x="0 : 240 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="480 : 240 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2750"
+       inkscape:persp3d-origin="215 : 143.33333 : 1"
+       inkscape:vp_x="0 : 215 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="430 : 215 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2830"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <g
+     id="wrapper"
+     transform="scale(0.020)">
+    <path
+       d="m 34.702986,408.13437 c 2.367196,-5.48093 32.404879,-47.87543 66.750284,-94.2099 49.25528,-66.44891 74.61409,-89.47895 120.04961,-109.0248 71.12554,-30.59742 142.32874,-32.35525 168.26002,-4.15402 61.33558,66.70455 124.63054,142.87846 142.85807,171.92632 25.68601,40.93369 12.60173,60.07407 -14.50984,21.22601 C 507.42352,378.58371 477.4635,342.01325 451.53328,312.63007 340.43961,186.74287 340.10029,186.61322 236.90791,230.57389 186.18922,252.18048 166.47754,270.06043 123.61005,333.34337 95.06701,375.47997 62.417798,411.78794 51.05624,414.0276 39.69479,416.26738 32.3358,413.61508 34.702986,408.13437 z"
+       id="path2838"
+       sodipodi:nodetypes="csssssssssc"
+       style="fill-opacity:1;fill:#000000" />
+    <path
+       d="m 34.702986,408.13437 c 2.367196,-5.48093 32.404879,-47.87543 66.750284,-94.2099 49.25528,-66.44891 74.61409,-89.47895 120.04961,-109.0248 71.12554,-30.59742 142.32874,-32.35525 168.26002,-4.15402 61.33558,66.70455 124.63054,142.87846 142.85807,171.92632 34.40213,55.71502 -22.68994,62.23235 -174.08769,65.9581 -80.61454,2.31281 -85.24178,0.87361 -168.92323,1.7133 -39.07364,-1.12238 -156.361399,-19.74053 -154.907064,-32.209 z"
+       id="path3593"
+       sodipodi:nodetypes="cssscccc"
+       style="fill-opacity:1;opacity:0.10000000000000001;fill:#000000;stroke:none;stroke-opacity:1" />
+    <path
+       d="m 150.97846,415.18101 c 0,-32.36715 90.19166,-134.76949 118.69935,-134.76949 45.60596,0 92.84717,28.28822 127.25562,76.20114 20.30607,28.27572 36.92016,54.52123 36.92016,58.32343 0,3.80207 -63.64691,6.91299 -141.43757,6.91299 -77.79065,0 -141.43756,-3.00059 -141.43756,-6.66807 z"
+       id="path3638"
+       sodipodi:nodetypes="cssssc"
+       style="fill-opacity:1;fill:#000000" />
+  </g>
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
diff --git a/icons/cave2.svg b/icons/cave2.svg
new file mode 100644 (file)
index 0000000..27f01f9
--- /dev/null
@@ -0,0 +1,121 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="81"
+   height="31.28476"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="jeskyne.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="5.6"
+     inkscape:cx="36.228065"
+     inkscape:cy="-0.28235733"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:snap-bbox="false"
+     inkscape:object-paths="true"
+     inkscape:window-width="1016"
+     inkscape:window-height="748"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-264.5,-506.86215)">
+    <g
+       id="g3609"
+       style="stroke:#ffffff;stroke-width:11;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+      <path
+         sodipodi:type="arc"
+         style="fill:none;stroke:#ffffff;stroke-width:11;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         id="path3611"
+         sodipodi:cx="305"
+         sodipodi:cy="532.36218"
+         sodipodi:rx="20"
+         sodipodi:ry="20"
+         d="m 285.00173,532.62508 c -0.1452,-11.04474 8.69063,-20.11598 19.73537,-20.26117 11.04475,-0.14519 20.11598,8.69064 20.26117,19.73538 0.002,0.18253 0.002,0.36509 -3e-4,0.54762"
+         sodipodi:start="3.1284475"
+         sodipodi:end="6.297422"
+         sodipodi:open="true" />
+      <path
+         style="fill:none;stroke:#ffffff;stroke-width:11;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         d="m 270,532.36218 15,0"
+         id="path3613" />
+      <path
+         style="fill:none;stroke:#ffffff;stroke-width:11;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         d="m 325,532.36218 15,0"
+         id="path3615" />
+    </g>
+    <g
+       id="g3588">
+      <path
+         sodipodi:open="true"
+         sodipodi:end="6.297422"
+         sodipodi:start="3.1284475"
+         d="m 285.00173,532.62508 c -0.1452,-11.04474 8.69063,-20.11598 19.73537,-20.26117 11.04475,-0.14519 20.11598,8.69064 20.26117,19.73538 0.002,0.18253 0.002,0.36509 -3e-4,0.54762"
+         sodipodi:ry="20"
+         sodipodi:rx="20"
+         sodipodi:cy="532.36218"
+         sodipodi:cx="305"
+         id="path2820"
+         style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:butt;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         sodipodi:type="arc" />
+      <path
+         id="path3594"
+         d="m 270,532.36218 15,0"
+         style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path3596"
+         d="m 325,532.36218 15,0"
+         style="fill:none;stroke:#000000;stroke-width:8;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    </g>
+  </g>
+</svg>
diff --git a/icons/cemetery.svg b/icons/cemetery.svg
new file mode 100644 (file)
index 0000000..576935b
--- /dev/null
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="100"
+   height="120"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="cemetery.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="2.8"
+     inkscape:cx="91.769571"
+     inkscape:cy="64.14082"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1016"
+     inkscape:window-height="748"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-220,-446.36218)">
+    <g
+       id="g3594"
+       transform="translate(0,-6)">
+      <path
+         id="path2818"
+         d="m 230,482.36218 40,0"
+         style="fill:none;stroke:#936506;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2820"
+         d="m 250,462.36218 0,50"
+         style="fill:none;stroke:#936506;stroke-width:8;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    </g>
+    <use
+       x="0"
+       y="0"
+       xlink:href="#g3594"
+       id="use3598"
+       transform="translate(40,60)"
+       width="744.09448"
+       height="1052.3622" />
+  </g>
+</svg>
diff --git a/icons/church.svg b/icons/church.svg
new file mode 100644 (file)
index 0000000..0680a16
--- /dev/null
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="7.021503"
+   height="12.639885"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="church.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective3607"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#4500ff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.55294118"
+     inkscape:pageshadow="2"
+     inkscape:zoom="16"
+     inkscape:cx="5.9464163"
+     inkscape:cy="-6.0866638"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:snap-grids="true"
+     inkscape:window-width="1272"
+     inkscape:window-height="1004"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     borderlayer="false"
+     showborder="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       units="mm"
+       spacingx="0.3333mm"
+       spacingy="0.3333mm"
+       dotted="false" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-2.0859509,-116.21568)">
+    <g
+       id="g3594-5"
+       style="stroke:#ffffff;stroke-width:1.77165353;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       transform="matrix(0.79214264,0,0,0.79214264,0.57943399,24.302138)">
+      <path
+         transform="translate(-53.899592,-3.6683608)"
+         d="m 63.779528,131.22736 c 0,1.95691 -1.586392,3.5433 -3.543307,3.5433 -1.956914,0 -3.543307,-1.58639 -3.543307,-3.5433 0,-1.95692 1.586393,-3.54331 3.543307,-3.54331 1.956915,0 3.543307,1.58639 3.543307,3.54331 z"
+         sodipodi:ry="3.5433071"
+         sodipodi:rx="3.5433071"
+         sodipodi:cy="131.22736"
+         sodipodi:cx="60.236221"
+         id="path2858-5"
+         style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:#ffffff;stroke-width:1.77165353;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         sodipodi:type="arc" />
+      <path
+         transform="translate(-53.899592,-3.6683608)"
+         sodipodi:nodetypes="cc"
+         id="path3632-0"
+         d="m 60.230197,128.85263 0,-8.26689"
+         style="fill:none;stroke:#ffffff;stroke-width:1.77165353;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         transform="translate(-53.899592,-3.6683608)"
+         sodipodi:nodetypes="cc"
+         id="path3634-5"
+         d="m 56.687244,124.12869 7.085906,0"
+         style="fill:none;stroke:#ffffff;stroke-width:1.77165353;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    </g>
+    <g
+       id="g3594"
+       transform="matrix(0.79214264,0,0,0.79214264,0.57943399,24.302138)">
+      <path
+         transform="translate(-53.899592,-3.6683608)"
+         d="m 63.779528,131.22736 c 0,1.95691 -1.586392,3.5433 -3.543307,3.5433 -1.956914,0 -3.543307,-1.58639 -3.543307,-3.5433 0,-1.95692 1.586393,-3.54331 3.543307,-3.54331 1.956915,0 3.543307,1.58639 3.543307,3.54331 z"
+         sodipodi:ry="3.5433071"
+         sodipodi:rx="3.5433071"
+         sodipodi:cy="131.22736"
+         sodipodi:cx="60.236221"
+         id="path2858"
+         style="fill:#000000;fill-opacity:1;fill-rule:nonzero;stroke:none"
+         sodipodi:type="arc" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path3632"
+         d="m 60.230197,128.85263 0,-8.26689"
+         style="fill:none;stroke:#000000;stroke-width:1.0629921;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         transform="translate(-53.899592,-3.6683608)" />
+      <path
+         sodipodi:nodetypes="cc"
+         id="path3634"
+         d="m 56.687244,124.12869 7.085906,0"
+         style="fill:none;stroke:#000000;stroke-width:0.99921262;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         transform="translate(-53.899592,-3.6683608)" />
+    </g>
+  </g>
+</svg>
diff --git a/icons/cliff.svg b/icons/cliff.svg
new file mode 100644 (file)
index 0000000..b773f92
--- /dev/null
@@ -0,0 +1,94 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="17.96653"
+   height="3.6682999"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="cliff.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="0.70710678"
+     inkscape:cx="401.49514"
+     inkscape:cy="-263.82391"
+     inkscape:document-units="mm"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:snap-grids="true"
+     inkscape:window-width="1272"
+     inkscape:window-height="1004"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     borderlayer="false"
+     showborder="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       units="mm"
+       spacingx="1mm"
+       spacingy="1mm"
+       dotted="false" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(53.899592,3.6683608)">
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.25;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m -53.774592,-3.5433608 17.71653,0"
+       id="path2818"
+       sodipodi:nodetypes="cc"
+       inkscape:transform-center-x="-1.8966508"
+       inkscape:transform-center-y="0.125" />
+    <path
+       style="fill:#000000;fill-opacity:1;stroke:none"
+       d="m -46.687982,-3.5433608 1.76698,3.543300013406 1.77633,-3.543300013406 -3.54331,0"
+       id="path2822"
+       inkscape:transform-center-x="-1.8966508"
+       inkscape:transform-center-y="1.89665" />
+  </g>
+</svg>
diff --git a/icons/cross.svg b/icons/cross.svg
new file mode 100644 (file)
index 0000000..e0468e0
--- /dev/null
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="54"
+   height="69"
+   id="svg3759"
+   version="1.1"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="cross.svg">
+  <defs
+     id="defs3761" />
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="5.6"
+     inkscape:cx="13.964286"
+     inkscape:cy="31.999997"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1016"
+     inkscape:window-height="748"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0">
+    <inkscape:grid
+       type="xygrid"
+       id="grid4283"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       originx="-298px"
+       originy="-488px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3764">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-298,-495.36218)">
+    <g
+       id="g4291"
+       style="stroke:#ffffff;stroke-width:18;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+      <path
+         inkscape:connector-curvature="0"
+         id="path4285"
+         d="m 325,502.36218 0,55"
+         style="fill:none;stroke:#ffffff;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path4287"
+         d="m 305,522.36218 40,0"
+         style="fill:none;stroke:#ffffff;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path4289"
+         d="m 325,557.36218 10,0"
+         style="fill:none;stroke:#ffffff;stroke-width:18;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    </g>
+    <g
+       transform="translate(0,2.6171874e-6)"
+       id="g4291-9"
+       style="stroke:#000000;stroke-width:10;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+      <path
+         inkscape:connector-curvature="0"
+         id="path4285-5"
+         d="m 325,502.36218 0,55"
+         style="fill:none;stroke:#000000;stroke-width:10;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path4287-4"
+         d="m 305,522.36218 40,0"
+         style="fill:none;stroke:#000000;stroke-width:10;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         inkscape:connector-curvature="0"
+         id="path4289-4"
+         d="m 325,557.36218 10,0"
+         style="fill:none;stroke:#000000;stroke-width:10;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    </g>
+  </g>
+</svg>
diff --git a/icons/deciduous.svg b/icons/deciduous.svg
new file mode 100644 (file)
index 0000000..300b30d
--- /dev/null
@@ -0,0 +1,318 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="11.339"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="deciduous.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="11.339">
+  <metadata
+     id="metadata2975">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="wrapper"
+     inkscape:cx="5.8105071"
+     inkscape:cy="5.5752164"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:window-height="768"
+     inkscape:window-width="1024"
+     inkscape:window-x="0"
+     inkscape:window-y="20"
+     inkscape:zoom="35.399848"
+     objecttolerance="10.0"
+     pagecolor="#ffffff"
+     showgrid="false"
+     inkscape:window-maximized="0" />
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       id="perspective2441"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2466"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3333"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3401"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3464"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3581"
+       inkscape:persp3d-origin="225 : 150 : 1"
+       inkscape:vp_x="0 : 225 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="450 : 225 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4312"
+       inkscape:persp3d-origin="225 : 150 : 1"
+       inkscape:vp_x="0 : 225 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="450 : 225 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective8860"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective8887"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4904"
+       inkscape:persp3d-origin="16 : 10.666667 : 1"
+       inkscape:vp_x="0 : 16 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="32 : 16 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4668"
+       inkscape:persp3d-origin="6 : 4 : 1"
+       inkscape:vp_x="0 : 6 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="12 : 6 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4471"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <symbol
+       id="symbol-university"
+       viewBox="244.5 110 489 219.9">
+      <path
+         d="M79,43l57,119c0,0,21-96,104-96s124,106,124,106l43-133l82-17L0,17L79,43z"
+         id="path4460" />
+      <path
+         d="M94,176l-21,39"
+         fill="none"
+         id="path4462"
+         stroke="#000000"
+         stroke-width="20" />
+      <path
+         d="M300,19c0,10.5-22.6,19-50.5,19S199,29.5,199,19s22.6-19,50.5-19S300,8.5,300,19z"
+         id="path4464" />
+      <path
+         d="M112,216l-16-38L64,88c0,0-9-8-4-35s16-24,16-24"
+         id="path4466"
+         ill="none"
+         stroke="#000000"
+         stroke-width="20" />
+    </symbol>
+    <inkscape:perspective
+       id="perspective3452"
+       inkscape:persp3d-origin="30 : 20 : 1"
+       inkscape:vp_x="0 : 30 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="60 : 30 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9479"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9690"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9819"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9817"
+       inkscape:persp3d-origin="30 : 20 : 1"
+       inkscape:vp_x="0 : 30 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="60 : 30 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <symbol
+       id="symbol9807"
+       viewBox="244.5 110 489 219.9">
+      <path
+         d="M79,43l57,119c0,0,21-96,104-96s124,106,124,106l43-133l82-17L0,17L79,43z"
+         id="path9809" />
+      <path
+         d="M94,176l-21,39"
+         fill="none"
+         id="path9811"
+         stroke="#000000"
+         stroke-width="20" />
+      <path
+         d="M300,19c0,10.5-22.6,19-50.5,19S199,29.5,199,19s22.6-19,50.5-19S300,8.5,300,19z"
+         id="path9813" />
+      <path
+         d="M112,216l-16-38L64,88c0,0-9-8-4-35s16-24,16-24"
+         id="path9815"
+         ill="none"
+         stroke="#000000"
+         stroke-width="20" />
+    </symbol>
+    <inkscape:perspective
+       id="perspective9805"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9803"
+       inkscape:persp3d-origin="6 : 4 : 1"
+       inkscape:vp_x="0 : 6 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="12 : 6 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9801"
+       inkscape:persp3d-origin="16 : 10.666667 : 1"
+       inkscape:vp_x="0 : 16 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="32 : 16 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9799"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9797"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9795"
+       inkscape:persp3d-origin="225 : 150 : 1"
+       inkscape:vp_x="0 : 225 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="450 : 225 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9793"
+       inkscape:persp3d-origin="225 : 150 : 1"
+       inkscape:vp_x="0 : 225 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="450 : 225 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9791"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9789"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9787"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9785"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective9783"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <g
+     id="wrapper"
+     transform="scale(0.020)">
+    <path
+       style="fill:none;fill-opacity:1;stroke:#ffffff;stroke-opacity:1;stroke-width:50;stroke-miterlimit:4;stroke-dasharray:none"
+       id="path3625"
+       d="M 286.8826,97.87318 C 261.09763,97.43733 233.95641,111.70565 228.47068,140.94717 C 226.14803,153.3279 224.20796,157.30914 209.5539,157.56326 C 164.41142,158.3461 145.21328,224.7052 183.35162,255.98159 C 187.1306,259.08071 184.9536,261.77225 172.23163,271.44733 C 107.48588,320.68656 177.20141,418.93839 243.42515,371.78291 C 253.12064,364.87912 254.65808,364.80358 256.84583,370.50475 C 261.46373,382.5388 259.41139,440.32236 253.65043,459.4647 L 247.89871,478.50928 L 282.53686,478.50928 C 285.96604,478.50928 287.68128,478.52126 290.46145,478.50928 C 293.27634,478.5218 295.02937,478.50928 298.51385,478.50928 L 333.152,478.50928 L 327.5281,459.4647 C 321.76713,440.32236 319.58693,382.5388 324.20488,370.50475 C 326.39263,364.80358 327.93007,364.87912 337.62556,371.78291 C 403.84931,418.93839 473.56483,320.68656 408.81908,271.44733 C 396.09711,261.77225 393.26406,257.73253 397.82691,255.98159 C 435.96524,241.34651 415.52985,165.00264 371.49681,157.56326 C 357.04533,155.12171 355.0305,153.3279 352.70785,140.94717 C 346.96239,110.32135 317.40486,96.22105 290.58926,98.1288 C 289.3671,98.04127 288.11352,97.89399 286.8826,97.87318 z" />
+    <path
+       d="M 286.8826,97.87318 C 261.09763,97.43733 233.95641,111.70565 228.47068,140.94717 C 226.14803,153.3279 224.20796,157.30914 209.5539,157.56326 C 164.41142,158.3461 145.21328,224.7052 183.35162,255.98159 C 187.1306,259.08071 184.9536,261.77225 172.23163,271.44733 C 107.48588,320.68656 177.20141,418.93839 243.42515,371.78291 C 253.12064,364.87912 254.65808,364.80358 256.84583,370.50475 C 261.46373,382.5388 259.41139,440.32236 253.65043,459.4647 L 247.89871,478.50928 L 282.53686,478.50928 C 285.96604,478.50928 287.68128,478.52126 290.46145,478.50928 C 293.27634,478.5218 295.02937,478.50928 298.51385,478.50928 L 333.152,478.50928 L 327.5281,459.4647 C 321.76713,440.32236 319.58693,382.5388 324.20488,370.50475 C 326.39263,364.80358 327.93007,364.87912 337.62556,371.78291 C 403.84931,418.93839 473.56483,320.68656 408.81908,271.44733 C 396.09711,261.77225 393.26406,257.73253 397.82691,255.98159 C 435.96524,241.34651 415.52985,165.00264 371.49681,157.56326 C 357.04533,155.12171 355.0305,153.3279 352.70785,140.94717 C 346.96239,110.32135 317.40486,96.22105 290.58926,98.1288 C 289.3671,98.04127 288.11352,97.89399 286.8826,97.87318 z"
+       id="path9696"
+       style="fill:#006322;fill-opacity:1" />
+  </g>
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
diff --git a/icons/fuel.svg b/icons/fuel.svg
new file mode 100644 (file)
index 0000000..c225750
--- /dev/null
@@ -0,0 +1,195 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="7.7474999"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="fuel.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="6.7085013">
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+        <dc:language>
+en</dc:language>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="g5143"
+     inkscape:cx="6.4066754"
+     inkscape:cy="5.7534912"
+     inkscape:pageopacity="0.51764706"
+     inkscape:pageshadow="2"
+     inkscape:window-height="748"
+     inkscape:window-width="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:zoom="32"
+     objecttolerance="10.0"
+     pagecolor="#8467ff"
+     showgrid="false"
+     inkscape:window-maximized="1" />
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       id="perspective12"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <style
+       id="style6"
+       type="text/css">
+    .fil0 {fill:#EF7900}</style>
+    <inkscape:perspective
+       id="perspective6807"
+       inkscape:persp3d-origin="29.116032 : 19.410688 : 1"
+       inkscape:vp_x="0 : 29.116032 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="58.232063 : 29.116032 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6954"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="ArrowStart"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 z"
+         id="path2295" />
+    </marker>
+    <marker
+       id="ArrowEnd"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 z"
+         id="path2292" />
+    </marker>
+    <inkscape:perspective
+       id="perspective7597"
+       inkscape:persp3d-origin="178.5405 : 158.483 : 1"
+       inkscape:vp_x="0 : 237.7245 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="357.08099 : 237.7245 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="marker8025"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 10,0 z"
+         id="path8027" />
+    </marker>
+    <marker
+       id="marker8021"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 0,0 z"
+         id="path8023" />
+    </marker>
+    <inkscape:perspective
+       id="perspective8018"
+       inkscape:persp3d-origin="205.96652 : 131.6262 : 1"
+       inkscape:vp_x="0 : 197.4393 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="411.93304 : 197.4393 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective8269"
+       inkscape:persp3d-origin="99.087723 : 77.339676 : 1"
+       inkscape:vp_x="0 : 116.00951 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="198.17545 : 116.00951 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5155"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <g
+     id="wrapper"
+     transform="matrix(0.02,0,0,0.02,-4.6682468,-2.1477037)">
+    <g
+       id="g5143-0"
+       transform="translate(128.5249,11.260187)">
+      <path
+         style="fill:#ffffff;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:31.25;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         id="path1897-6"
+         d="m 249,111.75 c -8.48242,0 -16.9375,6.13064 -16.9375,15.9375 l -0.1875,315.25 17.4375,0 0.15625,24.9375 160.09375,-0.0937 0.125,-24.84375 15,0.125 0,-318.625 c 10e-6,-6.79608 -3.93663,-12.6875 -11.34375,-12.6875 L 249,111.75 z m 26.78125,34.84375 104.90625,0 c 5.80827,0 10.37501,2.36551 10.375,10.375 l 0,82.875 c 0,5.61371 -3.00087,9.6875 -9.40625,9.6875 L 276.0625,249.25 c -6.09827,0 -11.34375,-4.50537 -11.34375,-11.0625 l 0.53125,-81.5 c 0,-6.5237 4.51979,-10.09375 10.53125,-10.09375 z" />
+      <path
+         style="fill:none;stroke:#ffffff;stroke-width:31.25;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+         id="path1899-8"
+         d="m 232.83211,220.10791 -16.02161,-0.0949 c -0.10388,65.19305 0.37125,193.85491 0.64277,196.05634 0.5796,4.69931 0.22912,9.56779 -0.97746,14.00308 -25.11647,63.27313 -106.38805,34.21172 -94.84854,-27.3567 25.3448,-61.07053 60.88334,-102.39502 78.57086,-174.42538 l 1.57455,53.54482 c -15.91818,30.97568 -44.00102,85.37648 -65.77992,130.84982 -1.83488,41.98386 48.10886,55.81624 63.75073,10.75222 6.97053,-78.88168 -0.26208,-164.86493 -0.39311,-247.2974 10.16452,-30.21727 21.38001,-33.10969 32.76209,-31.67045" />
+    </g>
+    <g
+       id="g5143"
+       transform="translate(128.28204,12.00983)">
+      <path
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
+         id="path1897"
+         d="m 249,111.75 c -8.48242,0 -16.9375,6.13064 -16.9375,15.9375 l -0.1875,315.25 17.4375,0 0.15625,24.9375 160.09375,-0.0937 0.125,-24.84375 15,0.125 0,-318.625 c 10e-6,-6.79608 -3.93663,-12.6875 -11.34375,-12.6875 L 249,111.75 z m 26.78125,34.84375 104.90625,0 c 5.80827,0 10.37501,2.36551 10.375,10.375 l 0,82.875 c 0,5.61371 -3.00087,9.6875 -9.40625,9.6875 L 276.0625,249.25 c -6.09827,0 -11.34375,-4.50537 -11.34375,-11.0625 l 0.53125,-81.5 c 0,-6.5237 4.51979,-10.09375 10.53125,-10.09375 z" />
+      <path
+         style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none"
+         id="path1899"
+         d="m 232.83211,220.10791 -16.02161,-0.0949 c -0.10388,65.19305 0.37125,193.85491 0.64277,196.05634 0.5796,4.69931 0.22912,9.56779 -0.97746,14.00308 -25.11647,63.27313 -106.38805,34.21172 -94.84854,-27.3567 25.3448,-61.07053 60.88334,-102.39502 78.57086,-174.42538 l 1.57455,53.54482 c -15.91818,30.97568 -44.00102,85.37648 -65.77992,130.84982 -1.83488,41.98386 48.10886,55.81624 63.75073,10.75222 6.97053,-78.88168 -0.26208,-164.86493 -0.39311,-247.2974 10.16452,-30.21727 21.38001,-33.10969 32.76209,-31.67045" />
+    </g>
+  </g>
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
diff --git a/icons/gate.svg b/icons/gate.svg
new file mode 100644 (file)
index 0000000..ad79d21
--- /dev/null
@@ -0,0 +1,231 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="8.0017128"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.48.3.1 r9886"
+   sodipodi:docname="gate.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="11.063485">
+  <g
+     id="wrapper"
+     transform="matrix(0.02,0,0,0.02,-0.35950751,-1.8806165)"
+     inkscape:transform-center-y="-2"
+     style="stroke:#ffffff;stroke-width:100;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+    <path
+       d="m 68.690452,226.50324 451.760378,0"
+       id="path2900"
+       style="fill:none;stroke:#ffffff;stroke-width:100;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       inkscape:connector-curvature="0" />
+    <path
+       d="m 68.690452,360.07403 451.760378,0"
+       id="path3730"
+       sodipodi:nodetypes="cc"
+       style="fill:none;stroke:#ffffff;stroke-width:100;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       inkscape:connector-curvature="0" />
+    <path
+       d="M 132.37824,443.67778 452.51615,144.52914"
+       id="path3732"
+       sodipodi:nodetypes="cc"
+       style="fill:none;stroke:#ffffff;stroke-width:100;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       inkscape:connector-curvature="0" />
+  </g>
+  <defs
+     id="defs22">
+    <inkscape:perspective
+       id="perspective24"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="ArrowStart"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 z"
+         id="path3568"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       id="ArrowEnd"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 z"
+         id="path3565"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <inkscape:perspective
+       id="perspective2683"
+       inkscape:persp3d-origin="306.082 : 204.39034 : 1"
+       inkscape:vp_x="0 : 306.58551 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="612.164 : 306.58551 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="marker2959"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 z"
+         id="path2626"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       id="marker2956"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 z"
+         id="path2623"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <inkscape:perspective
+       id="perspective2953"
+       inkscape:persp3d-origin="306.082 : 204.39034 : 1"
+       inkscape:vp_x="0 : 306.58551 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="612.164 : 306.58551 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="marker3077"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 z"
+         id="path3298"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <marker
+       id="marker3074"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 z"
+         id="path3295"
+         inkscape:connector-curvature="0" />
+    </marker>
+    <inkscape:perspective
+       id="perspective3071"
+       inkscape:persp3d-origin="180.936 : 163.92867 : 1"
+       inkscape:vp_x="0 : 245.89301 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="361.87201 : 245.89301 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3724"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_x="0 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="1 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+        <dc:language>
+en</dc:language>
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="svg2"
+     inkscape:cx="0.22510228"
+     inkscape:cy="5.0923615"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:window-height="748"
+     inkscape:window-maximized="1"
+     inkscape:window-width="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:zoom="22.627417"
+     objecttolerance="10.0"
+     pagecolor="#ffffff"
+     showgrid="false"
+     fit-margin-top="0"
+     fit-margin-left="0"
+     fit-margin-right="0"
+     fit-margin-bottom="0" />
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+  <g
+     inkscape:transform-center-y="-2"
+     transform="matrix(0.02,0,0,0.02,-0.35950751,-1.8806165)"
+     id="g3015">
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:54.68502426;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path3017"
+       d="m 68.690452,226.50324 451.760378,0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:54.68502426;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       sodipodi:nodetypes="cc"
+       id="path3019"
+       d="m 68.690452,360.07403 451.760378,0" />
+    <path
+       inkscape:connector-curvature="0"
+       style="fill:none;stroke:#000000;stroke-width:54.68502426;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       sodipodi:nodetypes="cc"
+       id="path3021"
+       d="M 132.37824,443.67778 452.51615,144.52914" />
+  </g>
+</svg>
diff --git a/icons/guidepost.svg b/icons/guidepost.svg
new file mode 100644 (file)
index 0000000..50751b5
--- /dev/null
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="6.7570868"
+   height="10.629921"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="guidepost.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective3607"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3664"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3691"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#4500ff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.55294118"
+     inkscape:pageshadow="2"
+     inkscape:zoom="45.254834"
+     inkscape:cx="8.238623"
+     inkscape:cy="2.7539586"
+     inkscape:document-units="mm"
+     inkscape:current-layer="main"
+     showgrid="true"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:snap-grids="true"
+     inkscape:window-width="1272"
+     inkscape:window-height="1004"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     borderlayer="false"
+     showborder="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       units="mm"
+       spacingx="0.0625mm"
+       spacingy="0.0625mm"
+       dotted="false" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-1.8451322,-118.92218)">
+    <g
+       transform="translate(-4.96063e-8,2.0953825e-6)"
+       id="border"
+       style="fill:none;stroke:#ffffff;stroke-width:0.99921262;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       inkscape:label="#g3678-5">
+      <path
+         sodipodi:nodetypes="cc"
+         transform="translate(1.9018252,116.03155)"
+         id="path3652-3"
+         d="m 3.3218504,12.634726 0,-8.8582678"
+         style="fill:none;stroke:#ffffff;stroke-width:1.77165353;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path3654-9"
+         d="m 3.0091087,120.25092 3.9862204,0 1.1072835,0.88583 -1.1072835,0.88583 -3.9862204,0 0,-1.77166 z"
+         style="fill:none;stroke:#ffffff;stroke-width:0.99921262;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path3654-8-8"
+         d="m 7.4382425,122.46548 -3.9862204,0 -1.1072835,0.88583 1.1072835,0.88583 3.9862204,0 0,-1.77166 z"
+         style="fill:none;stroke:#ffffff;stroke-width:0.99921262;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    </g>
+    <g
+       id="main"
+       transform="translate(2.2834645e-8,2.0953825e-6)"
+       inkscape:label="#g3678">
+      <path
+         sodipodi:nodetypes="cc"
+         transform="translate(1.9018252,116.03155)"
+         id="path3652"
+         d="m 3.3218504,12.634726 0,-8.8582678"
+         style="fill:none;stroke:#643721;stroke-width:0.99921262000000000;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path3654"
+         d="m 3.0091087,120.25092 3.9862204,0 1.1072835,0.88583 -1.1072835,0.88583 -3.9862204,0 0,-1.77166 z"
+         style="fill:#643721;fill-opacity:1;stroke:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path3654-8"
+         d="m 7.4382425,122.46548 -3.9862204,0 -1.1072835,0.88583 1.1072835,0.88583 3.9862204,0 0,-1.77166 z"
+         style="fill:#643721;fill-opacity:1;stroke:none" />
+    </g>
+  </g>
+</svg>
diff --git a/icons/logo.svg b/icons/logo.svg
new file mode 100644 (file)
index 0000000..10b0164
--- /dev/null
@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="logo.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2947"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3910"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4140"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#7470d0"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="7.9195959"
+     inkscape:cx="174.39239"
+     inkscape:cy="40.188039"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:snap-grids="true"
+     inkscape:snap-bbox="false"
+     inkscape:snap-nodes="true"
+     inkscape:window-width="1272"
+     inkscape:window-height="1004"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       units="mm"
+       spacingx="1mm"
+       spacingy="1mm" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <g
+       transform="matrix(0.09452091,0,0,0.09452091,156.91602,888.30189)"
+       id="g2916-1"
+       style="stroke:#ffffff;stroke-width:31.73900795;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+      <path
+         id="path2425-4"
+         d="m -106.87586,794.79497 c 0,0 1.91986,55.86792 1.91986,55.86792"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path2423-5"
+         d="m -176.18279,810.92179 c 0.40525,-0.39416 32.8296,-21.31044 67.0031,-25.9181 57.019835,-7.10348 64.50729,3.07177 65.083247,8.44738 3.83972,26.68605 -63.739347,29.75783 -63.931327,29.75783"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2421-8"
+         d="m -115.51523,867.04697 c 0,0 8.70465,27.64598 8.70465,27.64598"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2419-0"
+         d="m -113.21139,880.35544 c 0,0 -66.30044,13.05505 -63.73935,48.12705 0.76795,17.14818 34.81474,9.98327 44.28349,7.16491 20.99174,-5.11835 41.983492,-9.46875 46.844575,4.35424 5.375609,15.10162 -33.532275,23.549 -33.532275,23.80627 0,0 -63.9966,11.26189 -63.9966,11.26189"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2416-0"
+         d="m -174.13622,860.3889 c 0,0 22.78489,16.12682 22.78489,16.12682 0,0 14.07641,-27.8994 14.07641,-27.8994"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2414-8"
+         d="m -192.30962,950.23834 c 0,0 13.31231,38.91171 13.31231,38.91171"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2412-2"
+         d="m -90.941022,867.55766 c 5.37561,13.24703 12.671076,42.04492 14.206965,58.36373"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2410-2"
+         d="m -44.096443,864.29389 c 0,0 -35.517406,38.20521 -35.517406,38.01323 0,-0.19199 41.468972,16.51079 41.468972,16.51079"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2408-3"
+         d="m 89.783052,878.30887 c 0,0 43.772808,-8.19012 43.772808,-8.19012"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2406-0"
+         d="m 93.365512,903.14034 c 0,0 46.587318,-6.40081 46.587318,-6.40081"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2404-3"
+         d="m 95.669344,925.15345 c 0,0 47.101836,-6.9115 47.101836,-6.9115"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2402-1"
+         d="m 28.474255,834.66278 c 0,0 16.768056,58.621 16.768056,58.621 0,0 28.413922,-6.9115 28.413922,-6.9115"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path2400-5"
+         d="m -153.91242,1054.936 c 12.54436,3.8397 74.233297,51.1949 74.233297,51.1949 0.144238,-0.09 147.70249,3.8398 147.70249,3.8398 0,0 -5.118346,-110.58395 -5.118346,-110.58395 0,0 -142.069621,1.79315 -142.069621,1.79315 0,0 -68.34701,44.798 -74.74782,53.7561 z"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="ccc"
+         id="path2398-0"
+         d="m -113.97934,1157.8405 c 0.1806,-0.2266 35.836105,-43.0049 36.477337,-51.4523 0,0 28.797897,49.4057 34.684186,54.0133"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="ccc"
+         id="path2396-6"
+         d="m 27.065078,1161.4229 c 0.323587,-0.3493 39.806372,-51.195 39.806372,-51.195 0,0 43.38883,48.1232 43.38883,48.1232"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2394-1"
+         d="m -77.885975,1103.8924 c 0,0 -0.767942,-102.4552 -0.767942,-102.4552"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2392-4"
+         d="m 62.905021,999.64401 c 0,0 7.422178,-16.12682 27.899396,-18.1734 6.143552,-1.79315 32.000223,4.35041 31.742963,23.54899 0,0.2573 1.0252,13.823 -14.33368,16.1268 -8.19396,-0.7679 -15.358871,-4.8649 -14.848188,-15.8695 0,0 0.510683,-23.80629 30.207068,-20.99177 12.02984,0.51068 13.82299,2.81451 23.549,12.54436"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path2390-8"
+         d="m -196.14933,1009.6273 c 0,0 198.8974655,-49.91638 285.29115,-52.60418 0,0 -113.271721,34.94145 -148.981113,57.59578 0,0 37.245278,3.4558 49.14841,8.8314 0,0 -11.135187,8.0634 -16.126821,19.5825 0.295123,0.071 23.4222875,1.9199 28.7978936,9.5993"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2388-3"
+         d="m 21.946731,898.65938 c 0,0 -51.260256,-10.17525 -54.524016,22.65435 -1.727874,20.35051 31.1017287,19.58257 36.2853491,19.00661 12.8630609,-0.95993 39.9330839,-4.99163 41.0849979,-24.5742 1.535889,-16.5108 -36.6693204,-12.67108 -36.6693204,-12.67108"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2418-9"
+         d="m -2.6274736,808.80995 c 0,0 -19.1985964,-7.87143 -34.1735034,0.76794 0,0 -14.398947,11.71115 -12.863061,22.27038 1.535889,11.32717 21.694416,14.78291 31.4857,13.82299 19.9665419,-0.38398 24.7661909,-6.52753 28.605911,-11.13519 4.799649,-10.36724 1.727875,-13.05505 -1.3439028,-16.12682 -4.799649,-6.52753 -23.4222882,-10.17526 -23.4222882,-10.17526"
+         style="fill:none;stroke:#ffffff;stroke-width:31.73900795;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+      <path
+         id="path2429-4"
+         d="m 57.578694,247.35558 c 0,3.41532 -2.771858,6.18718 -6.187184,6.18718 -3.415326,0 -6.187184,-2.77186 -6.187184,-6.18718 0,-3.41533 2.771858,-6.18719 6.187184,-6.18719 3.415326,0 6.187184,2.77186 6.187184,6.18719 z"
+         style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:#ffffff;stroke-width:35.34571457;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         transform="matrix(0.8979592,0,0,0.8979592,-153.12491,819.07358)" />
+    </g>
+    <g
+       transform="matrix(0.09452091,0,0,0.09452091,156.91602,888.30189)"
+       id="g2916">
+      <path
+         id="path2425"
+         d="m -106.87586,794.79497 c 0,0 1.91986,55.86792 1.91986,55.86792"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccc"
+         id="path2423"
+         d="m -176.18279,810.92179 c 0.40525,-0.39416 32.8296,-21.31044 67.0031,-25.9181 57.019835,-7.10348 64.50729,3.07177 65.083247,8.44738 3.83972,26.68605 -63.739347,29.75783 -63.931327,29.75783"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2421"
+         d="m -115.51523,867.04697 c 0,0 8.70465,27.64598 8.70465,27.64598"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2419"
+         d="m -113.21139,880.35544 c 0,0 -66.30044,13.05505 -63.73935,48.12705 0.76795,17.14818 34.81474,9.98327 44.28349,7.16491 20.99174,-5.11835 41.983492,-9.46875 46.844575,4.35424 5.375609,15.10162 -33.532275,23.549 -33.532275,23.80627 0,0 -63.9966,11.26189 -63.9966,11.26189"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2416"
+         d="m -174.13622,860.3889 c 0,0 22.78489,16.12682 22.78489,16.12682 0,0 14.07641,-27.8994 14.07641,-27.8994"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2414"
+         d="m -192.30962,950.23834 c 0,0 13.31231,38.91171 13.31231,38.91171"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2412"
+         d="m -90.941022,867.55766 c 5.37561,13.24703 12.671076,42.04492 14.206965,58.36373"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2410"
+         d="m -44.096443,864.29389 c 0,0 -35.517406,38.20521 -35.517406,38.01323 0,-0.19199 41.468972,16.51079 41.468972,16.51079"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2408"
+         d="m 89.783052,878.30887 c 0,0 43.772808,-8.19012 43.772808,-8.19012"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2406"
+         d="m 93.365512,903.14034 c 0,0 46.587318,-6.40081 46.587318,-6.40081"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2404"
+         d="m 95.669344,925.15345 c 0,0 47.101836,-6.9115 47.101836,-6.9115"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2402"
+         d="m 28.474255,834.66278 c 0,0 16.768056,58.621 16.768056,58.621 0,0 28.413922,-6.9115 28.413922,-6.9115"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path2400"
+         d="m -153.91242,1054.936 c 12.54436,3.8397 74.233297,51.1949 74.233297,51.1949 0.144238,-0.09 147.70249,3.8398 147.70249,3.8398 0,0 -5.118346,-110.58395 -5.118346,-110.58395 0,0 -142.069621,1.79315 -142.069621,1.79315 0,0 -68.34701,44.798 -74.74782,53.7561 z"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="ccc"
+         id="path2398"
+         d="m -113.97934,1157.8405 c 0.1806,-0.2266 35.836105,-43.0049 36.477337,-51.4523 0,0 28.797897,49.4057 34.684186,54.0133"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="ccc"
+         id="path2396"
+         d="m 27.065078,1161.4229 c 0.323587,-0.3493 39.806372,-51.195 39.806372,-51.195 0,0 43.38883,48.1232 43.38883,48.1232"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2394"
+         d="m -77.885975,1103.8924 c 0,0 -0.767942,-102.4552 -0.767942,-102.4552"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2392"
+         d="m 62.905021,999.64401 c 0,0 7.422178,-16.12682 27.899396,-18.1734 6.143552,-1.79315 32.000223,4.35041 31.742963,23.54899 0,0.2573 1.0252,13.823 -14.33368,16.1268 -8.19396,-0.7679 -15.358871,-4.8649 -14.848188,-15.8695 0,0 0.510683,-23.80629 30.207068,-20.99177 12.02984,0.51068 13.82299,2.81451 23.549,12.54436"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         sodipodi:nodetypes="cccccc"
+         id="path2390"
+         d="m -196.14933,1009.6273 c 0,0 198.8974655,-49.91638 285.29115,-52.60418 0,0 -113.271721,34.94145 -148.981113,57.59578 0,0 37.245278,3.4558 49.14841,8.8314 0,0 -11.135187,8.0634 -16.126821,19.5825 0.295123,0.071 23.4222875,1.9199 28.7978936,9.5993"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2388"
+         d="m 21.946731,898.65938 c 0,0 -51.260256,-10.17525 -54.524016,22.65435 -1.727874,20.35051 31.1017287,19.58257 36.2853491,19.00661 12.8630609,-0.95993 39.9330839,-4.99163 41.0849979,-24.5742 1.535889,-16.5108 -36.6693204,-12.67108 -36.6693204,-12.67108"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2418"
+         d="m -2.6274736,808.80995 c 0,0 -19.1985964,-7.87143 -34.1735034,0.76794 0,0 -14.398947,11.71115 -12.863061,22.27038 1.535889,11.32717 21.694416,14.78291 31.4857,13.82299 19.9665419,-0.38398 24.7661909,-6.52753 28.605911,-11.13519 4.799649,-10.36724 1.727875,-13.05505 -1.3439028,-16.12682 -4.799649,-6.52753 -23.4222882,-10.17526 -23.4222882,-10.17526"
+         style="fill:none;stroke:#000000;stroke-width:11.05979252;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none" />
+      <path
+         id="path2429"
+         d="m 57.578694,247.35558 c 0,3.41532 -2.771858,6.18718 -6.187184,6.18718 -3.415326,0 -6.187184,-2.77186 -6.187184,-6.18718 0,-3.41533 2.771858,-6.18719 6.187184,-6.18719 3.415326,0 6.187184,2.77186 6.187184,6.18719 z"
+         style="color:#000000;fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.25;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+         transform="matrix(0.8979592,0,0,0.8979592,-153.12491,819.07358)" />
+    </g>
+  </g>
+</svg>
diff --git a/icons/memorial.svg b/icons/memorial.svg
new file mode 100644 (file)
index 0000000..ea1fa4d
--- /dev/null
@@ -0,0 +1,95 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="190"
+   height="176.02705"
+   id="svg3617"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="memorial.svg">
+  <defs
+     id="defs3619">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective3625" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.979899"
+     inkscape:cx="102.49509"
+     inkscape:cy="92.041782"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1016"
+     inkscape:window-height="748"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3627"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3622">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-192.5,-438.83513)">
+    <path
+       sodipodi:type="arc"
+       style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:11;stroke-linecap:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       id="path4156"
+       sodipodi:cx="290"
+       sodipodi:cy="487.36218"
+       sodipodi:rx="50"
+       sodipodi:ry="45"
+       d="m 240.00343,487.88917 c -0.32339,-24.85111 21.79868,-45.23285 49.41103,-45.5239 27.61234,-0.29105 50.25872,19.61881 50.58211,44.46992 0.001,0.11555 0.003,0.2311 0.003,0.34665"
+       sodipodi:start="3.1298815"
+       sodipodi:end="6.2791776"
+       sodipodi:open="true"
+       transform="translate(-2.5034287,1.9730086)" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:20;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 202.5,604.86218 170,0"
+       id="path4160"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:11;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none"
+       d="m 45,53.000061 0,114.999999 100,0 0,-114.999999 -100,0 z"
+       id="path4185"
+       transform="translate(192.5,436.86212)" />
+  </g>
+</svg>
diff --git a/icons/meritko.svg b/icons/meritko.svg
new file mode 100644 (file)
index 0000000..c9ac553
--- /dev/null
@@ -0,0 +1,196 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="meritko.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+    <inkscape:perspective
+       id="perspective2947"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective3910"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4140"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#7470d0"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="1"
+     inkscape:pageshadow="2"
+     inkscape:zoom="15.839192"
+     inkscape:cx="192.73651"
+     inkscape:cy="-6.2998166"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:snap-grids="true"
+     inkscape:snap-bbox="false"
+     inkscape:snap-nodes="true"
+     inkscape:window-width="1272"
+     inkscape:window-height="1004"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     showguides="true"
+     inkscape:guide-bbox="true"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       units="mm"
+       spacingx="1mm"
+       spacingy="1mm" />
+    <inkscape:grid
+       type="xygrid"
+       id="grid3972"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       units="mm"
+       spacingx="3.19mm"
+       spacingy="3.19mm"
+       color="#00ff00"
+       opacity="0.45490196"
+       empcolor="#00ff00"
+       empopacity="0.66666667"
+       dotted="false" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="116.92913,3.5433071"
+       id="guide3622" />
+    <sodipodi:guide
+       orientation="0,1"
+       position="129.4258,2.0203051"
+       id="guide2833" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title />
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.88582676999999999;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:1;fill-opacity:1"
+       d="m 113.0315,1052.3622 0,-3.5433"
+       id="path3609" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:0.88582676999999999;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;opacity:1;fill-opacity:1"
+       d="m 226.06299,1052.3622 -113.03149,0"
+       id="path2848" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 124.37503,1052.4253 -0.0404,-2.0834"
+       id="path3636"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 135.67611,1052.4253 -0.0383,-2.0834"
+       id="path3638"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 146.94094,1052.3622 0,-2.0203"
+       id="path3640"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 158.24409,1052.3622 0,-2.0203"
+       id="path3642"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 169.51622,1052.3937 0.031,-3.5748"
+       id="path3644" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 180.89286,1052.3399 -0.0425,-1.998"
+       id="path3648"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 192.14286,1052.4068 0.0107,-2.0649"
+       id="path3650"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 203.45669,1052.3622 0,-2.0203"
+       id="path3656"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 214.84375,1052.3399 -0.0839,-1.998"
+       id="path3658"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;opacity:1;fill-opacity:1"
+       d="m 226.06299,1052.3622 0,-3.5433"
+       id="path3660" />
+    <g
+       style="font-size:8px;font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;font-family:URW Palladio L;-inkscape-font-specification:URW Palladio L Medium;opacity:1"
+       id="text3662">
+      <path
+         d="m 162.01794,1059.8457 c 0.016,-0.01 0.032,-0.01 0.04,-0.01 0.064,0.01 0.08,0.064 0.08,0.304 l 0,3.544 c 0,0.232 -0.152,0.36 -0.456,0.376 l -0.576,0.024 0,0.296 c 0.488,-0.01 0.976,-0.016 1.12,-0.016 0.24,-0.01 0.408,-0.01 0.504,-0.01 0.096,0 0.288,0 0.552,0.01 0.104,0 0.472,0.01 0.864,0.016 l 0,-0.296 -0.496,-0.024 c -0.304,-0.016 -0.456,-0.136 -0.456,-0.376 l 0,-2.576 c 0,-0.632 0.016,-1.136 0.08,-2.032 l -0.096,-0.08 c -0.976,0.384 -1.896,0.696 -2.392,0.808 0,0.2 0.032,0.352 0.112,0.56 l 1.12,-0.52"
+         id="path3667"
+         style="opacity:1" />
+      <path
+         d="m 168.21794,1058.5977 -0.576,0.176 c -0.232,0.072 -0.504,0.12 -0.968,0.168 l 0,0.288 0.424,0.024 c 0.176,0.01 0.208,0.088 0.208,0.56 l 0,3.744 c 0,0.432 -0.04,0.496 -0.288,0.512 l -0.28,0.016 0,0.296 c 0,0 1.032,-0.024 1.032,-0.024 0.16,0 0.544,0.01 1.08,0.024 l 0,-0.296 -0.28,-0.016 c -0.248,-0.016 -0.288,-0.08 -0.288,-0.512 l 0,-1.008 0.08,0 0.848,0.96 c 0.12,0.136 0.576,0.696 0.696,0.848 l 1.376,0 0,-0.272 c -0.168,-0.016 -0.296,-0.096 -0.44,-0.256 l -1.568,-1.76 0.064,-0.064 c 0.24,-0.24 0.472,-0.456 0.56,-0.512 l 0.496,-0.36 c 0.128,-0.096 0.264,-0.144 0.384,-0.144 l 0.248,0 0,-0.304 -1.04,0 -0.856,0.904 c -0.184,0.192 -0.4,0.392 -0.848,0.776 l 0,-3.72 -0.064,-0.048"
+         id="path3669"
+         style="opacity:1" />
+      <path
+         d="m 171.58856,1061.2217 0.36,0.024 c 0.184,0.016 0.208,0.088 0.208,0.56 l 0,1.752 c 0,0.432 -0.04,0.496 -0.288,0.512 l -0.28,0.016 0,0.296 c 0,0 1.032,-0.024 1.032,-0.024 0.184,0 0.528,0.01 1.016,0.024 l 0,-0.296 -0.216,-0.016 c -0.256,-0.016 -0.288,-0.08 -0.288,-0.512 l 0,-1.736 c 0,-0.264 0.328,-0.544 0.648,-0.544 0.464,0 0.688,0.304 0.688,0.928 l 0,1.352 c 0,0.432 -0.032,0.496 -0.288,0.512 l -0.248,0.016 0,0.296 c 0.832,-0.024 0.832,-0.024 1.032,-0.024 0.192,0 0.192,0 1.048,0.024 l 0,-0.296 -0.28,-0.016 c -0.248,-0.016 -0.288,-0.08 -0.288,-0.512 l 0,-1.736 c 0,-0.264 0.328,-0.544 0.648,-0.544 0.464,0 0.688,0.304 0.688,0.928 l 0,2.176 0.744,-0.024 c 0.4,0.01 0.456,0.01 0.784,0.024 l 0,-0.296 -0.264,-0.016 c -0.248,-0.016 -0.288,-0.08 -0.288,-0.512 l 0,-1.488 c 0,-1.104 -0.288,-1.48 -1.144,-1.48 -0.264,0 -0.44,0.048 -0.576,0.152 l -0.64,0.496 c -0.184,-0.456 -0.504,-0.648 -1.056,-0.648 -0.256,0 -0.432,0.048 -0.568,0.152 l -0.64,0.496 0,-0.6 -0.048,-0.048 c -0.832,0.272 -1.056,0.32 -1.496,0.344 l 0,0.288"
+         id="path3671"
+         style="opacity:1" />
+    </g>
+  </g>
+</svg>
diff --git a/icons/power_pole.svg b/icons/power_pole.svg
new file mode 100644 (file)
index 0000000..559010c
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="744.09448819"
+   height="1052.3622047"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="power_pole.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective10" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="89.6"
+     inkscape:cx="63.009096"
+     inkscape:cy="207.90885"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1272"
+     inkscape:window-height="1004"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2818"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <rect
+       style="fill:#ffffff;fill-opacity:1;fill-rule:nonzero;stroke:#777777;stroke-width:0.10629921;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0"
+       id="rect3596"
+       width="1"
+       height="1"
+       x="60"
+       y="841.36218"
+       ry="0" />
+  </g>
+</svg>
diff --git a/icons/train.svg b/icons/train.svg
new file mode 100644 (file)
index 0000000..c4dde63
--- /dev/null
@@ -0,0 +1,131 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="11.898861"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="train.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="9.9799995">
+  <defs
+     id="defs22">
+    <inkscape:perspective
+       id="perspective24"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <marker
+       id="ArrowStart"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="10"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 10,0 0,5 10,10 z"
+         id="path3568" />
+    </marker>
+    <marker
+       id="ArrowEnd"
+       markerHeight="3"
+       markerUnits="strokeWidth"
+       markerWidth="4"
+       orient="auto"
+       refX="0"
+       refY="5"
+       viewBox="0 0 10 10">
+      <path
+         d="M 0,0 10,5 0,10 z"
+         id="path3565" />
+    </marker>
+    <inkscape:perspective
+       id="perspective2683"
+       inkscape:persp3d-origin="306.082 : 204.39034 : 1"
+       inkscape:vp_x="0 : 306.58551 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="612.164 : 306.58551 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5299"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <metadata
+     id="metadata10">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+        <dc:language>
+en</dc:language>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="wrapper"
+     inkscape:cx="0.61374335"
+     inkscape:cy="3.9329874"
+     inkscape:pageopacity="0.83137255"
+     inkscape:pageshadow="2"
+     inkscape:window-height="748"
+     inkscape:window-width="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:zoom="22.627417"
+     objecttolerance="10.0"
+     pagecolor="#7d77ff"
+     showgrid="false"
+     inkscape:window-maximized="1" />
+  <g
+     id="wrapper"
+     transform="matrix(0.02,0,0,0.02,-1.478075,-1.1469285)">
+    <path
+       d="m 322.96313,90.806136 -91.2572,0.0691 c -42.50274,-1e-5 -73.77286,34.131654 -73.77286,71.319524 l 0,263.40538 c 4e-5,36.02757 28.18623,65.57749 56.5304,70.14468 l -85.00292,127.50436 49.06673,0 60.78054,-89.11483 83.24066,0 0.069,0 0.069,0 83.24066,0 60.78054,89.11483 49.06673,0 -85.0027,-127.50436 c 28.34412,-4.56721 56.56495,-34.11709 56.56495,-70.14468 l 0,-263.40538 c -5e-5,-37.18783 -31.3047,-71.319524 -73.80741,-71.319524 l -90.56612,-0.0691 z m -36.14351,15.272884 35.72886,0 0.069,0 0.069,0 35.72887,0 c 6.46649,0 12.09391,5.4201 12.09391,11.88659 l 0,20.93974 c 0,6.4665 -5.31949,12.02481 -12.09391,12.02481 l -35.72887,0 -0.069,0 -0.069,0 -35.72886,0 c -6.77442,0 -12.09392,-5.55831 -12.09392,-12.02481 l 0,-20.93974 c 0,-6.46649 5.62741,-11.88658 12.09392,-11.88659 z m -52.66035,59.05284 88.38921,0 0.138,0 88.38921,0 c 24.01838,1e-5 36.7655,17.1963 36.76549,36.9037 l 0,47.40813 c 0.187,22.78669 -16.44223,36.76549 -36.76549,36.76549 l -88.38921,0 -0.069,0 -0.069,0 -88.38921,0 c -20.32327,0 -36.95271,-13.9788 -36.76549,-36.76549 l 0,-47.40813 c -1e-5,-19.7074 12.74709,-36.9037 36.76549,-36.9037 z m -4.00825,227.50374 c 17.866,-3e-5 32.37713,14.47658 32.37713,32.34257 0,17.86471 -14.51113,32.34258 -32.37713,32.34258 -17.86603,-3e-5 -32.34257,-14.47782 -32.34257,-32.34258 -5e-5,-17.86603 14.47658,-32.34257 32.34257,-32.34257 z m 184.17299,0 c 17.86597,-2e-5 32.34257,14.47657 32.34257,32.34257 -2e-5,17.86474 -14.47656,32.34259 -32.34257,32.34258 -17.86601,-3e-5 -32.34258,-14.47783 -32.34257,-32.34258 -2e-5,-17.86601 14.47659,-32.34257 32.34257,-32.34257 z"
+       id="path3578-9"
+       style="fill:none;stroke:#ffffff;stroke-width:62.5;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <g
+       id="g3574"
+       transform="matrix(1.2317123,0,0,1.2317123,-54.843627,-53.570605)"
+       style="fill:#000000;fill-opacity:1">
+      <g
+         id="g3576"
+         style="fill:#000000;fill-opacity:1">
+        <path
+           d="M 295.5625,77.1875 213.03125,77.25 c -38.43866,-1.1e-5 -66.71875,30.86801 -66.71875,64.5 l 0,238.21875 c 3e-5,32.58264 25.49108,59.30702 51.125,63.4375 l -76.875,115.3125 44.375,0 54.96875,-80.59375 75.28125,0 0.0625,0 0.0625,0 75.28125,0 54.96875,80.59375 44.375,0 -76.875,-115.3125 c 25.63388,-4.1305 51.15625,-30.85484 51.15625,-63.4375 l 0,-238.21875 c -4e-5,-33.63196 -28.31137,-64.5 -66.75,-64.5 L 295.5625,77.1875 z M 262.875,91 l 32.3125,0 0.0625,0 0.0625,0 32.3125,0 c 5.84817,0 10.9375,4.90183 10.9375,10.75 l 0,18.9375 c 0,5.84817 -4.81084,10.875 -10.9375,10.875 l -32.3125,0 -0.0625,0 -0.0625,0 -32.3125,0 c -6.12665,0 -10.9375,-5.02683 -10.9375,-10.875 l 0,-18.9375 c 0,-5.84817 5.08932,-10.749997 10.9375,-10.75 z m -47.625,53.40625 79.9375,0 0.125,0 79.9375,0 c 21.72176,1e-5 33.25001,15.552 33.25,33.375 l 0,42.875 c 0.16931,20.60784 -14.87004,33.25 -33.25,33.25 l -79.9375,0 -0.0625,0 -0.0625,0 -79.9375,0 c -18.37997,0 -33.41932,-12.64216 -33.25,-33.25 l 0,-42.875 c -1e-5,-17.823 11.52822,-33.375 33.25,-33.375 z m -3.625,205.75 c 16.15766,-3e-5 29.28125,13.09234 29.28125,29.25 0,16.1565 -13.12359,29.25 -29.28125,29.25 -16.15769,-2e-5 -29.25,-13.09346 -29.25,-29.25 -4e-5,-16.1577 13.09234,-29.25 29.25,-29.25 z m 166.5625,0 c 16.15764,-2e-5 29.25,13.09233 29.25,29.25 -2e-5,16.15652 -13.09233,29.25001 -29.25,29.25 -16.15768,-2e-5 -29.25001,-13.09347 -29.25,-29.25 -2e-5,-16.15768 13.09235,-29.25 29.25,-29.25 z"
+           id="path3578"
+           style="fill:#000000;fill-opacity:1;stroke:none"
+           transform="matrix(0.8977168,0,0,0.8977168,40.710583,46.777129)" />
+      </g>
+    </g>
+  </g>
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
diff --git a/icons/view_point.svg b/icons/view_point.svg
new file mode 100644 (file)
index 0000000..846886f
--- /dev/null
@@ -0,0 +1,155 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   height="11.339"
+   id="svg2"
+   inkscape:output_extension="org.inkscape.output.svg.inkscape"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="view_point.svg"
+   sodipodi:version="0.32"
+   version="1.0"
+   width="11.339">
+  <g
+     id="wrapper-4"
+     transform="matrix(0.01448815,0,0,0.01448815,1.2157583,1.502448)"
+     style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none">
+    <path
+       d="m 264.50523,235.49926 c -0.16658,0.78886 -42.10316,-33.79044 -85.91597,-79.79102 -9.98712,-10.48582 -67.67963,-60.980315 -14.46661,-91.66394 54.45289,-31.398539 69.60929,44.6705 73.90403,61.46637 15.13669,59.19661 26.64513,109.19973 26.47855,109.98859 z"
+       id="use6027-4"
+       sodipodi:nodetypes="cszss"
+       style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 236.59244,283.85543 c 0.59989,0.53869 -50.31495,19.56719 -112.05903,34.50991 -14.07455,3.40619 -86.650321,28.12212 -86.616605,-33.30353 0.03451,-62.85685 73.490425,-37.94816 90.183445,-33.26958 58.83412,16.48955 107.89231,31.52451 108.49219,32.0632 z"
+       id="use6029-3"
+       sodipodi:nodetypes="cszss"
+       style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 264.09276,334.21809 c 0.76647,-0.25017 -8.21179,53.35762 -26.14305,114.30092 -4.08743,13.89202 -18.97069,89.10244 -72.15001,58.36042 -54.41837,-31.45831 3.88114,-82.61866 16.27942,-94.73595 43.69743,-42.70706 81.24718,-77.67522 82.01364,-77.92539 z"
+       id="use6031-0"
+       sodipodi:nodetypes="cszss"
+       style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 321.10177,334.86324 c 0.16658,-0.78887 42.10315,33.79043 85.91597,79.791 9.98713,10.48583 67.67963,60.98032 14.4666,91.66395 C 367.03146,537.71673 351.87505,461.6477 347.58031,444.85183 332.44363,385.65521 320.93519,335.6521 321.10177,334.86324 z"
+       id="use6033-7"
+       sodipodi:nodetypes="cszss"
+       style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 348.14597,285.63847 c -0.5999,-0.5387 50.31494,-19.56718 112.05901,-34.50991 14.07456,-3.40619 86.65033,-28.12212 86.61661,33.30353 -0.0345,62.85685 -73.49042,37.94817 -90.18344,33.26959 -58.83412,-16.48956 -107.8923,-31.52452 -108.49218,-32.06321 z"
+       id="use6035-8"
+       sodipodi:nodetypes="cszss"
+       style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 323.1887,233.94211 c -0.76647,0.25017 8.2118,-53.35763 26.14307,-114.30092 4.08742,-13.89201 18.9707,-89.10244 72.15,-58.360413 54.41838,31.458315 -3.88114,82.618663 -16.27943,94.735943 -43.69743,42.70707 -81.24718,77.67522 -82.01364,77.92539 z"
+       id="use6039-6"
+       sodipodi:nodetypes="cszss"
+       style="fill:none;stroke:#ffffff;stroke-width:43.13870239;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+  </g>
+  <metadata
+     id="metadata2975">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>
+image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <cc:license
+           rdf:resource="http://web.resource.org/cc/PublicDomain" />
+        <dc:language>
+en</dc:language>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <sodipodi:namedview
+     bordercolor="#666666"
+     borderopacity="1.0"
+     gridtolerance="10.0"
+     guidetolerance="10.0"
+     id="base"
+     inkscape:current-layer="svg2"
+     inkscape:cx="6.3337443"
+     inkscape:cy="3.8533714"
+     inkscape:pageopacity="0.28235294"
+     inkscape:pageshadow="2"
+     inkscape:window-height="748"
+     inkscape:window-maximized="1"
+     inkscape:window-width="1016"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:zoom="83.42069"
+     objecttolerance="10.0"
+     pagecolor="#4841ff"
+     showgrid="false"
+     inkscape:snap-bbox="false"
+     inkscape:snap-bbox-midpoints="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3630"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       id="perspective3579"
+       inkscape:persp3d-origin="290 : 193.33333 : 1"
+       inkscape:vp_x="0 : 290 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="580 : 290 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective2826"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <g
+     id="wrapper"
+     transform="matrix(0.01448815,0,0,0.01448815,1.2080366,1.4684273)">
+    <path
+       d="m 264.50523,235.49926 c -0.16658,0.78886 -42.10316,-33.79044 -85.91597,-79.79102 -9.98712,-10.48582 -67.67963,-60.980315 -14.46661,-91.66394 54.45289,-31.398539 69.60929,44.6705 73.90403,61.46637 15.13669,59.19661 26.64513,109.19973 26.47855,109.98859 z"
+       id="use6027"
+       sodipodi:nodetypes="cszss"
+       style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:13.05850983;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 236.59244,283.85543 c 0.59989,0.53869 -50.31495,19.56719 -112.05903,34.50991 -14.07455,3.40619 -86.650321,28.12212 -86.616605,-33.30353 0.03451,-62.85685 73.490425,-37.94816 90.183445,-33.26958 58.83412,16.48955 107.89231,31.52451 108.49219,32.0632 z"
+       id="use6029"
+       sodipodi:nodetypes="cszss"
+       style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:13.05850983;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 264.09276,334.21809 c 0.76647,-0.25017 -8.21179,53.35762 -26.14305,114.30092 -4.08743,13.89202 -18.97069,89.10244 -72.15001,58.36042 -54.41837,-31.45831 3.88114,-82.61866 16.27942,-94.73595 43.69743,-42.70706 81.24718,-77.67522 82.01364,-77.92539 z"
+       id="use6031"
+       sodipodi:nodetypes="cszss"
+       style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:13.05850983;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 321.10177,334.86324 c 0.16658,-0.78887 42.10315,33.79043 85.91597,79.791 9.98713,10.48583 67.67963,60.98032 14.4666,91.66395 C 367.03146,537.71673 351.87505,461.6477 347.58031,444.85183 332.44363,385.65521 320.93519,335.6521 321.10177,334.86324 z"
+       id="use6033"
+       sodipodi:nodetypes="cszss"
+       style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:13.05850983;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 348.14597,285.63847 c -0.5999,-0.5387 50.31494,-19.56718 112.05901,-34.50991 14.07456,-3.40619 86.65033,-28.12212 86.61661,33.30353 -0.0345,62.85685 -73.49042,37.94817 -90.18344,33.26959 -58.83412,-16.48956 -107.8923,-31.52452 -108.49218,-32.06321 z"
+       id="use6035"
+       sodipodi:nodetypes="cszss"
+       style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:13.02788831;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+    <path
+       d="m 323.1887,233.94211 c -0.76647,0.25017 8.2118,-53.35763 26.14307,-114.30092 4.08742,-13.89201 18.9707,-89.10244 72.15,-58.360413 54.41838,31.458315 -3.88114,82.618663 -16.27943,94.735943 -43.69743,42.70707 -81.24718,77.67522 -82.01364,77.92539 z"
+       id="use6039"
+       sodipodi:nodetypes="cszss"
+       style="fill:#ff0000;fill-opacity:1;stroke:#ff0000;stroke-width:13.05850983;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-opacity:1;stroke-dasharray:none" />
+  </g>
+  <!-- 
+       Generated using the Perl SVG Module V2.50
+       by Ronan Oger
+       Info: http://www.roitsystems.com/
+ -->
+</svg>
diff --git a/icons/wetland.svg b/icons/wetland.svg
new file mode 100644 (file)
index 0000000..de6e907
--- /dev/null
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="51"
+   height="40"
+   id="svg3656"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="wetland.svg">
+  <defs
+     id="defs3658">
+    <inkscape:perspective
+       sodipodi:type="inkscape:persp3d"
+       inkscape:vp_x="0 : 526.18109 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_z="744.09448 : 526.18109 : 1"
+       inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+       id="perspective3664" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="1.4"
+     inkscape:cx="89.428571"
+     inkscape:cy="-29.500003"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1016"
+     inkscape:window-height="748"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1">
+    <inkscape:grid
+       type="xygrid"
+       id="grid3666"
+       empspacing="5"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata3661">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1"
+     transform="translate(-159.5,-462.86218)">
+    <path
+       style="fill:none;stroke:#3030a5;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+       d="m 159.5,472.86218 30,0"
+       id="path3668" />
+    <path
+       style="fill:none;stroke:#3030a5;stroke-width:3;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;fill-opacity:1;stroke-miterlimit:4;stroke-dasharray:none"
+       d="m 179.5,492.86218 30,0"
+       id="path3670" />
+  </g>
+</svg>
diff --git a/leo.c b/leo.c
new file mode 100644 (file)
index 0000000..10092d6
--- /dev/null
+++ b/leo.c
@@ -0,0 +1,253 @@
+/*
+ *     Hic Est Leo -- Main Program
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/conf.h>
+#include <ucw/opt.h>
+
+#include <stdio.h>
+
+#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 (file)
index 0000000..fa4123d
--- /dev/null
+++ b/leo.h
@@ -0,0 +1,20 @@
+/*
+ *     Hic Est Leo
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/clists.h>
+
+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 (file)
index 0000000..57d7861
--- /dev/null
+++ b/map.c
@@ -0,0 +1,135 @@
+/*
+ *     Hic Est Leo -- Global Map Operations
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/conf.h>
+#include <ucw/gary.h>
+#include <ucw/mempool.h>
+
+#include <stdio.h>
+#include <math.h>
+
+#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 (file)
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 (file)
index 0000000..632e8d5
--- /dev/null
+++ b/map.h
@@ -0,0 +1,30 @@
+/*
+ *     Hic Est Leo -- Global Map Operations
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (file)
index 0000000..12529f6
--- /dev/null
+++ b/osm.c
@@ -0,0 +1,581 @@
+/*
+ *     Hic Est Leo -- OSM Data Representation
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/gary.h>
+#include <ucw/mempool.h>
+#include <ucw/stkstring.h>
+#include <ucw/strtonum.h>
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <proj_api.h>
+
+#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 <ucw/hashtable.h>
+
+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) : "<none>"), 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 <ucw/hashtable.h>
+
+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<OSM_TYPE_MAX; i++)
+    {
+      clist_init(&osm_obj_list[i]);
+      osm_id_hash_init(&osm_id_hash[i]);
+    }
+  osm_tag_init();
+}
+
+void osm_dump(void)
+{
+  osm_node_dump_all();
+  osm_way_dump_all();
+  osm_relation_dump_all();
+  osm_multipolygon_dump_all();
+}
diff --git a/osm.h b/osm.h
new file mode 100644 (file)
index 0000000..4eb90ef
--- /dev/null
+++ b/osm.h
@@ -0,0 +1,164 @@
+/*
+ *     Hic Est Leo -- Representation of OSM Data
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (file)
index 0000000..30631f5
--- /dev/null
@@ -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 (file)
index 0000000..383525e
--- /dev/null
+++ b/shp.c
@@ -0,0 +1,113 @@
+/*
+ *     Hic Est Leo -- Reading ESRI Shape Files
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ *
+ *     FIXME: Currently, this parser handles only the subset
+ *     of shape file syntax which is used by gdal_contours.
+ */
+
+#undef LOCAL_DEBUG
+
+#include <ucw/lib.h>
+#include <ucw/conf.h>
+#include <ucw/fastbuf.h>
+#include <ucw/gary.h>
+#include <ucw/unaligned.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+
+#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 (file)
index 0000000..febd395
--- /dev/null
+++ b/shp.h
@@ -0,0 +1,12 @@
+/*
+ *     Hic Est Leo -- Reading ESRI Shape Files
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (file)
index 0000000..d82c712
--- /dev/null
+++ b/style.c
@@ -0,0 +1,271 @@
+/*
+ *     Hic Est Leo -- Styling
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/mempool.h>
+
+#include <stdio.h>
+
+#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 <ucw/hashtable.h> 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 <ucw/hashtable.h>
+
+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; i<r->num_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 (file)
index 0000000..b8509f1
--- /dev/null
+++ b/style.h
@@ -0,0 +1,108 @@
+/*
+ *     Hic Est Leo -- Styling
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (file)
index 0000000..34555e7
--- /dev/null
@@ -0,0 +1,237 @@
+/*
+ *     Hic Est Leo -- SVG Icon Embedder
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/fastbuf.h>
+#include <xml/xml.h>
+
+#include <fcntl.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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 <ucw/hashtable.h>
+
+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 <svg>", 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 (file)
index 0000000..ec3057a
--- /dev/null
+++ b/svg.c
@@ -0,0 +1,365 @@
+/*
+ *     Hic Est Leo -- SVG Generator
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/gary.h>
+#include <ucw/mempool.h>
+#include <xml/xml.h>
+
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#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, "<?xml version=\"1.0\" standalone=\"no\"?>");
+  bputsn(svg->fb, "<!DOCTYPE svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">");
+
+  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; i<e->indent; 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, "</%s>", 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, "<!-- ");
+      bputs(svg->fb, e->name);
+      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, "&quot;");
+       break;
+      case '\'':
+       bputs(svg->fb, "&apos;");
+       break;
+      case '<':
+       bputs(svg->fb, "&lt;");
+       break;
+      case '>':
+       bputs(svg->fb, "&gt;");
+       break;
+      case '&':
+       bputs(svg->fb, "&amp;");
+       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 (file)
index 0000000..09f8bd3
--- /dev/null
+++ b/svg.h
@@ -0,0 +1,112 @@
+/*
+ *     Hic Est Leo -- SVG Output
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (file)
index 0000000..4944fcd
--- /dev/null
@@ -0,0 +1,378 @@
+/*
+ *     Hic Est Leo -- Line and Area Symbolizer
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/fastbuf.h>
+#include <ucw/mempool.h>
+
+#include <math.h>
+#include <stdio.h>
+
+#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 (file)
index 0000000..c7c2be0
--- /dev/null
@@ -0,0 +1,153 @@
+/*
+ *     Hic Est Leo -- Point and Icon Symbolizer
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+
+#include <stdio.h>
+
+#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 (file)
index 0000000..2666acd
--- /dev/null
@@ -0,0 +1,562 @@
+/*
+ *     Hic Est Leo -- Text Symbolizer
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/stkstring.h>
+
+#include <stdio.h>
+#include <ft2build.h>
+#include FT_FREETYPE_H
+#include <pango/pangoft2.h>
+
+#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 <ucw/hashtable.h>
+
+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 (file)
index 0000000..00d8941
--- /dev/null
+++ b/sym.c
@@ -0,0 +1,148 @@
+/*
+ *     Hic Est Leo -- Symbols
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/gary.h>
+#include <ucw/mempool.h>
+#include <ucw/stkstring.h>
+
+#include <stdio.h>
+
+#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 <ucw/sorter/array-simple.h>
+
+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 (file)
index 0000000..7c4e97b
--- /dev/null
+++ b/sym.h
@@ -0,0 +1,150 @@
+/*
+ *     Hic Est Leo -- Symbolizers
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#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 (executable)
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 (file)
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 (file)
index 0000000..98f2f8b
--- /dev/null
+++ b/xml.c
@@ -0,0 +1,229 @@
+/*
+ *     Hic Est Leo -- OSM XML Parser
+ *
+ *     (c) 2014 Martin Mares <mj@ucw.cz>
+ */
+
+#include <ucw/lib.h>
+#include <ucw/fastbuf.h>
+#include <xml/xml.h>
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#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);
+}