Test Failed
Push — master ( c734ca...dd47e7 )
by Swen
03:31
created

GPXWriter::processGeometryData()   A

Complexity

Conditions 4
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 6
c 1
b 0
f 0
dl 0
loc 11
rs 10
cc 4
nc 2
nop 3
1
<?php
2
namespace geoPHP\Adapter;
3
4
use geoPHP\Geometry\Collection;
5
use geoPHP\Geometry\Geometry;
6
use geoPHP\Geometry\GeometryCollection;
7
use geoPHP\Geometry\Point;
8
use geoPHP\Geometry\LineString;
9
use geoPHP\Geometry\MultiLineString;
10
11
/*
12
 * Copyright (c) Patrick Hayes
13
 *
14
 * This code is open-source and licenced under the Modified BSD License.
15
 * For the full copyright and license information, please view the LICENSE
16
 * file that was distributed with this source code.
17
 */
18
19
/**
20
 * PHP Geometry/GPX encoder/decoder
21
 */
22
trait GPXWriter
23
{
24
25
    /**
26
     * @var string Name-space string. eg 'georss:'
27
     */
28
    private $nss = '';
29
30
    /**
31
     * @var GpxTypes
32
     */
33
    protected $gpxTypes;
34
35
    /**
36
     * Serialize geometries into a GPX string.
37
     *
38
     * @param Geometry|GeometryCollection $geometry
39
     * @param string $namespace
40
     * @param array<array> $allowedElements Which elements can be added to each GPX type
41
     *              If not specified, every element defined in the GPX specification can be added
42
     *              Can be overwritten with an associative array, with type name in keys.
43
     *              eg.: ['wptType' => ['ele', 'name'], 'trkptType' => ['ele'], 'metadataType' => null]
44
     * @return string The GPX string representation of the input geometries
45
     */
46
    public function write(Geometry $geometry, string $namespace = '', array $allowedElements = []): string
47
    {
48
        $namespace = trim($namespace);
49
        if (!empty($namespace)) {
50
            $this->nss = $namespace . ':';
51
        }
52
        $this->gpxTypes = new GpxTypes($allowedElements);
53
54
        return
55
            '<?xml version="1.0" encoding="UTF-8"?>
56
<' . $this->nss . 'gpx creator="geoPHP" version="1.1"
57
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
58
  xmlns="http://www.topografix.com/GPX/1/1"
59
  xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd" >
60
61
' . $this->geometryToGPX($geometry) .
62
            '</' . $this->nss . 'gpx>
63
';
64
    }
65
66
    /**
67
     * @param Geometry|Collection $geometry
68
     * @return string
69
     */
70
    protected function geometryToGPX($geometry): string
71
    {
72
        switch ($geometry->geometryType()) {
73
            case Geometry::POINT:
74
                /** @var Point $geometry */
75
                return $this->pointToGPX($geometry);
76
            case Geometry::LINESTRING:
77
            case Geometry::MULTI_LINESTRING:
78
                /** @var LineString $geometry */
79
                return $this->linestringToGPX($geometry);
80
            case Geometry::POLYGON:
81
            case Geometry::MULTI_POINT:
82
            case Geometry::MULTI_POLYGON:
83
            case Geometry::GEOMETRY_COLLECTION:
84
                /** @var GeometryCollection $geometry */
85
                return $this->collectionToGPX($geometry);
86
        }
87
        return '';
88
    }
89
90
    /**
91
     * @param Point $geom
92
     * @param string $tag Can be "wpt", "trkpt" or "rtept"
93
     * @return string
94
     */
95
    private function pointToGPX($geom, $tag = 'wpt'): string
96
    {
97
        if ($geom->isEmpty() || ($tag === 'wpt' && !in_array($tag, $this->gpxTypes->get('gpxType')))) {
98
            return '';
99
        }
100
        $indent = $tag === 'trkpt' ? "\t\t" : ($tag === 'rtept' ? "\t" : '');
101
102
        if ($geom->hasZ() || $geom->getData() !== null) {
103
            $node = $indent . "<" . $this->nss . $tag . " lat=\"" . $geom->getY() . "\" lon=\"" . $geom->getX() . "\">\n";
104
            if ($geom->hasZ()) {
105
                $geom->setData('ele', $geom->getZ());
106
            }
107
            $node .= self::processGeometryData($geom, $this->gpxTypes->get($tag . 'Type'), $indent . "\t") .
108
                $indent . "</" . $this->nss . $tag . ">\n";
109
            if ($geom->hasZ()) {
110
                $geom->setData('ele', null);
111
            }
112
            return $node;
113
        }
114
        return $indent . "<" . $this->nss . $tag . " lat=\"" . $geom->getY() . "\" lon=\"" . $geom->getX() . "\" />\n";
115
    }
116
117
    /**
118
     * Writes a LineString or MultiLineString to the GPX
119
     *
120
     * The (Multi)LineString will be included in a <trk></trk> block
121
     * The LineString or each LineString of the MultiLineString will be in <trkseg> </trkseg> inside the <trk>
122
     *
123
     * @param LineString|MultiLineString $geom
124
     * @return string
125
     */
126
    private function linestringToGPX($geom): string
127
    {
128
        $isTrack = $geom->getData('gpxType') === 'route' ? false : true;
129
        if ($geom->isEmpty() || !in_array($isTrack ? 'trk' : 'rte', $this->gpxTypes->get('gpxType'))) {
130
            return '';
131
        }
132
133
        if ($isTrack) { // write as <trk>
134
            /** @noinspection SpellCheckingInspection */
135
            $gpx = "<" . $this->nss . "trk>\n" . self::processGeometryData($geom, $this->gpxTypes->get('trkType'));
136
            $components = $geom->geometryType() === 'LineString' ? [$geom] : $geom->getComponents();
137
            foreach ($components as $lineString) {
138
                $gpx .= "\t<" . $this->nss . "trkseg>\n";
139
                foreach ($lineString->getPoints() as $point) {
140
                    $gpx .= $this->pointToGPX($point, 'trkpt');
141
                }
142
                $gpx .= "\t</" . $this->nss . "trkseg>\n";
143
            }
144
            /** @noinspection SpellCheckingInspection */
145
            $gpx .= "</" . $this->nss . "trk>\n";
146
        } else { // write as <rte>
147
            /** @noinspection SpellCheckingInspection */
148
            $gpx = "<" . $this->nss . "rte>\n" . self::processGeometryData($geom, $this->gpxTypes->get('rteType'));
149
            foreach ($geom->getPoints() as $point) {
150
                $gpx .= $this->pointToGPX($point, 'rtept');
151
            }
152
            /** @noinspection SpellCheckingInspection */
153
            $gpx .= "</" . $this->nss . "rte>\n";
154
        }
155
156
        return $gpx;
157
    }
158
159
    /**
160
     * @param Collection $geometry
161
     * @return string
162
     */
163
    public function collectionToGPX($geometry): string
164
    {
165
        $metadata = self::processGeometryData($geometry, $this->gpxTypes->get('metadataType'));
166
        $metadata = empty($metadata) || !in_array('metadataType', $this->gpxTypes->get('gpxType')) ?
167
            '' : "<metadata>\n" . $metadata . "</metadata>\n\n";
168
        $wayPoints = $routes = $tracks = "";
169
170
        foreach ($geometry->getComponents() as $component) {
171
            $geometryType = $component->geometryType();
172
            
173
            if (strpos($geometryType, 'Point') !== false) {
174
                $wayPoints .= $this->geometryToGPX($component);
175
            } elseif (strpos($geometryType, 'Linestring') !== false) {
176
                if ($component->getData('gpxType') === 'route') {
177
                    $routes .= $this->geometryToGPX($component);
178
                } else {
179
                    $tracks .= $this->geometryToGPX($component);
180
                }
181
            } else {
182
                return $this->geometryToGPX($component);
183
            }
184
        }
185
186
        return $metadata . $wayPoints . $routes . $tracks;
187
    }
188
189
    /**
190
     * @param Geometry $geometry
191
     * @param string[] $tagList Allowed tags
192
     * @param string $indent
193
     * @return string
194
     */
195
    protected static function processGeometryData($geometry, $tagList, $indent = "\t"): string
196
    {
197
        $tags = '';
198
        if ($geometry->getData() !== null) {
199
            foreach ($tagList as $tagName) {
200
                if ($geometry->hasDataProperty($tagName)) {
201
                    $tags .= self::createNodes($tagName, $geometry->getData($tagName), $indent) . "\n";
202
                }
203
            }
204
        }
205
        return $tags;
206
    }
207
208
    /**
209
     * @param string $tagName
210
     * @param string|array<array> $value
211
     * @param string $indent
212
     * @return string
213
     */
214
    protected static function createNodes($tagName, $value, $indent): string
215
    {
216
        $attributes = '';
217
        if (!is_array($value)) {
218
            $returnValue = $value;
219
        } else {
220
            $returnValue = '';
221
            if (array_key_exists('@attributes', $value)) {
222
                $attributes = '';
223
                foreach ($value['@attributes'] as $attributeName => $attributeValue) {
224
                    $attributes .= ' ' . $attributeName . '="' . $attributeValue . '"';
225
                }
226
                unset($value['@attributes']);
227
            }
228
            foreach ($value as $subKey => $subValue) {
229
                $returnValue .= "\n" . self::createNodes($subKey, $subValue, $indent . "\t") . "\n" . $indent;
230
            }
231
        }
232
        return $indent . "<{$tagName}{$attributes}>{$returnValue}</{$tagName}>";
233
    }
234
}
235