From: Andy Street Date: Wed, 19 Dec 2018 14:32:11 +0000 (+0000) Subject: Add route functionality X-Git-Url: https://git.street.me.uk/andy/gpx.git/commitdiff_plain/f17d256636ddd973ae9f02ae63616330ca5d12b0 Add route functionality --- diff --git a/docs/samples/full-1.0.gpx b/docs/samples/full-1.0.gpx index e2424da..cdcf366 100644 --- a/docs/samples/full-1.0.gpx +++ b/docs/samples/full-1.0.gpx @@ -31,4 +31,16 @@ 13 Jane Doe + + Sample Route + Sample + A Sample route + Imagination + https://www.example.com/route + Sample Route + 1 + Jane Doe + + + diff --git a/docs/samples/full-1.1.gpx b/docs/samples/full-1.1.gpx index 40a99c8..d913c74 100644 --- a/docs/samples/full-1.1.gpx +++ b/docs/samples/full-1.1.gpx @@ -52,4 +52,21 @@ Jane Doe + + Sample Route + Sample + A Sample route + Imagination + + Sample Route + text/html + + 1 + A type + + Jane Doe + + + + diff --git a/src/libgpx/gpx.php b/src/libgpx/gpx.php index 33fc479..3d45b6f 100644 --- a/src/libgpx/gpx.php +++ b/src/libgpx/gpx.php @@ -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; } diff --git a/src/libgpx/gpxreader.php b/src/libgpx/gpxreader.php index f243513..077f85e 100644 --- a/src/libgpx/gpxreader.php +++ b/src/libgpx/gpxreader.php @@ -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. * diff --git a/src/libgpx/gpxwriter.php b/src/libgpx/gpxwriter.php index 710d027..bb0af30 100644 --- a/src/libgpx/gpxwriter.php +++ b/src/libgpx/gpxwriter.php @@ -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 index 0000000..17a5000 --- /dev/null +++ b/src/libgpx/route.php @@ -0,0 +1,297 @@ + + * + * 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 + */ +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; + } + +}