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

geoPHP::detectFormat()   D

Complexity

Conditions 21
Paths 31

Size

Total Lines 74
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 35
dl 0
loc 74
rs 4.1666
c 0
b 0
f 0
cc 21
nc 31
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/*
3
 * (c) Patrick Hayes
4
 *
5
 * This code is open-source and licenced under the Modified BSD License.
6
 * For the full copyright and license information, please view the LICENSE
7
 * file that was distributed with this source code.
8
 */
9
10
// Adapters
11
include_once("lib/adapters/GeoAdapter.class.php"); // Abtract class
12
include_once("lib/adapters/GeoJSON.class.php");
13
include_once("lib/adapters/WKT.class.php");
14
include_once("lib/adapters/EWKT.class.php");
15
include_once("lib/adapters/WKB.class.php");
16
include_once("lib/adapters/EWKB.class.php");
17
include_once("lib/adapters/KML.class.php");
18
include_once("lib/adapters/GPX.class.php");
19
include_once("lib/adapters/GeoRSS.class.php");
20
include_once("lib/adapters/GoogleGeocode.class.php");
21
include_once("lib/adapters/GeoHash.class.php");
22
23
// Geometries
24
include_once("lib/geometry/Geometry.class.php"); // Abtract class
25
include_once("lib/geometry/Point.class.php");
26
include_once("lib/geometry/Collection.class.php"); // Abtract class
27
include_once("lib/geometry/LineString.class.php");
28
include_once("lib/geometry/MultiPoint.class.php");
29
include_once("lib/geometry/Polygon.class.php");
30
include_once("lib/geometry/MultiLineString.class.php");
31
include_once("lib/geometry/MultiPolygon.class.php");
32
include_once("lib/geometry/GeometryCollection.class.php");
33
34
class geoPHP
35
{
36
37
  static function version() {
38
    return '1.2';
39
  }
40
41
  // geoPHP::load($data, $type, $other_args);
42
  // if $data is an array, all passed in values will be combined into a single geometry
43
  static function load() {
44
    $args = func_get_args();
45
46
    $data = array_shift($args);
47
    $type = array_shift($args);
48
49
    $type_map = geoPHP::getAdapterMap();
50
51
    // Auto-detect type if needed
52
    if (!$type) {
53
      // If the user is trying to load a Geometry from a Geometry... Just pass it back
54
      if (is_object($data)) {
55
        if ($data instanceOf Geometry) return $data;
56
      }
57
      
58
      $detected = geoPHP::detectFormat($data);
59
      if (!$detected) {
60
        return FALSE;
61
      }
62
      
63
      $format = explode(':', $detected);
64
      $type = array_shift($format);
65
      $args = $format;
66
    }
67
68
    $processor_type = $type_map[$type];
69
70
    if (!$processor_type) {
71
      throw new exception('geoPHP could not find an adapter of type '.htmlentities($type));
72
    }
73
74
    $processor = new $processor_type();
75
76
    // Data is not an array, just pass it normally
77
    if (!is_array($data)) {
78
      $result = call_user_func_array(array($processor, "read"), array_merge(array($data), $args));
79
    }
80
    // Data is an array, combine all passed in items into a single geometry
81
    else {
82
      $geoms = array();
83
      foreach ($data as $item) {
84
        $geoms[] = call_user_func_array(array($processor, "read"), array_merge(array($item), $args));
85
      }
86
      $result = geoPHP::geometryReduce($geoms);
87
    }
88
89
    return $result;
90
  }
91
92
  static function getAdapterMap() {
93
    return array (
94
      'wkt' =>  'WKT',
95
      'ewkt' => 'EWKT',
96
      'wkb' =>  'WKB',
97
      'ewkb' => 'EWKB',
98
      'json' => 'GeoJSON',
99
      'geojson' => 'GeoJSON',
100
      'kml' =>  'KML',
101
      'gpx' =>  'GPX',
102
      'georss' => 'GeoRSS',
103
      'google_geocode' => 'GoogleGeocode',
104
      'geohash' => 'GeoHash',
105
    );
106
  }
107
108
  static function geometryList() {
109
    return array(
110
      'point' => 'Point',
111
      'linestring' => 'LineString',
112
      'polygon' => 'Polygon',
113
      'multipoint' => 'MultiPoint',
114
      'multilinestring' => 'MultiLineString',
115
      'multipolygon' => 'MultiPolygon',
116
      'geometrycollection' => 'GeometryCollection',
117
    );
118
  }
119
120
  static function geosInstalled($force = NULL) {
121
    static $geos_installed = NULL;
122
    if ($force !== NULL) $geos_installed = $force;
123
    if ($geos_installed !== NULL) {
124
      return $geos_installed;
125
    }
126
    $geos_installed = class_exists('GEOSGeometry');
127
    return $geos_installed;
128
  }
129
130
  static function geosToGeometry($geos) {
131
    if (!geoPHP::geosInstalled()) {
132
      return NULL;
133
    }
134
    $wkb_writer = new GEOSWKBWriter();
135
    $wkb = $wkb_writer->writeHEX($geos);
136
    $geometry = geoPHP::load($wkb, 'wkb', TRUE);
137
    if ($geometry) {
138
      $geometry->setGeos($geos);
139
      return $geometry;
140
    }
141
  }
142
143
  // Reduce a geometry, or an array of geometries, into their 'lowest' available common geometry.
144
  // For example a GeometryCollection of only points will become a MultiPoint
145
  // A multi-point containing a single point will return a point.
146
  // An array of geometries can be passed and they will be compiled into a single geometry
147
  static function geometryReduce($geometry) {
148
    // If it's an array of one, then just parse the one
149
    if (is_array($geometry)) {
150
      if (empty($geometry)) return FALSE;
151
      if (count($geometry) == 1) return geoPHP::geometryReduce(array_shift($geometry));
152
    }
153
154
    // If the geometry cannot even theoretically be reduced more, then pass it back
155
    if (gettype($geometry) == 'object') {
156
      $passbacks = array('Point','LineString','Polygon');
157
      if (in_array($geometry->geometryType(),$passbacks)) {
158
        return $geometry;
159
      }
160
    }
161
162
    // If it is a mutlti-geometry, check to see if it just has one member
163
    // If it does, then pass the member, if not, then just pass back the geometry
164
    if (gettype($geometry) == 'object') {
165
      $simple_collections = array('MultiPoint','MultiLineString','MultiPolygon');
166
      if (in_array(get_class($geometry),$passbacks)) {
167
        $components = $geometry->getComponents();
168
        if (count($components) == 1) {
169
          return $components[0];
170
        }
171
        else {
172
          return $geometry;
173
        }
174
      }
175
    }
176
177
    // So now we either have an array of geometries, a GeometryCollection, or an array of GeometryCollections
178
    if (!is_array($geometry)) {
179
      $geometry = array($geometry);
180
    }
181
182
    $geometries = array();
183
    $geom_types = array();
184
185
    $collections = array('MultiPoint','MultiLineString','MultiPolygon','GeometryCollection');
186
187
    foreach ($geometry as $item) {
188
      if ($item) {
189
        if (in_array(get_class($item), $collections)) {
190
          foreach ($item->getComponents() as $component) {
191
            $geometries[] = $component;
192
            $geom_types[] = $component->geometryType();
193
          }
194
        }
195
        else {
196
          $geometries[] = $item;
197
          $geom_types[] = $item->geometryType();
198
        }
199
      }
200
    }
201
202
    $geom_types = array_unique($geom_types);
203
    
204
    if (empty($geom_types)) {
205
      return FALSE;
206
    }
207
208
    if (count($geom_types) == 1) {
209
      if (count($geometries) == 1) {
210
        return $geometries[0];
211
      }
212
      else {
213
        $class = 'Multi'.$geom_types[0];
214
        return new $class($geometries);
215
      }
216
    }
217
    else {
218
      return new GeometryCollection($geometries);
219
    }
220
  }
221
222
  // Detect a format given a value. This function is meant to be SPEEDY.
223
  // It could make a mistake in XML detection if you are mixing or using namespaces in weird ways (ie, KML inside an RSS feed)
224
  static function detectFormat(&$input) {
225
    $mem = fopen('php://memory', 'r+');
226
    fwrite($mem, $input, 11); // Write 11 bytes - we can detect the vast majority of formats in the first 11 bytes
227
    fseek($mem, 0);
228
229
    $bytes = unpack("c*", fread($mem, 11));
230
231
    // If bytes is empty, then we were passed empty input
232
    if (empty($bytes)) return FALSE;
233
234
    // First char is a tab, space or carriage-return. trim it and try again
235
    if ($bytes[1] == 9 || $bytes[1] == 10 || $bytes[1] == 32) {
236
      $ltinput = ltrim($input);
237
      return geoPHP::detectFormat($ltinput);
238
    }
239
240
    // Detect WKB or EWKB -- first byte is 1 (little endian indicator)
241
    if ($bytes[1] == 1) {
242
      // If SRID byte is TRUE (1), it's EWKB
243
      if ($bytes[5]) return 'ewkb';
244
      else return 'wkb';
245
    }
246
247
    // Detect HEX encoded WKB or EWKB (PostGIS format) -- first byte is 48, second byte is 49 (hex '01' => first-byte = 1)
248
    if ($bytes[1] == 48 && $bytes[2] == 49) {
249
      // The shortest possible WKB string (LINESTRING EMPTY) is 18 hex-chars (9 encoded bytes) long
250
      // This differentiates it from a geohash, which is always shorter than 18 characters.
251
      if (strlen($input) >= 18) {
252
        //@@TODO: Differentiate between EWKB and WKB -- check hex-char 10 or 11 (SRID bool indicator at encoded byte 5)
253
        return 'ewkb:1';
254
      }
255
    }
256
257
    // Detect GeoJSON - first char starts with {
258
    if ($bytes[1] == 123) {
259
      return 'json';
260
    }
261
262
    // Detect EWKT - first char is S
263
    if ($bytes[1] == 83) {
264
      return 'ewkt';
265
    }
266
267
    // Detect WKT - first char starts with P (80), L (76), M (77), or G (71)
268
    $wkt_chars = array(80, 76, 77, 71);
269
    if (in_array($bytes[1], $wkt_chars)) {
270
      return 'wkt';
271
    }
272
273
    // Detect XML -- first char is <
274
    if ($bytes[1] == 60) {
275
      // grab the first 256 characters
276
      $string = substr($input, 0, 256);
277
      if (strpos($string, '<kml') !== FALSE)        return 'kml';
278
      if (strpos($string, '<coordinate') !== FALSE) return 'kml';
279
      if (strpos($string, '<gpx') !== FALSE)        return 'gpx';
280
      if (strpos($string, '<georss') !== FALSE)     return 'georss';
281
      if (strpos($string, '<rss') !== FALSE)        return 'georss';
282
      if (strpos($string, '<feed') !== FALSE)       return 'georss';
283
    }
284
285
    // We need an 8 byte string for geohash and unpacked WKB / WKT
286
    fseek($mem, 0);
287
    $string = trim(fread($mem, 8));
288
289
    // Detect geohash - geohash ONLY contains lowercase chars and numerics
290
    preg_match('/[a-z0-9]+/', $string, $matches);
291
    if ($matches[0] == $string) {
292
      return 'geohash';
293
    }
294
295
    // What do you get when you cross an elephant with a rhino?
296
    // http://youtu.be/RCBn5J83Poc
297
    return FALSE;
298
  }
299
300
}
301