/*

	Support for "OpenStreetMap" data files (.xml)

	Copyright (C) 2008 Olaf Klein, o.b.klein@gpsbabel.org

	This program is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program; if not, write to the Free Software
	Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111 USA

*/

#include "defs.h"
#include "avltree.h"
#include "xmlgeneric.h"

static char* opt_tag, *opt_tagnd, *created_by;

static arglist_t osm_args[] = {
  { "tag", &opt_tag, 	"Write additional way tag key/value pairs", NULL, ARGTYPE_STRING, ARG_NOMINMAX },
  { "tagnd", &opt_tagnd,	"Write additional node tag key/value pairs", NULL, ARGTYPE_STRING, ARG_NOMINMAX },
  { "created_by", &created_by, "Use this value as custom created_by value","GPSBabel", ARGTYPE_STRING, ARG_NOMINMAX },
  ARG_TERMINATOR
};

#define MYNAME "osm"

static avltree_t* waypoints;	/* AVL tree */

static avltree_t* keys = NULL;
static avltree_t* values = NULL;
static avltree_t* icons = NULL;

static gbfile* fout;
static int node_id;
static int skip_rte;

#if HAVE_LIBEXPAT
static route_head* rte;
static waypoint* wpt;
static int wpt_loaded, rte_loaded;

static xg_callback	osm_node, osm_node_tag, osm_node_end;
static xg_callback	osm_way, osm_way_nd, osm_way_tag, osm_way_end;

static
xg_tag_mapping osm_map[] = {
  { osm_node,	cb_start,	"/osm/node" },
  { osm_node_tag,	cb_start,	"/osm/node/tag" },
  { osm_node_end,	cb_end,		"/osm/node" },
  { osm_way,	cb_start,	"/osm/way" },
  { osm_way_nd,	cb_start,	"/osm/way/nd" },
  { osm_way_tag,	cb_start,	"/osm/way/tag" },
  { osm_way_end,	cb_end,		"/osm/way" },
  { NULL,	(xg_cb_type)0,		NULL }
};
#endif // HAVE_LIBEXPAT

static const char* osm_features[] = {
  "- dummy -",	/*  0 */
  "aeroway",	/*  1 */
  "amenity",	/*  2 */
  "building",	/*  3 */
  "cycleway",	/*  4 */
  "railway",	/*  5 */
  "highway",	/*  6 */
  "historic",	/*  7 */
  "landuse",	/*  8 */
  "leisure",	/*  9 */
  "man_made",	/* 10 */
  "military",	/* 11 */
  "natural",	/* 12 */
  "place",	/* 13 */
  "power",	/* 14 */
  "shop",		/* 15 */
  "sport",	/* 16 */
  "tourism",	/* 17 */
  "waterway",	/* 18 */
  "aerialway",	/* 19 */
  NULL
};

typedef struct osm_icon_mapping_s {
  const int key;
  const char* value;
  const char* icon;
} osm_icon_mapping_t;


/* based on <http://wiki.openstreetmap.org/index.php/Map_Features> */

static osm_icon_mapping_t osm_icon_mappings[] = {

  /* cycleway ...*/

  /* highway ...*/

//	{ 6, "mini_roundabout",		"?" },
//	{ 6, "stop",			"?" },
//	{ 6, "traffic_signals",		"?" },
//	{ 6, "crossing",		"?" },
//	{ 6, "gate",			"?" },
//	{ 6, "stile",			"?" },
//	{ 6, "cattle_grid",		"?" },
//	{ 6, "toll_booth",		"?" },
//	{ 6, "incline",			"?" },
//	{ 6, "incline_steep",		"?" },
//	{ 6, "viaduct",			"?" },
//	{ 6, "motorway_junction",	"?" },
//	{ 6, "services",		"?" },
//	{ 6, "ford",			"?" },
//	{ 6, "bus_stop",		"?" },
//	{ 6, "turning_circle",		"?" },
//	{ 6, "User Defined",		"?" },

  /* waterway ... */

  { 18, "dock",			"Dock" },
//	{ 18, "lock_gate",		"?" },
//	{ 18, "turning_point",		"?" },
//	{ 18, "aqueduct",		"?" },
//	{ 18, "boatyard",		"?" },
//	{ 18, "water_point",		"?" },
//	{ 18, "waste_disposal",		"?" },
//	{ 18, "mooring",		"?" },
//	{ 18, "weir",			"?" },
//	{ 18, "User Defined",		"?" },

  /* railway ... */

//	{ 5, "station",			"?" },
//	{ 5, "halt",			"?" },
//	{ 5, "tram_stop",		"?" },
//	{ 5, "viaduct",			"?" },
  { 5, "crossing",		"Crossing" },
//	{ 5, "level_crossing",		"?" },
//	{ 5, "subway_entrance",		"?" },
//	{ 5, "turntable",		"?" },
//	{ 5, "User Defined",		"?" },

  /* aeroway ... */

  { 1, "aerodrome",		"Airport" },
  { 1, "terminal",		"Airport" },
  { 1, "helipad",			"Heliport" },
//	{ 1, "User Defined",		"?" },

  /* aerialway ... */

//	{ 19, "User Defined",		"?" },

  /* power ... */

//	{ 14, "tower",			"?" },
//	{ 14, "sub_station",		"?" },
//	{ 14, "generator",		"?" },

  /* man_made ... */

//	{ 10, "works",			"?" },
//	{ 10, "beacon",			"?" },
//	{ 10, "survey_point",		"?" },
//	{ 10, "power_wind",		"?" },
//	{ 10, "power_hydro",		"?" },
//	{ 10, "power_fossil",		"?" },
//	{ 10, "power_nuclear",		"?" },
//	{ 10, "tower",			"?" },
//	{ 10, "water_tower",		"?" },
//	{ 10, "gasometer",		"?" },
//	{ 10, "reservoir_covered",	"?" },
//	{ 10, "lighthouse",		"?" },
//	{ 10, "windmill",		"?" },
//	{ 10, "wastewater_plant",	"?" },
//	{ 10, "crane",			"?" },
//	{ 10, "User Defined",		"?" },

  /* building ... */

  { 3, "yes",			"Building" },
//	{ 3, "User Defined",		"?" },

  /* leisure ... */

//	{ 9, "sports_centre",		"?" },
  { 9, "golf_course",		"Golf Course" },
  { 9, "stadium",			"Stadium" },
//	{ 9, "track",			"?" },
//	{ 9, "pitch",			"?" },
//	{ 9, "water_park",		"?" },
  { 9, "marina",			"Marina" },
//	{ 9, "slipway",			"?" },
  { 9, "fishing",			"Fishing Area" },
//	{ 9, "nature_reserve",		"?" },
  { 9, "park",			"Park" },
//	{ 9, "playground",		"?" },
//	{ 9, "garden",			"?" },
//	{ 9, "common",			"?" },
//	{ 9, "User Defined",		"?" },

  /* amenity ... */

  { 2, "pub",			"Bar" },
//	{ 2, "biergarten",		"?" },
  { 2, "nightclub",		"Bar" },
//	{ 2, "cafe",			"?" },
  { 2, "restaurant",		"Restaurant" },
  { 2, "fast_food",		"Fast Food" },
  { 2, "parking",			"Parking Area" },
//	{ 2, "bicycle_parking",		"?" },
//	{ 2, "bicycle_rental",		"?" },
  { 2, "car_rental",		"Car Rental" },
//	{ 2, "car_sharing",		"?" },
//	{ 2, "taxi",			"?" },
  { 2, "fuel",			"Gas Station" },
  { 2, "telephone",		"Telephone" },
  { 2, "toilets",			"Restroom" },
//	{ 2, "recycling",		"?" },
//	{ 2, "public_building",		"?" },
  { 2, "townhall",		"City Hall" },
//	{ 2, "place_of_worship",	"?" },
//	{ 2, "grave_yard",		"?" },
  { 2, "post_office",		"Post Office" },
//	{ 2, "post_box",		"?" },
  { 2, "school",			"School" },
//	{ 2, "university",		"?" },
//	{ 2, "college",			"?" },
  { 2, "pharmacy",		"Pharmacy" },
  { 2, "hospital",		"Medical Facility" },
//	{ 2, "library",			"?" },
  { 2, "police",			"Police Station" },
//	{ 2, "fire_station",		"?" },
//	{ 2, "bus_station",		"?" },
//	{ 2, "theatre",			"?" },
//	{ 2, "cinema",			"?" },
//	{ 2, "arts_centre",		"?" },
//	{ 2, "courthouse",		"?" },
//	{ 2, "prison",			"?" },
  { 2, "bank",			"Bank" },
//	{ 2, "bureau_de_change",	"?" },
//	{ 2, "atm",			"?" },
//	{ 2, "fountain",		"?" },
//	{ 2, "User Defined",		"?" },

  /* shop ... */

//	{ 15, "supermarket",		"?" },
  { 15, "convenience",		"Convenience Store" },
//	{ 15, "butcher",		"?" },
//	{ 15, "bicycle",		"?" },
//	{ 15, "doityourself",		"?" },
//	{ 15, "dry_cleaning",		"?" },
//	{ 15, "laundry",		"?" },
//	{ 15, "outdoor",		"?" },
//	{ 15, "kiosk",			"?" },
//	{ 15, "User Defined",		"?" },

  /* tourism ... */

  { 17, "information",		"Information" },
  { 17, "hotel",			"Hotel" },
  { 17, "motel",			"Lodging" },
  { 17, "guest_house",		"Lodging" },
  { 17, "hostel",			"Lodging" },
  { 17, "camp_site",		"Campground" },
  { 17, "caravan_site",		"RV Park" },
  { 17, "picnic_site",		"Picnic Area" },
  { 17, "viewpoint",		"Scenic Area" },
//	{ 17, "theme_park",		"?" },
//	{ 17, "attraction",		"?" },
  { 17, "zoo",			"Zoo" },
//	{ 17, "artwork",		"?" },
  { 17, "museum",			"Museum" },
//	{ 17, "User Defined",		"?" },

  /* historic ... */

//	{ 7, "castle",			"?" },
//	{ 7, "monument",		"?" },
//	{ 7, "memorial",		"?" },
//	{ 7, "archaeological_site",	"?" },
//	{ 7, "ruins",			"?" },
//	{ 7, "battlefield",		"?" },
//	{ 7, "User Defined",		"?" },

  /* landuse ... */

//	{ 8, "farm",			"?" },
//	{ 8, "quarry",			"?" },
//	{ 8, "landfill",		"?" },
//	{ 8, "basin",			"?" },
//	{ 8, "reservoir",		"?" },
  { 8, "forest",			"Forest" },
//	{ 8, "allotments",		"?" },
//	{ 8, "residential",		"?" },
//	{ 8, "retail",			"?" },
//	{ 8, "commercial",		"?" },
//	{ 8, "industrial",		"?" },
//	{ 8, "brownfield",		"?" },
//	{ 8, "greenfield",		"?" },
//	{ 8, "railway",			"?" },
//	{ 8, "construction",		"?" },
  { 8, "military",		"Military" },
  { 8, "cemetery",		"Cemetery" },
//	{ 8, "village_green",		"?" },
//	{ 8, "recreation_ground",	"?" },
//	{ 8, "User Defined",		"?" },

  /* military ... */

//	{ 11, "airfield",		"?" },
//	{ 11, "bunker",			"?" },
//	{ 11, "barracks",		"?" },
//	{ 11, "danger_area",		"?" },
//	{ 11, "range",			"?" },
//	{ 11, "naval_base",		"?" },
//	{ 11, "User Defined",		"?" },

  /* natural ... */

//	{ 12, "spring",			"?" },
//	{ 12, "peak",			"?" },
//	{ 12, "glacier",		"?" },
//	{ 12, "volcano",		"?" },
//	{ 12, "cliff",			"?" },
//	{ 12, "scree",			"?" },
//	{ 12, "scrub",			"?" },
//	{ 12, "fell",			"?" },
//	{ 12, "heath",			"?" },
//	{ 12, "wood",			"?" },
//	{ 12, "marsh",			"?" },
//	{ 12, "water",			"?" },
//	{ 12, "coastline",		"?" },
//	{ 12, "mud",			"?" },
  { 12, "beach",			"Beach" },
//	{ 12, "bay",			"?" },
//	{ 12, "land",			"?" },
//	{ 12, "cave_entrance",		"?" },
//	{ 12, "User Defined",		"?" },

  /* sport ... */

//	{ 16, "10pin",			"?" },
//	{ 16, "athletics",		"?" },
//	{ 16, "australian_football",	"?" },
//	{ 16, "baseball",		"?" },
//	{ 16, "basketball",		"?" },
//	{ 16, "boules",			"?" },
//	{ 16, "bowls",			"?" },
//	{ 16, "climbing",		"?" },
//	{ 16, "cricket",		"?" },
//	{ 16, "cricket_nets",		"?" },
//	{ 16, "croquet",		"?" },
//	{ 16, "cycling",		"?" },
//	{ 16, "dog_racing",		"?" },
//	{ 16, "equestrian",		"?" },
//	{ 16, "football",		"?" },
//	{ 16, "golf",			"?" },
//	{ 16, "gymnastics",		"?" },
//	{ 16, "hockey",			"?" },
//	{ 16, "horse_racing",		"?" },
//	{ 16, "motor",			"?" },
//	{ 16, "multi",			"?" },
//	{ 16, "pelota",			"?" },
//	{ 16, "racquet",		"?" },
//	{ 16, "rugby",			"?" },
//	{ 16, "skating",		"?" },
//	{ 16, "skateboard",		"?" },
//	{ 16, "soccer",			"?" },
  { 16, "swimming",		"Swimming Area" },
  { 16, "skiing",			"Skiing Area" },
//	{ 16, "table_tennis",		"?" },
//	{ 16, "tennis",			"?" },
//	{ 16, "orienteering",		"?" },
//	{ 16, "User Defined",		"?" },

  /* place ... */

//	{ 13, "continent",		"?" },
//	{ 13, "country",		"?" },
//	{ 13, "state",			"?" },
//	{ 13, "region",			"?" },
//	{ 13, "county",			"?" },
  { 13, "city",			"City (Large)" },
  { 13, "town",			"City (Medium)" },
  { 13, "village",		"City (Small)" },
//	{ 13, "hamlet",			"?" },
//	{ 13, "suburb",			"?" },
//	{ 13, "locality",		"?" },
//	{ 13, "island",			"?" },
//	{ 13, "User Defined",		"?" },

  { -1, NULL, NULL }
};

#if ! HAVE_LIBEXPAT

void
osm_rd_init(const char* fname)
{
  fatal(MYNAME ": This build excluded \" MYNAME \" support because expat was not installed.\n");
}

void
osm_read(void)
{
}

#else


/*******************************************************************************/
/*                                   READER                                    */
/*-----------------------------------------------------------------------------*/

static void
osm_features_init(void)
{
  int i;

  keys = avltree_init(AVLTREE_STATIC_KEYS, MYNAME);
  values = avltree_init(0, MYNAME);

  /* the first of osm_features is a place holder */
  for (i = 1; osm_features[i]; i++) {
    avltree_insert(keys, osm_features[i], gb_int2ptr(i));
  }

  for (i = 0; osm_icon_mappings[i].value; i++) {
    char buff[128];

    buff[0] = osm_icon_mappings[i].key;
    strncpy(&buff[1], osm_icon_mappings[i].value, sizeof(buff) - 1);
    avltree_insert(values, buff, (const void*)&osm_icon_mappings[i]);
  }
}


static char
osm_feature_ikey(const char* key)
{
  int result;
  const void* p;

  if (avltree_find(keys, key, &p)) {
    result = gb_ptr2int(p);
  } else {
    result = -1;
  }

  return result;
}


static char*
osm_feature_symbol(const int ikey, const char* value)
{
  char* result;
  char buff[128];
  osm_icon_mapping_t* data;

  buff[0] = ikey;
  strncpy(&buff[1], value, sizeof(buff) - 1);

  if (avltree_find(values, buff, (const void**)&data)) {
    result = xstrdup(data->icon);
  } else {
    xasprintf(&result, "%s:%s", osm_features[ikey], value);
  }

  return result;
}


static char*
osm_strip_html(const char* str)
{
  utf_string utf;
  utf.is_html = 1;
  utf.utfstring = (char*)str;

  return strip_html(&utf);	// util.c
}


static void
osm_node_end(const char* args, const char** unused)
{
  if (wpt) {
    if (wpt->wpt_flags.fmt_use) {
      waypt_add(wpt);
    } else {
      waypt_free(wpt);
    }
    wpt = NULL;
  }
}


static void
osm_node(const char* args, const char** attrv)
{
  const char** avp = &attrv[0];

  wpt = waypt_new();

  while (*avp) {
    if (strcmp(avp[0], "id") == 0) {
      xasprintf(&wpt->description, "osm-id %s", avp[1]);
      if (! avltree_insert(waypoints, avp[1], (void*)wpt)) {
        warning(MYNAME ": Duplicate osm-id %s!\n", avp[1]);
      } else {
        wpt->wpt_flags.fmt_use = 1;
      }
    } else if (strcmp(avp[0], "user") == 0) ;
    else if (strcmp(avp[0], "lat") == 0) {
      wpt->latitude = atof(avp[1]);
    } else if (strcmp(avp[0], "lon") == 0) {
      wpt->longitude = atof(avp[1]);
    } else if (strcmp(avp[0], "timestamp") == 0) {
      wpt->creation_time = xml_parse_time(avp[1], &wpt->microseconds);
    }

    avp += 2;
  }
}


static void
osm_node_tag(const char* args, const char** attrv)
{
  const char** avp = &attrv[0];
  const char* key = "", *value = "";
  char* str;
  signed char ikey;

  while (*avp) {
    if (strcmp(avp[0], "k") == 0) {
      key = avp[1];
    } else if (strcmp(avp[0], "v") == 0) {
      value = avp[1];
    }
    avp+=2;
  }

  str = osm_strip_html(value);

  if (strcmp(key, "name") == 0) {
    if (! wpt->shortname) {
      wpt->shortname = xstrdup(str);
    }
  } else if (strcmp(key, "name:en") == 0) {
    if (wpt->shortname) {
      xfree(wpt->shortname);
    }
    wpt->shortname = xstrdup(str);
  } else if ((ikey = osm_feature_ikey(key)) >= 0) {
    wpt->icon_descr = osm_feature_symbol(ikey, value);
    wpt->wpt_flags.icon_descr_is_dynamic = 1;
  } else if (strcmp(key, "note") == 0) {
    if (wpt->notes) {
      char* tmp;
      xasprintf(&tmp, "%s; %s", wpt->notes, str);
      xfree(wpt->notes);
      wpt->notes = tmp;
    } else {
      wpt->notes = xstrdup(str);
    }
  } else if (strcmp(key, "gps:hdop") == 0) {
    wpt->hdop = atof(str);
  } else if (strcmp(key, "gps:vdop") == 0) {
    wpt->vdop = atof(str);
  } else if (strcmp(key, "gps:pdop") == 0) {
    wpt->pdop = atof(str);
  } else if (strcmp(key, "gps:sat") == 0) {
    wpt->sat = atoi(str);
  } else if (strcmp(key, "gps:fix") == 0) {
    if (strcmp(str, "2d") == 0) {
      wpt->fix = fix_2d;
    } else if (strcmp(str, "3d") == 0) {
      wpt->fix = fix_3d;
    } else if (strcmp(str, "dgps") == 0) {
      wpt->fix = fix_dgps;
    } else if (strcmp(str, "pps") == 0) {
      wpt->fix = fix_pps;
    } else if (strcmp(str, "none") == 0) {
      wpt->fix = fix_none;
    }
  }

  xfree(str);
}


static void
osm_way(const char* args, const char** attrv)
{
  const char** avp = &attrv[0];

  rte = route_head_alloc();

  while (*avp) {
    if (strcmp(avp[0], "id") == 0) {
      xasprintf(&rte->rte_desc, "osm-id %s", avp[1]);
    }
    avp += 2;
  }
}

static void
osm_way_nd(const char* args, const char** attrv)
{
  const char** avp = &attrv[0];

  while (*avp) {
    if (strcmp(avp[0], "ref") == 0) {
      waypoint* tmp;
      if (avltree_find(waypoints, avp[1], (const void**)&tmp)) {
        tmp = waypt_dupe(tmp);
        route_add_wpt(rte, tmp);
      } else {
        warning(MYNAME ": Way reference id \"%s\" wasn't listed under nodes!\n", avp[1]);
      }
    }
    avp += 2;
  }
}

static void
osm_way_tag(const char* args, const char** attrv)
{
  const char** avp = &attrv[0];
  const char* key = "", *value = "";
  char* str;

  while (*avp) {
    if (strcmp(avp[0], "k") == 0) {
      key = avp[1];
    } else if (strcmp(avp[0], "v") == 0) {
      value = avp[1];
    }
    avp += 2;
  }

  str = osm_strip_html(value);

  if (strcmp(key, "name") == 0) {
    if (! rte->rte_name) {
      rte->rte_name = xstrdup(str);
    }
  } else if (strcmp(key, "name:en") == 0) {
    if (rte->rte_name) {
      xfree(rte->rte_name);
    }
    rte->rte_name = xstrdup(str);
  }

  xfree(str);
}

static void
osm_way_end(const char* args, const char** unused)
{
  if (rte) {
    route_add_head(rte);
    rte = NULL;
  }
}

static void
osm_rd_init(const char* fname)
{
  wpt = NULL;
  rte = NULL;
  wpt_loaded = 0;
  rte_loaded = 0;

  waypoints = avltree_init(0, MYNAME);
  if (! keys) {
    osm_features_init();
  }

  xml_init(fname, osm_map, NULL);
}

static void
osm_read(void)
{
  xml_read();
}

#endif

static void
osm_rd_deinit(void)
{
  xml_deinit();
  avltree_done(waypoints);
}

/*******************************************************************************/
/*                                   WRITER                                    */
/*-----------------------------------------------------------------------------*/

static void
osm_init_icons(void)
{
  int i;

  if (icons) {
    return;
  }

  icons = avltree_init(AVLTREE_STATIC_KEYS, MYNAME);

  for (i = 0; osm_icon_mappings[i].value; i++) {
    avltree_insert(icons, osm_icon_mappings[i].icon, (const void*)&osm_icon_mappings[i]);
  }
}

static void
osm_write_tag(const char* key, const char* value)
{
  if (value && *value) {
    char* str = xml_entitize(value);
    gbfprintf(fout, "    <tag k='%s' v='%s'/>\n", key, str);
    xfree(str);
  }
}

static void
osm_disp_feature(const waypoint* wpt)
{
  osm_icon_mapping_t* map;

  if (avltree_find(icons, wpt->icon_descr, (const void**) &map)) {
    osm_write_tag(osm_features[map->key], map->value);
  }
}

static void
osm_write_opt_tag(const char* atag)
{
  char* tag, *cin, *ce;

  if (!atag) {
    return;
  }

  tag = cin = xstrdup(atag);
  ce = cin + strlen(cin);

  while (cin < ce) {
    char* sc, *dp;

    if ((sc = strchr(cin, ';'))) {
      *sc = '\0';
    }

    if ((dp = strchr(cin, ':'))) {
      *dp++ = '\0';
      osm_write_tag(cin, dp);
    }
    cin += strlen(cin) + 1;
  }

  xfree(tag);
}

static void
osm_release_ids(const waypoint* wpt)
{
  if (wpt && wpt->extra_data) {
    waypoint* tmp = (waypoint*)wpt;
    xfree(tmp->extra_data);
    tmp->extra_data = NULL;
  }
}

static void
osm_waypt_disp(const waypoint* wpt)
{
  char* buff;

  xasprintf(&buff, "%s\01%f\01%f", (wpt->shortname) ? wpt->shortname : "",
            wpt->latitude, wpt->longitude);

  if (avltree_insert(waypoints, buff, (const void*) wpt)) {
    int* id;

    id = (int*) xmalloc(sizeof(*id));
    *id = --node_id;
    ((waypoint*)(wpt))->extra_data = id;

    gbfprintf(fout, "  <node id='%d' visible='true' lat='%0.7f' lon='%0.7f'", *id, wpt->latitude, wpt->longitude);
    if (wpt->creation_time) {
      char time_string[64];
      xml_fill_in_time(time_string, wpt->creation_time, wpt->microseconds, XML_LONG_TIME);
      gbfprintf(fout, " timestamp='%s'", time_string);
    }
    gbfprintf(fout, ">\n");

    if (wpt->hdop) {
      gbfprintf(fout, "    <tag k='gps:hdop' v='%f' />\n", wpt->hdop);
    }
    if (wpt->vdop) {
      gbfprintf(fout, "    <tag k='gps:vdop' v='%f' />\n", wpt->vdop);
    }
    if (wpt->pdop) {
      gbfprintf(fout, "    <tag k='gps:pdop' v='%f' />\n", wpt->pdop);
    }
    if (wpt->sat > 0) {
      gbfprintf(fout, "    <tag k='gps:sat' v='%d' />\n", wpt->sat);
    }

    switch (wpt->fix) {
    case fix_2d:
      gbfprintf(fout, "    <tag k='gps:fix' v='2d' />\n");
      break;
    case fix_3d:
      gbfprintf(fout, "    <tag k='gps:fix' v='3d' />\n");
      break;
    case fix_dgps:
      gbfprintf(fout, "    <tag k='gps:fix' v='dgps' />\n");
      break;
    case fix_pps:
      gbfprintf(fout, "    <tag k='gps:fix' v='pps' />\n");
      break;
    case fix_none:
      gbfprintf(fout, "    <tag k='gps:fix' v='none' />\n");
      break;
    case fix_unknown:
    default:
      break;
    }

    if (strlen(created_by) !=0) {
      gbfprintf(fout, "    <tag k='created_by' v='%s",created_by);
      if (gpsbabel_time != 0)
        if (strcmp("GPSBabel",created_by)==0) {
          gbfprintf(fout, "-%s", gpsbabel_version);
        }
      gbfprintf(fout, "'/>\n");
    }

    osm_write_tag("name", wpt->shortname);
    osm_write_tag("note", (wpt->notes) ? wpt->notes : wpt->description);
    if (wpt->icon_descr) {
      osm_disp_feature(wpt);
    }

    osm_write_opt_tag(opt_tagnd);

    gbfprintf(fout, "  </node>\n");
  }

  xfree(buff);
}

static void
osm_rte_disp_head(const route_head* rte)
{
  skip_rte = (rte->rte_waypt_ct <= 0);

  if (skip_rte) {
    return;
  }

  gbfprintf(fout, "  <way id='%d' visible='true'>\n", --node_id);
}

static void
osm_rtept_disp(const waypoint* wpt_ref)
{
  char* buff;
  waypoint* wpt;

  if (skip_rte) {
    return;
  }

  xasprintf(&buff, "%s\01%f\01%f", (wpt_ref->shortname) ? wpt_ref->shortname : "",
            wpt_ref->latitude, wpt_ref->longitude);

  if (avltree_find(waypoints, buff, (const void**) &wpt)) {
    int* id = (int*) wpt->extra_data;
    gbfprintf(fout, "    <nd ref='%d'/>\n", *id);
  }

  xfree(buff);
}

static void
osm_rte_disp_trail(const route_head* rte)
{
  if (skip_rte) {
    return;
  }

  if (strlen(created_by) !=0) {
    gbfprintf(fout, "    <tag k='created_by' v='%s",created_by);
    if (gpsbabel_time != 0)
      if (strcmp("GPSBabel",created_by)==0) {
        gbfprintf(fout, "-%s", gpsbabel_version);
      }
    gbfprintf(fout, "'/>\n");
  }

  osm_write_tag("name", rte->rte_name);
  osm_write_tag("note", rte->rte_desc);

  if (opt_tag && (case_ignore_strncmp(opt_tag, "tagnd", 5) != 0)) {
    osm_write_opt_tag(opt_tag);
  }

  gbfprintf(fout, "  </way>\n");
}

/*-----------------------------------------------------------------------------*/

static void
osm_wr_init(const char* fname)
{
  fout = gbfopen(fname, "w", MYNAME);

  osm_init_icons();
  waypoints = avltree_init(0, MYNAME);
  node_id = 0;
}

static void
osm_write(void)
{
  gbfprintf(fout, "<?xml version='1.0' encoding='UTF-8'?>\n");
  gbfprintf(fout, "<osm version='0.5' generator='GPSBabel");
  if (gpsbabel_time != 0) {
    gbfprintf(fout, "-%s", gpsbabel_version);
  }
  gbfprintf(fout, "'>\n");

  waypt_disp_all(osm_waypt_disp);
  route_disp_all(NULL, NULL, osm_waypt_disp);
  track_disp_all(NULL, NULL, osm_waypt_disp);

  route_disp_all(osm_rte_disp_head, osm_rte_disp_trail, osm_rtept_disp);
  track_disp_all(osm_rte_disp_head, osm_rte_disp_trail, osm_rtept_disp);

  gbfprintf(fout, "</osm>\n");
}

static void
osm_wr_deinit(void)
{
  gbfclose(fout);

  waypt_disp_all(osm_release_ids);
  route_disp_all(NULL, NULL, osm_release_ids);
  track_disp_all(NULL, NULL, osm_release_ids);

  avltree_done(waypoints);
}

static void
osm_exit(void)
{
  if (keys) {
    avltree_done(keys);
  }
  if (values) {
    avltree_done(values);
  }
  if (icons) {
    avltree_done(icons);
  }
}

/*-----------------------------------------------------------------------------*/

ff_vecs_t osm_vecs = {
  ff_type_file,
  {
    (ff_cap)(ff_cap_read | ff_cap_write)	/* waypoints */,
    ff_cap_write 			/* tracks */,
    (ff_cap)(ff_cap_read | ff_cap_write) 	/* routes */,
  },
  osm_rd_init,
  osm_wr_init,
  osm_rd_deinit,
  osm_wr_deinit,
  osm_read,
  osm_write,
  osm_exit,
  osm_args,
  CET_CHARSET_UTF8, 0
};
