]> git.street.me.uk Git - andy/gpx.git/commitdiff
Add route functionality
authorAndy Street <andy@street.me.uk>
Wed, 19 Dec 2018 14:32:11 +0000 (14:32 +0000)
committerAndy Street <andy@street.me.uk>
Wed, 19 Dec 2018 14:32:11 +0000 (14:32 +0000)
docs/samples/full-1.0.gpx
docs/samples/full-1.1.gpx
src/libgpx/gpx.php
src/libgpx/gpxreader.php
src/libgpx/gpxwriter.php
src/libgpx/route.php [new file with mode: 0644]

index e2424da86bef690f0371e66c09e0f73fbdfa9ca7..cdcf366f801dd5e06cfa52c01470aa29b8c15cc7 100644 (file)
     <dgpsid>13</dgpsid>
     <custom:checkedBy>Jane Doe</custom:checkedBy>
   </wpt>
+  <rte>
+    <name>Sample Route</name>
+    <cmt>Sample</cmt>
+    <desc>A Sample route</desc>
+    <src>Imagination</src>
+    <url>https://www.example.com/route</url>
+    <urlname>Sample Route</urlname>
+    <number>1</number>
+    <custom:checkedBy>Jane Doe</custom:checkedBy>
+    <rtept lat="0.0" lon="0.0"/>
+    <rtept lat="1.0" lon="1.0"/>
+  </rte>
 </gpx>
index 40a99c844bb39c0a7f8482a45332b0153d4e3c0c..d913c74857198e6b46d1d7e56fd8b4eb9ac668b2 100644 (file)
       <custom:checkedBy>Jane Doe</custom:checkedBy>\r
     </extensions>\r
   </wpt>\r
+  <rte>\r
+    <name>Sample Route</name>\r
+    <cmt>Sample</cmt>\r
+    <desc>A Sample route</desc>\r
+    <src>Imagination</src>\r
+    <link href="https://www.example.com/route">\r
+      <text>Sample Route</text>\r
+      <type>text/html</type>\r
+    </link>\r
+    <number>1</number>\r
+    <type>A type</type>\r
+    <extensions>\r
+      <custom:checkedBy>Jane Doe</custom:checkedBy>\r
+    </extensions>\r
+    <rtept lat="0.0" lon="0.0"/>\r
+    <rtept lat="1.0" lon="1.0"/>\r
+  </rte>\r
 </gpx>\r
index 33fc47978930c1ee7c0bb13223a771735863d7ee..3d45b6f8adaf6ab258502bedeecc07b8d689cfe8 100644 (file)
@@ -106,6 +106,13 @@ class GPX implements Geographic
    */
   protected $waypoints;
 
+  /**
+   * A list of routes.
+   *
+   * @var TypedDoublyLinkedList (Route)
+   */
+  protected $routes;
+
   /**
    * Fetch the name of the program that created the GPX file.
    *
@@ -283,6 +290,19 @@ class GPX implements Geographic
     return $this->waypoints;
   }
 
+  /**
+   * Fetch a list of routes.
+   *
+   * @param boolean $create Create the list if it does not already exist.
+   * @return TypedDoublyLinkedList A list of routes.
+   */
+  public function getRoutes(bool $create = true)
+  {
+    if ($create && $this->routes === null)
+      $this->routes = new TypedDoublyLinkedList('libgpx\Route');
+    return $this->routes;
+  }
+
   /**
    * Fetch a bounding box that covers the feature.
    *
@@ -292,9 +312,17 @@ class GPX implements Geographic
   public function getBounds()
   {
     $result = null;
-    foreach ($this->waypoints as $waypoint) {
-      $bounds = $waypoint->getBounds();
-      $result = ($result === null ? $bounds : $result->extend($bounds));
+    $lists = [
+      $this->waypoints,
+      $this->routes
+    ];
+    foreach ($lists as $list) {
+      if ($list === null) continue;
+      foreach ($list as $geo) {
+        $bounds = $geo->getBounds();
+        if ($bounds === null) continue;
+        $result = ($result === null ? $bounds : $result->extend($bounds));
+      }
     }
     return $result;
   }
index f243513f4a4cf3b8f5ba9af258339f8a6e0ace4c..077f85e9f290debedd121b01a1e6a4753d23f866 100644 (file)
@@ -180,6 +180,9 @@ class GPXReader
       'elements' => [
         'wpt' => function ($gpx) {
           $gpx->getWaypoints()[] = $this->readWpt();
+        },
+        'rte' => function ($gpx) {
+          $gpx->getRoutes()[] = $this->readRte();
         }
       ]
     ];
@@ -532,6 +535,88 @@ class GPXReader
     return $result;
   }
 
+  /**
+   * Read a rte type.
+   *
+   * @see https://www.topografix.com/GPX/1/1/#type_rteType
+   *
+   * @return Route
+   * @throws MalformedGPXException If the GPX file was invalid or not supported.
+   */
+  protected function readRte()
+  {
+    try {
+      $result = new Route();
+      $struct = [
+        'elements' => [
+          'name' => function ($route) {
+            $route->setName($this->readXSDString());
+          },
+          'cmt' => function ($route) {
+            $route->setComment($this->readXSDString());
+          },
+          'desc' => function ($route) {
+            $route->setDescription($this->readXSDString());
+          },
+          'src' => function ($route) {
+            $route->setSource($this->readXSDString());
+          },
+          'number' => function ($route) {
+            $route->setNumber($this->string2int($this->readXSDString()));
+          },
+          'rtept' => function ($route) {
+            $route->getPoints()[] = $this->readWpt();
+          }
+        ]
+      ];
+      if ($this->version == '1.1') {
+        $struct['elements']['link'] = function ($route) {
+          $route->getLinks()[] = $this->readLink();
+        };
+        $struct['elements']['type'] = function ($route) {
+          $route->setType($this->readXSDString());
+        };
+        $struct['elements']['extensions'] = function ($route) {
+          $this->readExtension($route->getExtensions());
+        };
+      } else {
+        $struct['elements']['url'] = function ($route, &$state) {
+          $href = $this->readXSDString();
+          $links = $route->getLinks();
+          if ($links->isEmpty()) {
+            $link = new Link($href);
+            if (isset($state['urlname'])) {
+              $link->setText($state['urlname']);
+              unset($state['urlname']);
+            }
+            $links[] = $link;
+          } else {
+            $links->bottom()->setHref($href);
+          }
+        };
+        $struct['elements']['urlname'] = function ($route, &$state) {
+          $text = $this->readXSDString();
+          $links = $route->getLinks();
+          if ($links->isEmpty()) {
+            $state['urlname'] = $text;
+          } else {
+            $links->bottom()->setText($text);
+          }
+        };
+        $struct['extensions'] = function ($route) {
+          $route->getExtensions()[] = $this->xml->readOuterXML();
+          $this->xml->next();
+        };
+      }
+      $this->readStruct($struct, $result);
+    } catch (DomainException $e) {
+      throw new MalformedGPXException(
+        $e->getMessage(), $e->getCode(), $e
+      );
+    }
+    return $result;
+  }
+
   /**
    * Read a data structure from XML.
    *
index 710d02764f5b813518e881588741abe5f64f4c65..bb0af30d71d008be3353ae4a2c4e5ebf1ba38856 100644 (file)
@@ -239,6 +239,12 @@ class GPXWriter
         $this->writePoint($wpt, 'wpt', $xml);
       }
     }
+    $routes = $gpx->getRoutes(false);
+    if ($routes !== null) {
+      foreach ($routes as $route) {
+        $this->writeRoute($route, $xml);
+      }
+    }
     $xml->endElement();
     $xml->endDocument();
     $xml->flush();
@@ -493,4 +499,60 @@ class GPXWriter
     $xml->endElement();
   }
 
+  /**
+   * Write XML for a route element.
+   *
+   * @param Route $route The route to write.
+   * @param XMLWriter $xml Where to write.
+   */
+  protected function writeRoute(Route $route, XMLWriter $xml)
+  {
+    $xml->startElement('rte');
+    $var = $route->getName();
+    if ($var !== null)
+      $xml->writeElement('name', $var);
+    $var = $route->getComment();
+    if ($var !== null)
+      $xml->writeElement('cmt', $var);
+    $var = $route->getDescription();
+    if ($var !== null)
+      $xml->writeElement('desc', $var);
+    $var = $route->getSource();
+    if ($var !== null)
+      $xml->writeElement('src', $var);
+    $var = $route->getLinks(false);
+    if ($var !== null) {
+      foreach($var as $link) {
+        $this->writeLink($link, $xml);
+        if ($this->format == '1.0')
+          break;
+      }
+    }
+    $var = $route->getNumber();
+    if ($var !== null)
+      $xml->writeElement('number', $var);
+    if ($this->format == '1.1') {
+      $var = $route->getType();
+      if ($var !== null)
+        $xml->writeElement('type', $var);
+    }
+    $var = $route->getExtensions(false);
+    if ($var !== null && !$var->isEmpty()) {
+      if ($this->format == '1.1')
+        $xml->startElement('extensions');
+      foreach ($var as $extension) {
+        $xml->writeRaw($extension);
+      }
+      if ($this->format == '1.1')
+        $xml->endElement();
+    }
+    $var = $route->getPoints(false);
+    if ($var !== null) {
+      foreach ($var as $point) {
+        $this->writePoint($point, 'rtept', $xml);
+      }
+    }
+    $xml->endElement();
+  }
+
 }
diff --git a/src/libgpx/route.php b/src/libgpx/route.php
new file mode 100644 (file)
index 0000000..17a5000
--- /dev/null
@@ -0,0 +1,297 @@
+<?php
+/**
+ * route.php
+ *
+ * Copyright 2018 Andy Street <andy@street.me.uk>
+ *
+ * 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., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301, USA.
+ *
+ *
+ */
+
+namespace libgpx;
+
+use \DomainException;
+
+/**
+ * The GPX route type.
+ *
+ * @see https://www.topografix.com/GPX/1/1/#type_rteType
+ *
+ * @author Andy Street <andy@street.me.uk>
+ */
+class Route implements Geographic
+{
+
+  /**
+   * The name of the route.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
+   * GPS waypoint comment.
+   *
+   * Sent to GPS as comment.
+   *
+   * @var string
+   */
+  protected $comment;
+
+  /**
+   * A text description of the element.
+   *
+   * Holds additional information about the element intended for the user,
+   * not the GPS.
+   *
+   * @var string
+   */
+  protected $description;
+
+  /**
+   * Source of data.
+   *
+   * Included to give user some idea of reliability and accuracy of data.
+   * E.g. "Garmin eTrex", "USGS quad Boston North".
+   *
+   * @var string
+   */
+  protected $source;
+
+  /**
+   * A list of links associated with this route.
+   *
+   * @var TypedDoublyLinkedList (Link)
+   */
+  protected $links;
+
+  /**
+   * GPS route number.
+   *
+   * @var int
+   */
+  protected $number;
+
+  /**
+   * Type (classification) of route.
+   *
+   * @var string
+   */
+  protected $type;
+
+  /**
+   * A list of XML snippets describing unsupported data.
+   *
+   * @var TypedDoublyLinkedList (string)
+   */
+  protected $extensions;
+
+  /**
+   * A list of route points.
+   *
+   * @var DoublyLinkedList (Point)
+   */
+  protected $points;
+
+  /**
+   * Fetch the name of the route.
+   *
+   * @return string|null The name or null if not set.
+   */
+  public function getName()
+  {
+    return $this->name;
+  }
+
+  /**
+   * Set the name of the route.
+   *
+   * @param string $name The name or null to delete.
+   * @return void
+   */
+  public function setName(string $name = null)
+  {
+    $this->name = $name;
+  }
+
+  /**
+   * Fetch a comment for the route.
+   *
+   * @return string The comment or null if not set.
+   */
+  public function getComment()
+  {
+    return $this->comment;
+  }
+
+  /**
+   * Set a comment for the route.
+   *
+   * @param string $comment The comment or null to delete.
+   * @return void
+   */
+  public function setComment(string $comment = null)
+  {
+    $this->comment = $comment;
+  }
+  /**
+   * Fetch the description for the route.
+   *
+   * @return string The description or null if not set.
+   */
+  public function getDescription()
+  {
+    return $this->description;
+  }
+
+  /**
+   * Set the description of the point.
+   *
+   * @param string $description The description or null to delete.
+   * @return void
+   */
+  public function setDescription(string $description = null)
+  {
+    $this->description = $description;
+  }
+
+  /**
+   * Fetch the source of the data.
+   *
+   * @return string|null The source or null if not set.
+   */
+  public function getSource()
+  {
+    return $this->source;
+  }
+
+  /**
+   * Set the source of the data.
+   *
+   * @param string $source The source of the data or null to delete.
+   * @return void
+   */
+  public function setSource(string $source = null)
+  {
+    $this->source = $source;
+  }
+
+  /**
+   * Fetch a list of links associated with this route.
+   *
+   * @param boolean $create Create the list if it does not already exist.
+   * @return TypedDoublyLinkedList|null A list of Link objects.
+   */
+  public function getLinks(bool $create = true)
+  {
+    if ($create && $this->links === null)
+      $this->links = new TypedDoublyLinkedList('libgpx\Link');
+    return $this->links;
+  }
+
+  /**
+   * Fetch the route number.
+   *
+   * @return int|null The route number or null if not set.
+   */
+  public function getNumber()
+  {
+    return $this->number;
+  }
+
+  /**
+   * Set the number of the route.
+   *
+   * @param int $number The route number or null to delete.
+   * @return void
+   * @throws DomainException If the number is < 0
+   */
+  public function setNumber(int $number = null)
+  {
+    if ($number !== null && $number < 0)
+      throw new DomainException(
+        sprintf('Number must be >= 0 but got "%s"', $number)
+      );
+    $this->number = $number;
+  }
+
+  /**
+   * Fetch the type of the route.
+   *
+   * @return string|null The type or null if not set.
+   */
+  public function getType()
+  {
+    return $this->type;
+  }
+
+  /**
+   * Set the type of the route.
+   *
+   * @param string $type The type or null to delete.
+   * @return void
+   */
+  public function setType(string $type = null)
+  {
+    $this->type = $type;
+  }
+
+  /**
+   * Fetch a list of XML strings that describe unsupported elements.
+   *
+   * @param boolean $create Create the list if it does not already exist.
+   * @return TypedDoublyLinkedList|null A list of XML strings.
+   */
+  public function getExtensions(bool $create = true)
+  {
+    if ($create && $this->extensions === null)
+      $this->extensions = new TypedDoublyLinkedList('string');
+    return $this->extensions;
+  }
+
+  /**
+   * Fetch an ordered list of route points.
+   *
+   * @param boolean $create Create the list if it does not already exist.
+   * @return TypedDoublyLinkedList|null A list of route points.
+   */
+  public function getPoints(bool $create = true)
+  {
+    if ($create && $this->points === null)
+      $this->points = new TypedDoublyLinkedList('libgpx\Point');
+    return $this->points;
+  }
+
+  /**
+   * Fetch a bounding box that covers the feature.
+   *
+   * @return Bounds|null A bounding box covering the extent of the feature or
+   *         null if not applicable.
+   */
+  public function getBounds()
+  {
+    $result = null;
+    if ($this->points !== null) {
+      foreach ($this->points as $point) {
+        $bounds = $point->getBounds();
+        $result = ($result === null ? $bounds : $result->extend($bounds));
+      }
+    }
+    return $result;
+  }
+
+}