Passed
Push — master ( 58539d...96b0d3 )
by Mark
01:31
created

KML   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 249
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 58
eloc 137
dl 0
loc 249
rs 4.5599
c 0
b 0
f 0

How to fix   Complexity   

Complex Class

Complex classes like KML often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use KML, and based on these observations, apply Extract Interface, too.

1
<?php
2
/*
3
 * Copyright (c) Patrick Hayes
4
 * Copyright (c) 2010-2011, Arnaud Renevier
5
 *
6
 * This code is open-source and licenced under the Modified BSD License.
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
/**
12
 * PHP Geometry/KML encoder/decoder
13
 *
14
 * Mainly inspired/adapted from OpenLayers( http://www.openlayers.org )
15
 *   Openlayers/format/WKT.js
16
 *
17
 * @package    sfMapFishPlugin
18
 * @subpackage GeoJSON
19
 * @author     Camptocamp <[email protected]>
20
 */
21
class KML extends GeoAdapter
22
{
23
  private $namespace = FALSE;
24
  private $nss = ''; // Name-space string. eg 'georss:'
25
26
  /**
27
   * Read KML string into geometry objects
28
   *
29
   * @param string $kml A KML string
30
   *
31
   * @return Geometry|GeometryCollection
32
   */
33
  public function read($kml) {
34
    return $this->geomFromText($kml);
35
  }
36
37
  /**
38
   * Serialize geometries into a KML string.
39
   *
40
   * @param Geometry $geometry
41
   *
42
   * @return string The KML string representation of the input geometries
43
   */
44
  public function write(Geometry $geometry, $namespace = FALSE) {
45
    if ($namespace) {
46
      $this->namespace = $namespace;
47
      $this->nss = $namespace.':';
48
    }
49
    return $this->geometryToKML($geometry);
50
  }
51
52
  public function geomFromText($text) {
53
    // Change to lower-case and strip all CDATA
54
    $text = mb_strtolower($text, mb_detect_encoding($text));
55
    $text = preg_replace('/<!\[cdata\[(.*?)\]\]>/s','',$text);
56
57
    // Load into DOMDocument
58
    $xmlobj = new DOMDocument();
59
    @$xmlobj->loadXML($text);
60
    if ($xmlobj === false) {
61
      throw new Exception("Invalid KML: ". $text);
62
    }
63
64
    $this->xmlobj = $xmlobj;
65
    try {
66
      $geom = $this->geomFromXML();
67
    } catch(InvalidText $e) {
68
        throw new Exception("Cannot Read Geometry From KML: ". $text);
69
    } catch(Exception $e) {
70
        throw $e;
71
    }
72
73
    return $geom;
74
  }
75
76
  protected function geomFromXML() {
77
    $geometries = array();
78
    $geom_types = geoPHP::geometryList();
79
    $placemark_elements = $this->xmlobj->getElementsByTagName('placemark');
80
    if ($placemark_elements->length) {
81
      foreach ($placemark_elements as $placemark) {
82
        foreach ($placemark->childNodes as $child) {
83
          // Node names are all the same, except for MultiGeometry, which maps to GeometryCollection
84
          $node_name = $child->nodeName == 'multigeometry' ? 'geometrycollection' : $child->nodeName;
85
          if (array_key_exists($node_name, $geom_types)) {
86
            $function = 'parse'.$geom_types[$node_name];
87
            $geometries[] = $this->$function($child);
88
          }
89
        }
90
      }
91
    }
92
    else {
93
      // The document does not have a placemark, try to create a valid geometry from the root element
94
      $node_name = $this->xmlobj->documentElement->nodeName == 'multigeometry' ? 'geometrycollection' : $this->xmlobj->documentElement->nodeName;
95
      if (array_key_exists($node_name, $geom_types)) {
96
        $function = 'parse'.$geom_types[$node_name];
97
        $geometries[] = $this->$function($this->xmlobj->documentElement);
98
      }
99
    }
100
    return geoPHP::geometryReduce($geometries);
101
  }
102
103
  protected function childElements($xml, $nodename = '') {
104
    $children = array();
105
    if ($xml->childNodes) {
106
      foreach ($xml->childNodes as $child) {
107
        if ($child->nodeName == $nodename) {
108
          $children[] = $child;
109
        }
110
      }
111
    }
112
    return $children;
113
  }
114
115
  protected function parsePoint($xml) {
116
    $coordinates = $this->_extractCoordinates($xml);
117
    if (!empty($coordinates)) {
118
      return new Point($coordinates[0][0],$coordinates[0][1]);
119
    }
120
    else {
121
      return new Point();
122
    }
123
  }
124
125
  protected function parseLineString($xml) {
126
    $coordinates = $this->_extractCoordinates($xml);
127
    $point_array = array();
128
    foreach ($coordinates as $set) {
129
      $point_array[] = new Point($set[0],$set[1]);
130
    }
131
    return new LineString($point_array);
132
  }
133
134
  protected function parsePolygon($xml) {
135
    $components = array();
136
137
    $outer_boundary_element_a = $this->childElements($xml, 'outerboundaryis');
138
    if (empty($outer_boundary_element_a)) {
139
      return new Polygon(); // It's an empty polygon
140
    }
141
    $outer_boundary_element = $outer_boundary_element_a[0];
142
    $outer_ring_element_a = $this->childElements($outer_boundary_element, 'linearring');
143
    $outer_ring_element = $outer_ring_element_a[0];
144
    $components[] = $this->parseLineString($outer_ring_element);
145
146
    if (count($components) != 1) {
147
      throw new Exception("Invalid KML");
148
    }
149
150
    $inner_boundary_element_a = $this->childElements($xml, 'innerboundaryis');
151
    if (count($inner_boundary_element_a)) {
152
      foreach ($inner_boundary_element_a as $inner_boundary_element) {
153
        foreach ($this->childElements($inner_boundary_element, 'linearring') as $inner_ring_element) {
154
          $components[] = $this->parseLineString($inner_ring_element);
155
        }
156
      }
157
    }
158
159
    return new Polygon($components);
160
  }
161
162
  protected function parseGeometryCollection($xml) {
163
    $components = array();
164
    $geom_types = geoPHP::geometryList();
165
    foreach ($xml->childNodes as $child) {
166
      $nodeName = ($child->nodeName == 'linearring') ? 'linestring' : $child->nodeName;
167
      if (array_key_exists($nodeName, $geom_types)) {
168
        $function = 'parse'.$geom_types[$nodeName];
169
        $components[] = $this->$function($child);
170
      }
171
    }
172
    return new GeometryCollection($components);
173
  }
174
175
  protected function _extractCoordinates($xml) {
176
    $coord_elements = $this->childElements($xml, 'coordinates');
177
    $coordinates = array();
178
    if (count($coord_elements)) {
179
      $coord_sets = explode(' ', preg_replace('/[\r\n]+/', ' ', $coord_elements[0]->nodeValue));
180
      foreach ($coord_sets as $set_string) {
181
        $set_string = trim($set_string);
182
        if ($set_string) {
183
          $set_array = explode(',',$set_string);
184
          if (count($set_array) >= 2) {
185
            $coordinates[] = $set_array;
186
          }
187
        }
188
      }
189
    }
190
191
    return $coordinates;
192
  }
193
194
  private function geometryToKML($geom) {
195
    $type = strtolower($geom->getGeomType());
196
    switch ($type) {
197
      case 'point':
198
        return $this->pointToKML($geom);
199
        break;
200
      case 'linestring':
201
        return $this->linestringToKML($geom);
202
        break;
203
      case 'polygon':
204
        return $this->polygonToKML($geom);
205
        break;
206
      case 'multipoint':
207
      case 'multilinestring':
208
      case 'multipolygon':
209
      case 'geometrycollection':
210
        return $this->collectionToKML($geom);
211
        break;
212
    }
213
  }
214
215
  private function pointToKML($geom) {
216
    $out = '<'.$this->nss.'Point>';
217
    if (!$geom->isEmpty()) {
218
      $out .= '<'.$this->nss.'coordinates>'.$geom->getX().",".$geom->getY().'</'.$this->nss.'coordinates>';
219
    }
220
    $out .= '</'.$this->nss.'Point>';
221
    return $out;
222
  }
223
224
  private function linestringToKML($geom, $type = FALSE) {
225
    if (!$type) {
226
      $type = $geom->getGeomType();
227
    }
228
229
    $str = '<'.$this->nss . $type .'>';
230
231
    if (!$geom->isEmpty()) {
232
      $str .= '<'.$this->nss.'coordinates>';
233
      $i=0;
234
      foreach ($geom->getComponents() as $comp) {
235
        if ($i != 0) $str .= ' ';
236
        $str .= $comp->getX() .','. $comp->getY();
237
        $i++;
238
      }
239
240
      $str .= '</'.$this->nss.'coordinates>';
241
    }
242
243
    $str .= '</'. $this->nss . $type .'>';
244
245
    return $str;
246
  }
247
248
  public function polygonToKML($geom) {
249
    $components = $geom->getComponents();
250
    $str = '';
251
    if (!empty($components)) {
252
      $str = '<'.$this->nss.'outerBoundaryIs>' . $this->linestringToKML($components[0], 'LinearRing') . '</'.$this->nss.'outerBoundaryIs>';
253
      foreach (array_slice($components, 1) as $comp) {
254
        $str .= '<'.$this->nss.'innerBoundaryIs>' . $this->linestringToKML($comp) . '</'.$this->nss.'innerBoundaryIs>';
255
      }
256
    }
257
258
    return '<'.$this->nss.'Polygon>'. $str .'</'.$this->nss.'Polygon>';
259
  }
260
261
  public function collectionToKML($geom) {
262
    $components = $geom->getComponents();
263
    $str = '<'.$this->nss.'MultiGeometry>';
264
    foreach ($geom->getComponents() as $comp) {
265
      $sub_adapter = new KML();
266
      $str .= $sub_adapter->write($comp);
267
    }
268
269
    return $str .'</'.$this->nss.'MultiGeometry>';
270
  }
271
272
}
273