diff --git a/gmap.module b/gmap.module
index b31869d..b57199f 100644
--- a/gmap.module
+++ b/gmap.module
@@ -1178,6 +1178,12 @@ function gmap_map_cleanup(&$map) {
     list($map['latitude'],$map['longitude']) = explode(',',$map['latlon']);
   }
   unset($map['latlon']);
+  // convert gpx data to markers and polygons
+  if (isset($map['gpx']) && function_exists('gmap_gpx_parse_file')) {
+    $gpxdata = gmap_gpx_parse_file($map['gpx']);
+    gmap_gpx_data2map($map, $gpxdata);
+    unset($map['gpx']);
+  }
 }
 
 function theme_gmap_coord($element) {
@@ -1369,3 +1375,6 @@ function gmap_decimal($num) {
   // Otherwise, cast to float, possibly losing precision.
   return (float) $num;
 }
+
+// we now can parse gpx files
+require_once(drupal_get_path('module','gmap') .'/gmap_gpx.inc');
diff --git a/gmap_gpx.inc b/gmap_gpx.inc
new file mode 100755
index 0000000..f19509e
--- /dev/null
+++ b/gmap_gpx.inc
@@ -0,0 +1,216 @@
+<?php
+
+
+// $Id$
+/*
+ * Created on 11.04.2007
+ * parse gpx files so we can show tracks on the map
+ */
+
+function gmap_gpx_parse_file($gpxfile) {
+
+  $cid = 'gmap:gpx:file:'.check_plain(realpath($gpxfile));
+  if($cached = cache_get($cid)) {
+    return unserialize($cached->data);
+  }
+
+  $fp = fopen($gpxfile, "r");
+  if (!$fp) return NULL;
+
+  $data = fgets($fp, 8192);
+  while (!feof($fp)) {
+    $data .= fgets($fp, 8192);
+  }
+  fclose($fp);
+
+  $gpxdata = gmap_gpx_parse($data);
+  if ($gpxdata) {
+    cache_set($cid, 'cache', serialize($gpxdata), CACHE_TEMPORARY);
+  }
+
+  return $gpxdata;
+}
+
+/**
+ * parse the data-string for gpx info
+ * @returns gpx data array on success, NULL on failure
+ */
+function gmap_gpx_parse(&$data) {
+  global $gmap_gpx;
+
+  $parser = drupal_xml_parser_create($data);
+  xml_set_element_handler($parser, '_gmap_gpx_start_element', '_gmap_gpx_stop_element');
+  xml_set_character_data_handler($parser, '_gmap_gpx_char_data');
+
+  $gmap_gpx = array ('stack' => array ());
+  $r = xml_parse($parser, $data, true); //$buf, feof($fp));
+  xml_parser_free($parser);
+
+  if ($r) {
+    unset($gmap_gpx['stack']);
+    $r =& $gmap_gpx;
+    unset($gmap_gpx);
+    return $r;
+  }
+
+  return null;
+}
+
+function gmap_gpx_data2map(&$map, &$gpxdata) {
+
+  $c = is_array($gpxdata['wpt']) ? count($gpxdata['wpt']) : 0;
+  for ($i = 0; $i <= $c; $i++) {
+    $wp =& $gpxdata['wpt'][$i];
+    $m = array('latitude' => $wp['lat'], 'longitude' => $wp['lon']);
+    if ($wp['name']) $m['opts']['title'] = $wp['name'];
+    $map['markers'][] = $m;
+    // TODO: select icon from $wp['sym'] and $wp['type']
+  }
+
+  if (is_array($gpxdata['rte'])) { // 0..n routes
+    $c = count($gpxdata['rte']);
+    for ($i = 0; $i < $c; $i++) {
+      _gmap_gpx_add_wp_poly($map, $gpxdata['rte'][$i]);
+    }
+  }
+
+  if (is_array($gpxdata['trk'])) { // 0..n tracks with 1..m segments
+    $c = count($gpxdata['trk']);
+    for ($i = 0; $i < $c; $i++) {
+      $c2 = count($gpxdata['trk'][$i]);
+      for ($j = 0; $j < $c; $j++) {
+        _gmap_gpx_add_wp_poly($map, $gpxdata['trk'][$i][$j]);
+      }
+    }
+  }
+
+  // TODO: make sure we don't screw up around 180 degrees
+  if ($gpxdata['bounds']) {
+    $lat = ($gpxdata['bounds']['minlat'] + $gpxdata['bounds']['maxlat']) / 2;
+    $lon = ($gpxdata['bounds']['minlon'] + $gpxdata['bounds']['maxlon']) / 2;
+    $map['latitude'] = $lat;
+    $map['longitude'] = $lon;
+  }
+}
+
+function _gmap_gpx_add_wp_poly(&$map, &$wpa) {
+  $c = count($wpa);
+  $s = array('type' => 'polygon', 'points' => array());
+  for ($i = 0; $i < $c; $i++) {
+    $wp = $wpa[$i];
+    $s['points'][] = array('latitude' => $wp['lat'], 'longitude' => $wp['lon']);
+  }
+  $map['shapes'][] = $s;
+}
+
+/**
+ * the paths to tags representing waypoint types nodes
+ */
+function _gmap_gpx_wp_elements() {
+  static $wpa = array (
+    'GPX WPT' => 1,
+    'GPX RTE RTEPT' => 1,
+    'GPX TRK TRKSEG TRKPT' => 1,
+  );
+  return $wpa;
+}
+
+function _gmap_gpx_start_element(&$parser, $name, $attrs) {
+  global $gmap_gpx;
+  $wpa = _gmap_gpx_wp_elements();
+  $path = join(' ', $gmap_gpx['stack']) . ' ' . $name;
+
+  //if (($path == 'GPX WPT') || ($path == 'GPX RTE RTEPT') || ($path == 'GPX TRK TRKSEG TRKPT')) {
+  if (isset ($wpa[$path])) {
+    $gmap_gpx['waypoint'] = array (
+      'lat' => $attrs['LAT'],
+      'lon' => $attrs['LON'],
+      //'lat_pretty' => _gmap_gpx_latlon_pretty($attrs['LAT'], 'N', 'S'),
+      //'lon_pretty' => _gmap_gpx_latlon_pretty($attrs['LON'], 'E', 'W', true),
+    );
+    $gmap_gpx['wp_cont'] = strtolower($name);
+  }
+  elseif (isset($gmap_gpx['waypoint']) && ($name == 'EXTENSIONS')) {
+    $gmap_gpx['in_extension'] = true;
+  }
+  elseif ($path == 'GPX BOUNDS') {
+    foreach ($attrs as $key => $val) {
+      $gmap_gpx['bounds'][strtolower($key)] = $val;
+    }
+  }
+
+  $gmap_gpx['char_data'] = '';
+  $gmap_gpx['stack'][] = $name;
+}
+
+function _gmap_gpx_stop_element(&$parser, $name) {
+  global $gmap_gpx;
+  $wpa = _gmap_gpx_wp_elements();
+
+  $path = join(' ', $gmap_gpx['stack']);
+  array_pop($gmap_gpx['stack']); // remove $name from stack
+
+  $cname = $gmap_gpx['wp_cont'];
+  if (isset($wpa[$path])) {
+    $gmap_gpx[$cname][] = $gmap_gpx['waypoint'];
+    unset($gmap_gpx['waypoint']);
+    unset($gmap_gpx['wp_cont']);
+  }
+  elseif (isset($gmap_gpx['waypoint'])) {
+    if ($gmap_gpx['in_extension']) {
+      if ($name == 'EXTENSIONS') unset($gmap_gpx['in_extension']);
+      return;
+    }
+    if ($name == 'TIME') {
+      $gmap_gpx['waypoint']['time'] = strtotime($gmap_gpx['char_data']);
+    }
+    else {
+      $gmap_gpx['waypoint'][strtolower($name)] = $gmap_gpx['char_data'];
+    }
+  }
+  elseif ($path == 'GPX RTE') {
+    $gmap_gpx['rte'][] = $gmap_gpx['rtept'];
+    unset($gmap_gpx['rtept']);
+  }
+  elseif ($path == 'GPX TRK TRKSEG') {
+    $gmap_gpx['trkseg'][] = $gmap_gpx['trkpt'];
+    unset($gmap_gpx['trkpt']);
+  }
+  elseif ($path == 'GPX TRK') {
+    $gmap_gpx['trk'][] = $gmap_gpx['trkseg'];
+    unset($gmap_gpx['trkseg']);
+  }
+
+  unset($gmap_gpx['char_data']);
+}
+
+function _gmap_gpx_char_data(&$parser, $data) {
+  global $gmap_gpx;
+  $gmap_gpx['char_data'] .= $data;
+}
+
+function _gmap_gpx_latlon_pretty($deg, $pos, $neg, $three = false) {
+  if ($deg > 0) {
+    $str = $pos;
+  } else {
+    $str = $neg;
+    $deg = - $deg;
+  }
+
+  $d = $deg;
+  settype($d, 'integer');
+  $deg -= $d;
+  if ($three && $d < 100)
+    $str .= '0';
+  if ($d < 10)
+    $str .= '0';
+  $str .= $d . '°';
+
+  $deg *= 60;
+  $deg *= 1000;
+  settype($deg, 'integer');
+  $deg /= 1000;
+  $str .= $deg . "'";
+
+  return $str;
+}
