Test Failed
Push — master ( 201e94...68876a )
by
unknown
07:48
created

GIS::__get()   C

Complexity

Conditions 14
Paths 14

Size

Total Lines 43
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 14.2288

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 14
eloc 31
c 1
b 0
f 0
nc 14
nop 1
dl 0
loc 43
ccs 17
cts 19
cp 0.8947
crap 14.2288
rs 6.2666

How to fix   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
namespace Smindel\GIS;
4
5
use SilverStripe\Core\Config\Config;
6
use SilverStripe\Core\Config\Configurable;
7
use SilverStripe\Core\Injector\Injectable;
8
use SilverStripe\ORM\DB;
9
use Smindel\GIS\ORM\FieldType\DBGeography;
10
use Smindel\GIS\ORM\FieldType\DBGeometry;
11
use proj4php\Proj4php;
12
use proj4php\Proj;
13
use proj4php\Point;
14
use Exception;
15
16
/**
17
 * @property string $array
18
 * @property string $ewkt
19
 * @property string $wkt
20
 * @property string $srid
21
 * @property string $type
22
 * @property string $coordinates
23
 */
24
class GIS
25
{
26
    use Configurable;
27
28
    use Injectable;
29
30
    const WKT_PATTERN = '/^(([A-Z]+)\s*(\(.+\)))$/i';
31
    const EWKT_PATTERN = '/^SRID=(\d+);(([A-Z]+)\s*(\(.+\)))$/i';
32
33
    const TYPES = [
34
        'point' => 'Point',
35
        'linestring' => 'LineString',
36
        'polygon' => 'Polygon',
37
        'multipoint' => 'MultiPoint',
38
        'multilinestring' => 'MultiLineString',
39
        'multipolygon' => 'MultiPolygon',
40
        'geometrycollection' => 'GeometryCollection'
41
    ];
42
43
    private static $default_srid = 4326;
44
45
    protected $value;
46
47
    /**
48
     * Constructor
49
     *
50
     * @param $value mixed geo value:
51
     *      string: wkt (default srid) or ewkt
52
     *      array: just coordinates (default srid, autodetect shape) or assoc with type, srid and coordinates
53
     *      GIS: extract value
54
     *      DBGeography: extract value
55 28
     */
56
    public function __construct($value)
57 28
    {
58 3
        DB::get_schema()->initialise();
0 ignored issues
show
Bug introduced by
The method initialise() does not exist on SilverStripe\ORM\Connect\DBSchemaManager. It seems like you code against a sub-type of SilverStripe\ORM\Connect\DBSchemaManager such as Smindel\GIS\ORM\MySQLGISSchemaManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

58
        DB::get_schema()->/** @scrutinizer ignore-call */ initialise();
Loading history...
59 28
        if ($value instanceof GIS) {
60
            $this->value = $value->value;
61 28
        } else if ($value instanceof DBGeography) {
62
            $this->value = $value->getValue();
63
        } else if (is_string($value) && preg_match(self::WKT_PATTERN, $value)) {
64 28
            $this->value = 'SRID=' . self::config()->default_srid . ';' . $value;
65 25
        } else if (is_array($value) && count($value) == 3 && isset($value['type'])) {
66
            $this->value = $value;
67 26
        } else if (is_string($value) && preg_match(self::EWKT_PATTERN, $value)) {
68 8
            $this->value = $value;
69
        } else if (empty($value) || isset($value['coordinates']) && empty($value['coordinates'])) {
70 8
            $this->value = null;
71
        } else if (is_array($value) && !isset($value['type'])) {
72 8
            switch (true) {
73 1
                case is_numeric($value[0]):
74 1
                    $type = 'Point';
75 1
                    break;
76
                case is_numeric($value[0][0]):
77 8
                    $type = 'LineString';
78 8
                    break;
79 8
                case is_numeric($value[0][0][0]):
80 8
                    $type = 'Polygon';
81
                    break;
82
                case is_numeric($value[0][0][0][0]):
83
                    $type = 'MultiPolygon';
84
                    break;
85 28
            }
86
            $this->value = [
87
                'srid' => GIS::config()->default_srid,
88
                'type' => $type,
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $type does not seem to be defined for all execution paths leading up to this point.
Loading history...
89
                'coordinates' => $value,
90
            ];
91
        } else {
92 28
            throw new Exception('Invalid geo value: "' . var_export($value) . '"');
0 ignored issues
show
Bug introduced by
Are you sure the usage of var_export($value) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
93
        }
94 28
    }
95 13
96
    public function __isset($property)
97
    {
98
        return array_search($property, ['array', 'ewkt', 'srid', 'type', 'coordinates']) !== false;
99 26
    }
100 26
101 23
    public function __get($property)
102 22
    {
103 16
        if (isset($this->value[$property])) {
104 15
            return $this->value[$property];
105 15
        }
106
107 15
        switch ($property) {
108
            case 'array':
109 15
                return ['srid' => $this->srid, 'type' => $this->type, 'coordinates' => $this->coordinates];
110 9
            case 'ewkt':
111
                return (string)$this;
112
            case 'wkt':
113 15
                return explode(';', (string)$this)[1];
114
            case 'srid':
115
                return preg_match('/^SRID=(\d+);/i', $this->value, $matches) ? (int)$matches[1] : null;
116
            case 'type':
117
                return preg_match('/^SRID=\d+;(' . implode('|', array_change_key_case(self::TYPES, CASE_UPPER)) . ')/i', $this->value, $matches) ? self::TYPES[strtolower($matches[1])] : null;
118
            case 'coordinates':
119
                if (preg_match(self::EWKT_PATTERN, $this->value, $matches)) {
120
121 19
                    $coords = str_replace(['(', ')'], ['[', ']'], preg_replace('/([\d\.-]+)\s+([\d\.-]+)/', "[$1,$2]", $matches[4]));
122
123 19
                    if (strtolower($matches[3]) != 'point') {
124 11
                        $coords = "[$coords]";
125
                    }
126
127 13
                    return json_decode($coords, true)[0];
128 13
                } else {
129 13
                    return null;
130
                }
131
            case 'geometries':
132 13
                // primarily used for GeometryCollections
133
                // @todo: what's supposed to be returned for non-GeometryCollections?
134
                if (preg_match(self::EWKT_PATTERN, $this->value, $matches)) {
135
                    $geometries = preg_split('/,(?=[a-zA-Z])/', substr($matches[4], 1, -1));
136
                    $srid = $this->srid;
137
                    return array_map(function ($geometry) use ($srid) {
138
                        return GIS::create('SRID=' . $srid . ';' . $geometry);
139
                    }, $geometries);
140
                }
141
                return null;
142
            default:
143 13
                throw new Exception('Unkown property ' . $property);
144
        }
145 13
    }
146
147
    public function __toString()
148 9
    {
149
        if (is_string($this->value)) {
150 9
            return $this->value;
151
        }
152
153 9
        $type = isset($this->value['type']) ? strtoupper($this->value['type']) : null;
154
        $srid = isset($this->value['srid']) ? $this->value['srid'] : GIS::config()->default_srid;
155 9
        $array = isset($this->value['coordinates']) ? $this->value['coordinates'] : $this->value;
156
157
        $replacements = [
158
            '/(?<=\d),(?=-|\d)/' => ' ',
159 9
            '/\[\[\[\[/' => '(((',
160 8
            '/\]\]\]\]/' => ')))',
161 8
            '/\[\[\[/' => '((',
162
            '/\]\]\]/' => '))',
163
            '/\[\[/' => '(',
164 1
            '/\]\]/' => ')',
165
            '/\[/' => '',
166
            '/\]/' => '',
167
        ];
168
169 12
        $coords = preg_replace(array_keys($replacements), array_values($replacements), json_encode($array));
170
171 12
        return sprintf('SRID=%d;%s%s', $srid, $type, $type == 'POINT' ? "($coords)" : $coords);
172 12
    }
173 12
174
    public function isNull()
175 12
    {
176 1
        return empty($this->value) || isset($this->value['coordinates']) && empty($this->value['coordinates']);
177 1
    }
178 1
179
    public static function of($dataObjectClass)
180 11
    {
181
        if ($field = $dataObjectClass::config()->get('default_geo_field')) {
182
            return $field;
183 12
        }
184 12
185 12
        foreach ($dataObjectClass::config()->get('db') ?: [] as $field => $type) {
186 12
            if (in_array($type, ['Geography', 'Geometry', DBGeography::class, DBGeometry::class])) {
187
                return $field;
188
            }
189
        }
190
    }
191
192
    /**
193
     * reproject an array representation of a geometry to the given srid
194
     */
195 1
    public function reproject($toSrid = 4326)
196
    {
197 1
        $fromSrid = $this->srid;
198
199 1
        if ($fromSrid == $toSrid) {
200
            return clone $this;
201 1
        }
202
203 1
        $fromCoordinates = $this->coordinates;
204
        $type = $this->type;
205
206
        $fromProj = self::get_proj4($fromSrid);
207 1
        $toProj = self::get_proj4($toSrid);
208
        $toCoordinates = self::reproject_array($fromCoordinates, $fromProj, $toProj);
209
210 1
        return GIS::create([
211
            'srid' => $toSrid,
212
            'type' => $type,
213
            'coordinates' => $toCoordinates,
214
        ]);
215 1
    }
216 1
217 1
    /**
218
     * @var proj4php instance
219
     */
220 5
    protected static $proj4;
221
222 5
    protected static function get_proj4($srid)
223 4
    {
224
        self::$proj4 = self::$proj4 ?: new Proj4php();
225
226 5
        if (!self::$proj4->hasDef('EPSG:' . $srid)) {
227
228 2
            $projDefs = Config::inst()->get(self::class, 'projections');
229 2
230
            if (!isset($projDefs[$srid])) {
231
                throw new Exception("Cannot use unregistered SRID $srid. Register it's <a href=\"http://spatialreference.org/ref/epsg/$srid/proj4/\">PROJ.4 definition</a> in GIS::projections.");
232 2
            }
233
234
            self::$proj4->addDef('EPSG:' . $srid, $projDefs[$srid]);
235 5
        }
236
237
        return new Proj('EPSG:' . $srid, self::$proj4);
238 1
    }
239
240 1
    protected static function reproject_array($coordinates, $fromProj, $toProj)
241
    {
242
        return self::each($coordinates, function ($coordinate) use ($fromProj, $toProj) {
243
            return array_slice(self::$proj4->transform($toProj, new Point($coordinate[0], $coordinate[1], $fromProj))->toArray(), 0, 2);
244
        });
245
    }
246
247
    public static function each($coordinates, $callback)
248
    {
249
        if ($coordinates instanceof GIS) {
250
            $coordinates = $coordinates->coordinates;
251
        }
252
253
        if (is_array($coordinates[0])) {
254
255
            foreach ($coordinates as &$coordinate) {
256
                $coordinate = self::each($coordinate, $callback);
257
            }
258
259
            return $coordinates;
260
        }
261
262
        return $callback($coordinates);
263
    }
264
265
    public function distance($geo)
266
    {
267
        return DB::query('select ' . DB::get_schema()->translateDistanceQuery($this, GIS::create($geo)))->value();
0 ignored issues
show
Bug introduced by
The method translateDistanceQuery() does not exist on SilverStripe\ORM\Connect\DBSchemaManager. It seems like you code against a sub-type of SilverStripe\ORM\Connect\DBSchemaManager such as Smindel\GIS\ORM\MySQLGISSchemaManager. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

267
        return DB::query('select ' . DB::get_schema()->/** @scrutinizer ignore-call */ translateDistanceQuery($this, GIS::create($geo)))->value();
Loading history...
268
    }
269
}
270