]> git.street.me.uk Git - andy/gpx.git/blame - src/libgpx/gpxwriter.php
Only create lists as required
[andy/gpx.git] / src / libgpx / gpxwriter.php
CommitLineData
88564339
AS
1<?php
2/**
3 * gpxwriter.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
25namespace libgpx;
26
27use \DateTime;
28use \DateTimeInterface;
29use \DateTimeZone;
30use \RuntimeException;
31use \UnexpectedValueException;
32use \XMLWriter;
33
34/**
35 * Write GPX files.
36 *
37 * @author Andy Street <andy@street.me.uk>
38 */
39class GPXWriter
40{
41
42 /**
43 * Who to credit as the document creator in the GPX root element.
44 *
45 * @var string
46 * @see http://www.topografix.com/GPX/1/1/#type_gpxType
47 */
48 protected $creator = 'libgpx';
49
50 /**
51 * The version of the GPX standard to generate.
52 *
53 * @var string
54 */
55 protected $format = '1.1';
56
57 /**
58 * Whether to indent the XML output for readability.
59 *
60 * @var boolean
61 */
62 protected $indent = true;
63
64 /**
65 * Whether to include milliseconds in timestamps.
66 *
67 * @var boolean
68 */
69 protected $milliseconds = true;
70
71 /**
72 * Fetch the name credited as the creator of the XML files.
73 *
74 * @return string The creator.
75 */
76 public function getCreator()
77 {
78 return $this->creator;
79 }
80
81 /**
82 * Set the name to be credited as the XML document creator.
83 *
84 * @param string $creator The name to credit as the creator.
85 * @return void
86 */
87 public function setCreator(string $creator)
88 {
89 $this->creator = $creator;
90 }
91
92 /**
93 * Fetch the GPX file format that this class generates.
94 *
95 * @return string The GPX file format.
96 */
97 public function getFormat()
98 {
99 return $this->format;
100 }
101
102 /**
103 * Set the GPX file format that this class generates.
104 *
105 * @param string $format Accepted values are "1.0" or "1.1".
106 * @return void
107 * @throws UnexpectedValueException If $format is not a known GPX version.
108 */
109 public function setFormat(string $format)
110 {
111 switch ($format) {
112 case '1.0':
113 case '1.1':
114 $this->format = $format;
115 break;
116 default:
117 throw new UnexpectedValueException(
118 sprintf('Unknown GPX version "%s"', $format)
119 );
120 }
121 }
122
123 /**
124 * Whether this class will indent output for readability.
125 *
126 * @return boolean If the output will be indented.
127 */
128 public function getIndent()
129 {
130 return $this->indent;
131 }
132
133 /**
134 * Set whether to indent output for readability.
135 *
136 * @param boolean $indent Whether to indent.
137 * @return void
138 */
139 public function setIndent(bool $indent)
140 {
141 $this->indent = $indent;
142 }
143
144 /**
145 * Whether timestamps are written to include milliseconds.
146 *
147 * @return boolean
148 */
149 public function getTimestampUseMilliseconds()
150 {
151 return $this->milliseconds;
152 }
153
154 /**
155 * Set whether to output milliseconds in timestamps.
156 *
157 * @param boolean $milliseconds Whether to use milliseconds.
158 * @return void
159 */
160 public function setTimestampUseMilliseconds(bool $milliseconds)
161 {
162 $this->milliseconds = $milliseconds;
163 }
164
165 /**
166 * Fetch an XML representation as a string.
167 *
168 * @param GPX $gpx The source for the XML.
169 * @return string An XML representation of the GPX object.
170 */
171 public function fetch(GPX $gpx)
172 {
173 $xml = new XMLWriter();
174 $xml->openMemory();
175 $this->write($gpx, $xml);
176 return $xml->outputMemory();
177 }
178
179 /**
180 * Write XML to standard out.
181 *
182 * @param GPX $gpx The source for the XML.
183 * @return void
184 */
185 public function output(GPX $gpx)
186 {
187 $xml = new XMLWriter();
188 $xml->openURI('php://output');
189 $this->write($gpx, $xml);
190 }
191
192 /**
193 * Write XML to a file.
194 *
195 * @param GPX $gpx The source for the XML.
196 * @param string $filename Where to write the XML.
197 * @return void
198 * @throws RuntimeException If the file cannot be written to.
199 */
200 public function save(GPX $gpx, string $filename)
201 {
202 $xml = new XMLWriter();
203 if (!$xml->openURI('php://output'))
204 throw RuntimeException(sprintf('Unable to write to "%s"', $filename));
205 $this->write($gpx, $xml);
206 }
207
208 /**
209 * Generate the XML.
210 *
211 * @param GPX $gpx The source for the XML.
212 * @param XMLWriter $xml The instance to use to generate the XML.
213 * @return void
214 */
215 protected function write(GPX $gpx, XMLWriter $xml)
216 {
217 $xml->setIndent($this->indent);
218 $xml->startDocument('1.0', 'UTF-8');
219 if ($this->format == '1.1') {
220 $namespace = libgpx::NAMESPACE_GPX_1_1;
221 $schema = libgpx::SCHEMA_GPX_1_1;
222 } else {
223 $namespace = libgpx::NAMESPACE_GPX_1_0;
224 $schema = libgpx::SCHEMA_GPX_1_0;
225 }
226 $xml->startElementNs(null, 'gpx', $namespace);
227 $xml->writeAttribute('version', $this->format);
228 $xml->writeAttribute('creator', $this->creator);
229 $xml->writeAttributeNS(
230 'xsi',
231 'schemaLocation',
232 libgpx::NAMESPACE_XMLSCHEMA,
233 $namespace . ' ' . $schema
234 );
235 $this->writeMetadata($gpx, $xml);
57c75891
AS
236 $waypoints = $gpx->getWaypoints(false);
237 if ($waypoints !== null) {
238 foreach ($waypoints as $wpt) {
239 $this->writePoint($wpt, 'wpt', $xml);
240 }
f528d248 241 }
88564339
AS
242 $xml->endElement();
243 $xml->endDocument();
244 $xml->flush();
245 }
246
247 /**
248 * Generate XML for Metadata elements.
249 *
250 * @param GPX $gpx The object to source the metadata from.
251 * @param XMLWriter $xml Where to write the metadata.
252 * @return void
253 */
254 protected function writeMetadata(GPX $gpx, XMLWriter $xml)
255 {
256 if ($this->format == '1.1')
257 $xml->startElement('metadata');
258 $name = $gpx->getName();
259 if (!empty($name))
260 $xml->writeElement('name', $name);
261 $desc = $gpx->getDesc();
262 if (!empty($desc))
263 $xml->writeElement('desc', $desc);
264 $author = $gpx->getAuthor();
265 if ($author instanceof Person)
266 $this->writePerson($author, $xml);
267 if ($this->format == '1.1') {
268 $copyright = $gpx->getCopyright();
269 if ($copyright instanceof Copyright)
270 $this->writeCopyright($copyright, $xml);
271 }
57c75891
AS
272 $links = $gpx->getLinks(false);
273 if ($links !== null) {
274 foreach($links as $link) {
275 $this->writeLink($link, $xml);
276 if ($this->format == '1.0')
277 break;
278 }
88564339
AS
279 }
280 $xml->writeElement('time', $this->createTimestamp(new DateTime()));
57c75891
AS
281 $keywords = $gpx->getKeywords(false);
282 if ($keywords !== null && !$keywords->isEmpty())
7c465a82
AS
283 $xml->writeElement(
284 'keywords',
285 implode(', ', $gpx->getKeywords()->toArray())
286 );
88564339
AS
287 $bounds = $gpx->getBounds();
288 if ($bounds instanceof Bounds) {
289 $xml->startElement('bounds');
290 $xml->writeAttribute('minlat', $bounds->getMinLat());
291 $xml->writeAttribute('minlon', $bounds->getMinLon());
292 $xml->writeAttribute('maxlat', $bounds->getMaxLat());
293 $xml->writeAttribute('maxlon', $bounds->getMaxLon());
294 $xml->endElement();
295 }
7c465a82 296 if ($this->format == '1.1') {
57c75891
AS
297 $extensions = $gpx->getMetadataExtensions(false);
298 if ($extensions !== null && !$extensions->isEmpty()) {
7c465a82
AS
299 $xml->startElement('extensions');
300 foreach ($extensions as $extension) {
301 $xml->writeRaw($extension);
302 }
303 $xml->endElement();
304 }
305 $xml->endElement(); // </metadata>
306 }
88564339
AS
307 }
308
309 /**
310 * Generate XML for GPX Person type.
311 *
312 * @param Person $person The person object to source data from.
313 * @param XMLWriter $xml Where to write the data.
314 * @return void
315 */
316 protected function writePerson(Person $person, XMLWriter $xml)
317 {
318 if ($this->format == '1.1') {
319 $xml->startElement('author');
320 $name = $person->getName();
321 if (!empty($name))
322 $xml->writeElement('name', $name);
323 $email = $person->getEmail();
324 if (!empty($email)) {
325 $email = explode('@', $email, 2);
326 $xml->startElement('email');
327 $xml->writeAttribute('id', $email[0]);
328 $xml->writeAttribute('domain', $email[1]);
329 $xml->endElement();
330 }
331 $link = $person->getLink();
332 if (!empty($link))
333 $this->writeLink($link, $xml);
334 $xml->endElement();
335 } else {
336 $name = $person->getName();
337 if (!empty($name))
338 $xml->writeElement('author', $name);
339 $email = $person->getEmail();
340 if (!empty($email))
341 $xml->writeElement('email', $email);
342 }
343 }
344
345 /**
346 * Generate XML for a GPX Link type.
347 *
348 * @param Link $link The link object to source data from.
349 * @param XMLWriter $xml Where to write the data.
350 * @return void
351 */
352 protected function writeLink(Link $link, XMLWriter $xml)
353 {
354 if ($this->format == '1.1') {
355 $xml->startElement('link');
356 $xml->writeAttribute('href', $link->getHref());
357 $text = $link->getText();
358 if (!empty($text))
359 $xml->writeElement('text', $text);
360 $type = $link->getType();
361 if (!empty($type))
362 $xml->writeElement('type', $type);
363 $xml->endElement();
364 } else {
365 $xml->writeElement('url', $link->getHref());
366 $text = $link->getText();
367 if (!empty($text))
368 $xml->writeElement('urlname', $text);
369 }
370 }
371
372 /**
373 * Generate XML for GPX Copyright type.
374 *
375 * @param Copyright $copyright Object to source copyright data from.
376 * @param XMLWriter $xml Where to write the data.
377 * @return void
378 */
379 protected function writeCopyright(Copyright $copyright, XMLWriter $xml)
380 {
381 $xml->startElement('copyright');
382 $xml->writeAttribute('author', $copyright->getAuthor());
383 $year = $copyright->getYear();
384 if (!empty($year))
385 $xml->writeElement('year', $year);
386 $license = $copyright->getLicense();
387 if (!empty($license))
388 $xml->writeElement('license', $license);
389 $xml->endElement();
390 }
391
392 /**
393 * Create an ISO 8601 timestamp.
394 *
395 * @param DateTimeInterface $timestamp The timestamp to format.
396 * @param boolean $millis Include milliseconds.
397 * @return string A timestamp.
398 */
399 protected function createTimestamp(
400 DateTimeInterface $timestamp,
401 bool $millis = null
402 ) {
403 if ($millis === null)
404 $millis = $this->milliseconds;
405 if (!$timestamp->getTimezone()->getOffset($timestamp))
406 $timestamp = $timestamp->setTimezone(new DateTimeZone('UTC'));
407 $format = 'Y-m-d\TH:i:s' . ($millis ? '.v\Z' : '\Z');
408 return $timestamp->format($format);
409 }
410
f528d248
AS
411 /**
412 * Generate XML for GPX wpt type.
413 *
414 * @param Point $point The point to write.
415 * @param string $element The XML element name (e.g. <wpt>)
416 * @param XMLWriter $xml Where to write the point.
417 * @return void
418 */
419 protected function writePoint(Point $point, string $element, XMLWriter $xml)
420 {
421 $xml->startElement($element);
422 $xml->writeAttribute('lat', $point->getLatitude());
423 $xml->writeAttribute('lon', $point->getLongitude());
424 $val = $point->getEle();
425 if ($val !== null)
426 $xml->writeElement('ele', $val);
427 $val = $point->getTime();
428 if ($val !== null)
429 $xml->writeElement('time', $this->createTimestamp($val));
430 $val = $point->getMagvar();
431 if ($val !== null)
432 $xml->writeElement('magvar', $val);
433 $val = $point->getGeoidHeight();
434 if ($val !== null)
435 $xml->writeElement('geoidheight', $val);
436 $var = $point->getName();
437 if ($var !== null)
438 $xml->writeElement('name', $var);
439 $var = $point->getComment();
440 if ($var !== null)
441 $xml->writeElement('cmt', $var);
442 $var = $point->getDescription();
443 if ($var !== null)
444 $xml->writeElement('desc', $var);
445 $var = $point->getSource();
446 if ($var !== null)
447 $xml->writeElement('src', $var);
448 $var = $point->getLinks(false);
449 if ($var !== null) {
450 foreach($var as $link) {
451 $this->writeLink($link, $xml);
452 if ($this->format == '1.0')
453 break;
454 }
455 }
456 $var = $point->getSymbol();
457 if ($var !== null)
458 $xml->writeElement('sym', $var);
459 $var = $point->getType();
460 if ($var !== null)
461 $xml->writeElement('type', $var);
462 $var = $point->getFix();
463 if ($var !== null)
464 $xml->writeElement('fix', $var);
465 $var = $point->getSatellites();
466 if ($var !== null)
467 $xml->writeElement('sat', $var);
468 $var = $point->getHdop();
469 if ($var !== null)
470 $xml->writeElement('hdop', $var);
471 $var = $point->getVdop();
472 if ($var !== null)
473 $xml->writeElement('vdop', $var);
474 $var = $point->getPdop();
475 if ($var !== null)
476 $xml->writeElement('pdop', $var);
477 $var = $point->getAgeOfDGPSData();
478 if ($var !== null)
479 $xml->writeElement('ageofdgpsdata', $var);
480 $var = $point->getDGPSId();
481 if ($var !== null)
482 $xml->writeElement('dgpsid', $var);
483 $var = $point->getExtensions(false);
484 if ($var !== null && !$var->isEmpty()) {
485 if ($this->format == '1.1')
486 $xml->startElement('extensions');
487 foreach ($var as $extension) {
488 $xml->writeRaw($extension);
489 }
490 if ($this->format == '1.1')
491 $xml->endElement();
492 }
493 $xml->endElement();
494 }
495
88564339 496}