]>
Commit | Line | Data |
---|---|---|
88564339 AS |
1 | <?php |
2 | /** | |
3 | * gpxreader.php | |
4 | * | |
5 | * Copyright 2018 Andy Street <andy@street.me.uk> | |
6 | * | |
7 | * This program is free software; you can redistribute it and/or modify | |
8 | * it under the terms of the GNU General Public License as published by | |
9 | * the Free Software Foundation; either version 2 of the License, or | |
10 | * (at your option) any later version. | |
11 | * | |
12 | * This program is distributed in the hope that it will be useful, | |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
15 | * GNU General Public License for more details. | |
16 | * | |
17 | * You should have received a copy of the GNU General Public License | |
18 | * along with this program; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, | |
20 | * MA 02110-1301, USA. | |
21 | * | |
22 | * | |
23 | */ | |
24 | ||
25 | namespace libgpx; | |
26 | ||
27 | use \DateTime; | |
28 | use \DateTimeZone; | |
f528d248 | 29 | use \DomainException; |
88564339 AS |
30 | use \Exception; |
31 | use \InvalidArgumentException; | |
32 | use \RuntimeException; | |
33 | ||
34 | /** | |
35 | * Read GPX files. | |
36 | * | |
37 | * @author Andy Street <andy@street.me.uk> | |
38 | */ | |
39 | class GPXReader | |
40 | { | |
41 | ||
42 | /** | |
43 | * The namespace of the XML currently being parsed. | |
44 | * | |
45 | * @var string | |
46 | */ | |
47 | protected $ns; | |
48 | ||
49 | /** | |
50 | * The GPX version of the XML currently being parsed. | |
51 | * | |
52 | * @var string | |
53 | */ | |
54 | protected $version; | |
55 | ||
56 | /** | |
57 | * The reader currently reading the XML. | |
58 | * | |
59 | * @var XMLReader | |
60 | */ | |
61 | protected $xml; | |
62 | ||
63 | /** | |
64 | * Read GPX from a string. | |
65 | * | |
66 | * @param string $xml The GPX XML data. | |
67 | * @return GPX The GPX data. | |
68 | * @throws RuntimeException If the XML could not be read. | |
69 | * @throws MalformedGPXException If the GPX file is invalid. | |
70 | */ | |
71 | public function readString(string $xml) | |
72 | { | |
73 | try { | |
74 | $this->xml = new XMLReader(); | |
75 | if (!$this->xml->XML($xml, null, LIBXML_NOERROR | LIBXML_NOWARNING)) { | |
76 | throw new RuntimeException('Unable to read XML from string.'); | |
77 | } | |
78 | return $this->read(); | |
79 | } finally { | |
80 | $this->xml = null; | |
81 | } | |
82 | } | |
83 | ||
84 | /** | |
85 | * Read GPX from a file. | |
86 | * | |
87 | * This function is an alias of `GPXReader::readURI()`. | |
88 | * | |
89 | * @param string $filename The name of the file containing GPX XML data. | |
90 | * @return GPX The GPX data. | |
91 | * @throws RuntimeException If the XML could not be read. | |
92 | * @throws MalformedGPXException If the GPX file is invalid. | |
93 | */ | |
94 | public function readFile(string $filename) | |
95 | { | |
96 | return $this->readURI($filename); | |
97 | } | |
98 | ||
99 | /** | |
100 | * Read GPX from a URI. | |
101 | * | |
102 | * @param string $uri The location of the file containing GPX XML data. | |
103 | * @return GPX The GPX data. | |
104 | * @throws RuntimeException If the XML could not be read. | |
105 | * @throws MalformedGPXException If the GPX file is invalid. | |
106 | */ | |
107 | public function readURI(string $uri) | |
108 | { | |
109 | try { | |
110 | $this->xml = new XMLReader(); | |
111 | if (!$this->xml->open($uri, null, LIBXML_NOERROR | LIBXML_NOWARNING)) { | |
112 | throw new RuntimeException(sprintf('Unable to read XML from: %s', $uri)); | |
113 | } | |
114 | return $this->read(); | |
115 | } finally { | |
116 | $this->xml = null; | |
117 | } | |
118 | } | |
119 | ||
120 | /** | |
121 | * Read GPX data from XML. | |
122 | * | |
123 | * @param XMLReader $xml Where to read the GPX data from. | |
124 | * @return GPX The data that was read. | |
125 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
126 | */ | |
127 | protected function read() | |
128 | { | |
129 | $xml = $this->xml; | |
130 | try { | |
131 | // Fast forward to the root element. | |
132 | while ($xml->nodeType !== XMLReader::ELEMENT) { | |
133 | $xml->read(); | |
134 | } | |
135 | ||
136 | // Detect file version | |
137 | switch ($xml->namespaceURI) { | |
138 | case libgpx::NAMESPACE_GPX_1_0: | |
139 | $this->version = '1.0'; | |
140 | $this->ns = libgpx::NAMESPACE_GPX_1_0; | |
141 | break; | |
142 | case libgpx::NAMESPACE_GPX_1_1: | |
143 | $this->version = '1.1'; | |
144 | $this->ns = libgpx::NAMESPACE_GPX_1_1; | |
145 | break; | |
146 | default: | |
147 | throw new MalformedGPXException('Unknown or unsupported file format.'); | |
148 | } | |
149 | ||
150 | // Read GPX element. | |
151 | if ($xml->localName == 'gpx') | |
152 | return $this->readGPX(); | |
153 | else | |
154 | throw new MalformedGPXException('Root element must be "gpx".'); | |
155 | ||
156 | } finally { | |
157 | $this->version = null; | |
158 | $this->ns = null; | |
159 | } | |
160 | } | |
161 | ||
162 | /** | |
163 | * Read a gpx type. | |
164 | * | |
165 | * @return GPX The GPX data. | |
166 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
167 | */ | |
168 | protected function readGPX() | |
169 | { | |
170 | // Sanity check - ensure version attribute and schema declaration match. | |
171 | if ($this->xml->getAttribute('version') != $this->version) | |
172 | throw new MalformedGPXException( | |
173 | 'GPX version attribute does not match namespace declaration.' | |
174 | ); | |
175 | ||
176 | $result = new GPX(); | |
177 | $result->setCreator($this->xml->getAttribute('creator')); | |
178 | ||
179 | $struct = [ | |
f528d248 AS |
180 | 'elements' => [ |
181 | 'wpt' => function ($gpx) { | |
182 | $gpx->getWaypoints()[] = $this->readWpt(); | |
183 | } | |
184 | ] | |
88564339 AS |
185 | ]; |
186 | if ($this->version == '1.1') { | |
187 | $struct['elements']['metadata'] = function ($gpx) { | |
188 | $this->readMetadata($gpx); | |
189 | }; | |
190 | } else { | |
191 | $struct['elements']['name'] = function ($gpx) { | |
192 | $gpx->setName($this->readXSDString()); | |
193 | }; | |
194 | $struct['elements']['desc'] = function ($gpx) { | |
195 | $gpx->setDesc($this->readXSDString()); | |
196 | }; | |
197 | $struct['elements']['author'] = function ($gpx) { | |
198 | $author = $gpx->getAuthor(); | |
199 | if ($author === null) { | |
200 | $author = new Person(); | |
201 | $gpx->setAuthor($author); | |
202 | } | |
203 | $author->setName($this->readXSDString()); | |
204 | }; | |
205 | $struct['elements']['email'] = function ($gpx) { | |
206 | $author = $gpx->getAuthor(); | |
207 | if ($author === null) { | |
208 | $author = new Person(); | |
209 | $gpx->setAuthor($author); | |
210 | } | |
211 | $author->setEmail($this->readXSDString()); | |
212 | }; | |
213 | $struct['elements']['url'] = function ($gpx, &$state) { | |
214 | $href = $this->readXSDString(); | |
215 | $links = $gpx->getLinks(); | |
f528d248 | 216 | if ($links->isEmpty()) { |
88564339 AS |
217 | $link = new Link($href); |
218 | if (isset($state['urlname'])) { | |
219 | $link->setText($state['urlname']); | |
220 | unset($state['urlname']); | |
221 | } | |
7c465a82 AS |
222 | $links[] = $link; |
223 | } else { | |
224 | $links->bottom()->setHref($href); | |
88564339 AS |
225 | } |
226 | }; | |
227 | $struct['elements']['urlname'] = function ($gpx, &$state) { | |
228 | $text = $this->readXSDString(); | |
229 | $links = $gpx->getLinks(); | |
f528d248 | 230 | if ($links->isEmpty()) { |
88564339 | 231 | $state['urlname'] = $text; |
7c465a82 AS |
232 | } else { |
233 | $links->bottom()->setText($text); | |
88564339 AS |
234 | } |
235 | }; | |
236 | $struct['elements']['time'] = function ($gpx) { | |
237 | $gpx->setTime($this->string2DateTime($this->readXSDString())); | |
238 | }; | |
239 | $struct['elements']['keywords'] = function ($gpx) { | |
7c465a82 AS |
240 | $keywords = $gpx->getKeywords(); |
241 | $words = explode(',', $this->readXSDString()); | |
242 | foreach ($words as $word) | |
243 | $keywords[] = trim($word); | |
88564339 AS |
244 | }; |
245 | $struct['elements']['bounds'] = false; | |
246 | } | |
247 | ||
248 | $this->readStruct($struct, $result); | |
249 | return $result; | |
250 | } | |
251 | ||
252 | /** | |
253 | * Read a metadata type. | |
254 | * | |
255 | * Note: This is GPX 1.1 only as GPX 1.0 does not have a metadata type. | |
256 | * | |
257 | * @param GPX $gpx The gpx object to populate. | |
258 | * @return void | |
259 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
260 | */ | |
261 | protected function readMetadata(GPX $gpx) | |
262 | { | |
263 | $struct = [ | |
264 | 'elements' => [ | |
265 | 'name' => function ($gpx) { | |
266 | $gpx->setName($this->readXSDString()); | |
267 | }, | |
268 | 'desc' => function ($gpx) { | |
269 | $gpx->setDesc($this->readXSDString()); | |
270 | }, | |
271 | 'author' => function ($gpx) { | |
272 | $gpx->setAuthor($this->readPerson()); | |
273 | }, | |
274 | 'copyright' => function ($gpx) { | |
275 | $gpx->setCopyright($this->readCopyright()); | |
276 | }, | |
277 | 'link' => function ($gpx) { | |
7c465a82 AS |
278 | $links = $gpx->getLinks(); |
279 | $links[] = $this->readLink(); | |
88564339 AS |
280 | }, |
281 | 'time' => function ($gpx) { | |
282 | $gpx->setTime($this->string2DateTime($this->readXSDString())); | |
283 | }, | |
284 | 'keywords' => function ($gpx) { | |
7c465a82 AS |
285 | $keywords = $gpx->getKeywords(); |
286 | $words = explode(',', $this->readXSDString()); | |
287 | foreach ($words as $word) | |
288 | $keywords[] = trim($word); | |
88564339 | 289 | }, |
7c465a82 AS |
290 | 'bounds' => false, // Not required as it is calculated internally. |
291 | 'extensions' => function ($gpx) { | |
292 | $this->readExtension($gpx->getMetadataExtensions()); | |
293 | } | |
88564339 AS |
294 | ] |
295 | ]; | |
296 | $this->readStruct($struct, $gpx); | |
297 | } | |
298 | ||
299 | /** | |
300 | * Read a person type. | |
301 | * | |
302 | * @return Person The person data. | |
303 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
304 | */ | |
305 | protected function readPerson() | |
306 | { | |
307 | $struct = [ | |
308 | 'elements' => [ | |
309 | 'name' => function ($person) { | |
310 | $person->setName($this->readXSDString()); | |
311 | }, | |
312 | 'email' => function ($person) { | |
313 | $id = $this->xml->getAttribute('id'); | |
314 | if ($id === null) | |
315 | throw new MalformedGPXException( | |
316 | 'Missing required attribute "id" in "email"' | |
317 | ); | |
318 | $domain = $this->xml->getAttribute('domain'); | |
319 | if ($domain === null) | |
320 | throw new MalformedGPXException( | |
321 | 'Missing required attribute "domain" in "email"' | |
322 | ); | |
323 | $person->setEmail($id . '@' . $domain); | |
324 | $this->xml->read(); | |
325 | }, | |
326 | 'link' => function ($person) { | |
327 | $person->setLink($this->readLink()); | |
328 | } | |
329 | ] | |
330 | ]; | |
331 | $person = new Person(); | |
332 | $this->readStruct($struct, $person); | |
333 | return $person; | |
334 | } | |
335 | ||
336 | /** | |
337 | * Read a link type. | |
338 | * | |
339 | * @return Link The link data. | |
340 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
341 | */ | |
342 | protected function readLink() | |
343 | { | |
344 | $href = $this->xml->getAttribute('href'); | |
345 | if ($href === null) | |
346 | throw new MalformedGPXException( | |
347 | 'Missing required attribute "href" in "link"' | |
348 | ); | |
349 | $struct = [ | |
350 | 'elements' => [ | |
351 | 'text' => function ($link) { | |
352 | $link->setText($this->readXSDString()); | |
353 | }, | |
354 | 'type' => function ($link) { | |
355 | $link->setType($this->readXSDString()); | |
356 | } | |
357 | ] | |
358 | ]; | |
359 | $link = new Link($href); | |
360 | $this->readStruct($struct, $link); | |
361 | return $link; | |
362 | } | |
363 | ||
364 | /** | |
365 | * Read a copyright type. | |
366 | * | |
367 | * @return Copyright The copyright data. | |
368 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
369 | */ | |
370 | protected function readCopyright() | |
371 | { | |
372 | $author = $this->xml->getAttribute('author'); | |
373 | if ($author === null) | |
374 | throw new MalformedGPXException( | |
375 | 'Missing required attribute "author" in "copyright"' | |
376 | ); | |
377 | $struct = [ | |
378 | 'elements' => [ | |
379 | 'year' => function ($copyright) { | |
380 | try { | |
381 | $year = $this->readXSDString(); | |
382 | $copyright->setYear($year); | |
383 | } catch (InvalidArgumentException $e) { | |
384 | throw new MalformedGPXException( | |
385 | sprintf('"%s" is not a valid year', $year) | |
386 | ); | |
387 | } | |
388 | }, | |
389 | 'license' => function ($copyright) { | |
390 | $copyright->setLicense($this->readXSDString()); | |
391 | } | |
392 | ] | |
393 | ]; | |
394 | $copyright = new Copyright($author); | |
395 | $this->readStruct($struct, $copyright); | |
396 | return $copyright; | |
397 | } | |
398 | ||
7c465a82 AS |
399 | /** |
400 | * Read an extensions type. | |
401 | * | |
402 | * This is suitable for GPX 1.1 only as private elements are mixed into other | |
403 | * elements in GPX 1.0. | |
404 | * | |
405 | * @param TypedDoublyLinkedList $list The list to fill with extension XML. | |
406 | * @return void | |
407 | * @throws MalformedGPXException If the GPX file was invalid. | |
408 | */ | |
409 | protected function readExtension(TypedDoublyLinkedList $list) | |
410 | { | |
411 | $struct = [ | |
412 | 'extensions' => function ($list) { | |
413 | $list[] = $this->xml->readOuterXml(); | |
414 | $this->xml->next(); | |
415 | } | |
416 | ]; | |
417 | $this->readStruct($struct, $list); | |
418 | } | |
419 | ||
f528d248 AS |
420 | /** |
421 | * Read a wpt type. | |
422 | * | |
423 | * @see https://www.topografix.com/GPX/1/1/#type_wptType | |
424 | * | |
425 | * @return Point | |
426 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
427 | */ | |
428 | protected function readWpt() | |
429 | { | |
430 | try { | |
431 | $result = new Point( | |
432 | $this->string2float($this->xml->getAttribute('lat')), | |
433 | $this->string2float($this->xml->getAttribute('lon')) | |
434 | ); | |
435 | $struct = [ | |
436 | 'elements' => [ | |
437 | 'ele' => function ($point) { | |
438 | $point->setEle($this->string2float($this->readXSDString())); | |
439 | }, | |
440 | 'time' => function ($point) { | |
441 | $point->setTime($this->string2DateTime($this->readXSDString())); | |
442 | }, | |
443 | 'magvar' => function ($point) { | |
444 | $point->setMagvar($this->string2float($this->readXSDString())); | |
445 | }, | |
446 | 'geoidheight' => function ($point) { | |
447 | $point->setGeoidHeight($this->string2float($this->readXSDString())); | |
448 | }, | |
449 | 'name' => function ($point) { | |
450 | $point->setName($this->readXSDString()); | |
451 | }, | |
452 | 'cmt' => function ($point) { | |
453 | $point->setComment($this->readXSDString()); | |
454 | }, | |
455 | 'desc' => function ($point) { | |
456 | $point->setDescription($this->readXSDString()); | |
457 | }, | |
458 | 'src' => function ($point) { | |
459 | $point->setSource($this->readXSDString()); | |
460 | }, | |
461 | 'sym' => function ($point) { | |
462 | $point->setSymbol($this->readXSDString()); | |
463 | }, | |
464 | 'type' => function ($point) { | |
465 | $point->setType($this->readXSDString()); | |
466 | }, | |
467 | 'fix' => function ($point) { | |
468 | $point->setFix($this->readXSDString()); | |
469 | }, | |
470 | 'sat' => function ($point) { | |
471 | $point->setSatellites($this->string2int($this->readXSDString())); | |
472 | }, | |
473 | 'hdop' => function ($point) { | |
474 | $point->setHdop($this->string2float($this->readXSDString())); | |
475 | }, | |
476 | 'vdop' => function ($point) { | |
477 | $point->setVdop($this->string2float($this->readXSDString())); | |
478 | }, | |
479 | 'pdop' => function ($point) { | |
480 | $point->setPdop($this->string2float($this->readXSDString())); | |
481 | }, | |
482 | 'ageofdgpsdata' => function ($point) { | |
483 | $point->setAgeOfDGPSData($this->string2float($this->readXSDString())); | |
484 | }, | |
485 | 'dgpsid' => function ($point) { | |
486 | $point->setDGPSId($this->string2int($this->readXSDString())); | |
487 | } | |
488 | ] | |
489 | ]; | |
490 | if ($this->version == '1.1') { | |
491 | $struct['elements']['link'] = function ($point) { | |
492 | $point->getLinks()[] = $this->readLink(); | |
493 | }; | |
494 | $struct['elements']['extensions'] = function ($point) { | |
495 | $this->readExtension($point->getExtensions()); | |
496 | }; | |
497 | } else { | |
498 | $struct['elements']['url'] = function ($point, &$state) { | |
499 | $href = $this->readXSDString(); | |
500 | $links = $point->getLinks(); | |
501 | if ($links->isEmpty()) { | |
502 | $link = new Link($href); | |
503 | if (isset($state['urlname'])) { | |
504 | $link->setText($state['urlname']); | |
505 | unset($state['urlname']); | |
506 | } | |
507 | $links[] = $link; | |
508 | } else { | |
509 | $links->bottom()->setHref($href); | |
510 | } | |
511 | }; | |
512 | $struct['elements']['urlname'] = function ($point, &$state) { | |
513 | $text = $this->readXSDString(); | |
514 | $links = $point->getLinks(); | |
515 | if ($links->isEmpty()) { | |
516 | $state['urlname'] = $text; | |
517 | } else { | |
518 | $links->bottom()->setText($text); | |
519 | } | |
520 | }; | |
521 | $struct['extensions'] = function ($point) { | |
522 | $point->getExtensions()[] = $this->xml->readOuterXML(); | |
523 | $this->xml->next(); | |
524 | }; | |
525 | } | |
526 | $this->readStruct($struct, $result); | |
527 | } catch (DomainException $e) { | |
528 | throw new MalformedGPXException( | |
529 | $e->getMessage(), $e->getCode(), $e | |
530 | ); | |
531 | } | |
532 | return $result; | |
533 | } | |
534 | ||
88564339 AS |
535 | /** |
536 | * Read a data structure from XML. | |
537 | * | |
538 | * This is a generalized function for parsing XML data structures and | |
539 | * populating values based on data retrieved. The main control element is the | |
540 | * `$struct` array which holds a list of callable functions which are used to | |
541 | * modify the supplied object. The structure of the array is: | |
542 | * | |
543 | * [ | |
7c465a82 | 544 | * 'extensions' => function($object, &$state) {...}, |
88564339 | 545 | * 'elements' => [ |
7c465a82 | 546 | * 'nodename' => function($object, &$state) {...} |
88564339 AS |
547 | * ] |
548 | * ] | |
549 | * | |
7c465a82 AS |
550 | * The following keys are defined: |
551 | * | |
552 | * * `extensions` - Call the supplied anonymous function if any XML element | |
553 | * is encountered in a namespace other than the document | |
554 | * namespace. | |
555 | * * `elements` - Call the supplied anonymous function if an XML element | |
556 | * is encountered in the document namespace with a matching | |
557 | * node name. | |
558 | * | |
88564339 AS |
559 | * The anonymous function parameters are as follows: |
560 | * | |
561 | * * `$object` - The object to be populated (as passed to this function) | |
562 | * * `$state` - An array that can be used to store data temporarily while | |
563 | * the structure is being written. This is useful if a value | |
564 | * that is being read is split across several XML elements. | |
565 | * | |
566 | * @param array $struct a data structure as defined above. | |
567 | * @param object $object The object to populate with data. | |
568 | * @return void | |
569 | * @throws MalformedGPXException If the GPX file was invalid or not supported. | |
570 | */ | |
571 | protected function readStruct($struct, $object) | |
572 | { | |
573 | $xml = $this->xml; | |
574 | if ($xml->isEmptyElement) { | |
575 | $xml->read(); | |
576 | return; | |
577 | } | |
578 | $element = $xml->localName; | |
579 | $xml->read(); | |
580 | $state = []; | |
581 | while (true) { | |
582 | if ( | |
583 | $xml->nodeType == XMLReader::END_ELEMENT | |
584 | && $xml->namespaceURI == $this->ns | |
585 | && $xml->localName == $element | |
586 | ) return; | |
587 | if ($xml->nodeType != XMLReader::ELEMENT) { | |
588 | $xml->read(); | |
589 | continue; | |
590 | } | |
591 | if ( | |
592 | $xml->namespaceURI == $this->ns | |
593 | && isset($struct['elements'][$xml->localName]) | |
594 | ) { | |
595 | if (is_callable($struct['elements'][$xml->localName])) { | |
596 | $struct['elements'][$xml->localName]($object, $state); | |
597 | } else { | |
598 | $xml->read(); | |
599 | } | |
600 | continue; | |
601 | } | |
7c465a82 AS |
602 | if ( |
603 | $xml->namespaceURI != $this->ns | |
604 | && isset($struct['extensions']) | |
605 | && is_callable($struct['extensions']) | |
606 | ) { | |
607 | $struct['extensions']($object, $state); | |
608 | continue; | |
609 | } | |
88564339 AS |
610 | throw new MalformedGPXException( |
611 | sprintf( | |
612 | 'Unknown element "%s" in element "%s"', | |
613 | $xml->localName, | |
614 | $element | |
615 | ) | |
616 | ); | |
617 | } | |
618 | } | |
619 | ||
620 | /** | |
621 | * Read an `xsd:string` type from XML. | |
622 | * | |
623 | * @return string The text string. | |
624 | * @throws MalformedGPXException If the element has non-text children. | |
625 | */ | |
626 | protected function readXSDString() | |
627 | { | |
628 | $xml = $this->xml; | |
629 | $result = ''; | |
630 | if ($xml->nodeType != XMLReader::ELEMENT || $xml->isEmptyElement) | |
631 | return $result; | |
632 | $element = $xml->name; | |
633 | while ($xml->read()) { | |
634 | switch ($xml->nodeType) { | |
635 | case XMLReader::TEXT: | |
636 | case XMLReader::CDATA: | |
637 | $result .= $xml->value; | |
638 | break; | |
639 | case XMLReader::END_ELEMENT: | |
640 | $xml->read(); | |
641 | break 2; | |
642 | default: | |
643 | throw new MalformedGPXException( | |
644 | sprintf('Element "%s" contains non-text data.', $element) | |
645 | ); | |
646 | } | |
647 | ||
648 | } | |
649 | return $result; | |
650 | } | |
651 | ||
652 | /** | |
653 | * Convert a string to DateTime. | |
654 | * | |
655 | * @param string $timestamp The timestamp to convert. | |
656 | * @return DateTime The parsed DateTime. | |
657 | * @throws MalformedGPXException If the timestamp could not be parsed. | |
658 | */ | |
659 | protected function string2DateTime(string $timestamp) | |
660 | { | |
661 | try { | |
662 | $result = new DateTime($timestamp, new DateTimeZone('Z')); | |
663 | } catch (Exception $e) { | |
664 | throw new MalformedGPXException( | |
665 | sprintf('Unknown datetime format "%s"', $timestamp) | |
666 | ); | |
667 | } | |
668 | return $result; | |
669 | } | |
670 | ||
f528d248 AS |
671 | /** |
672 | * Convert a string to a float. | |
673 | * | |
674 | * @param string $value The string value to convert. | |
675 | * @return float | |
676 | * @throws MalformedGPXException If the value is not numeric. | |
677 | */ | |
678 | protected function string2float(string $value) | |
679 | { | |
680 | if (!is_numeric($value)) | |
681 | throw new MalformedGPXException( | |
682 | sprintf('Expected decimal value but got "%s"', $value) | |
683 | ); | |
684 | return floatval($value); | |
685 | } | |
686 | ||
687 | /** | |
688 | * Convert a string to a int. | |
689 | * | |
690 | * @param string $value The string value to convert. | |
691 | * @return int | |
692 | * @throws MalformedGPXException If the value is not numeric. | |
693 | */ | |
694 | protected function string2int(string $value) | |
695 | { | |
696 | if (!is_numeric($value)) | |
697 | throw new MalformedGPXException( | |
698 | sprintf('Expected decimal value but got "%s"', $value) | |
699 | ); | |
700 | return intval($value); | |
701 | } | |
702 | ||
88564339 | 703 | } |