Passed
Push — master ( a468b8...894176 )
by Andreas
05:35
created

GIS::to_array()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 29
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 15.8818

Importance

Changes 0
Metric Value
cc 10
eloc 20
nc 10
nop 1
dl 0
loc 29
ccs 11
cts 18
cp 0.6111
crap 15.8818
rs 7.6666
c 0
b 0
f 0

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 proj4php\Proj4php;
11
use proj4php\Proj;
12
use proj4php\Point;
13
use Exception;
14
15
class GIS
16
{
17
    use Configurable;
18
19
    use Injectable;
20
21
    private static $default_srid = 4326;
0 ignored issues
show
introduced by
The private property $default_srid is not used, and could be removed.
Loading history...
22
23
    const EWKT_PATTERN = '/^SRID=(\d+);(([A-Z]+)\s*(\(.+\)))$/i';
24
25
    const TYPES = [
26
        'point' => 'Point',
27
        'linestring' => 'LineString',
28
        'polygon' => 'Polygon',
29
        'multipolygon' => 'MultiPolygon',
30
    ];
31
32 8
    public static function of($dataObjectClass)
33
    {
34 8
        if ($field = $dataObjectClass::config()->get('default_geo_field')) {
35
            return $field;
36
        }
37
38 8
        foreach ($dataObjectClass::config()->get('db') ?: [] as $field => $type) {
39 8
            if ($type == 'Geography' || $type == 'Geometry') {
40 8
                return $field;
41
            }
42
        }
43
    }
44
45 12
    public static function to_ewkt($geo)
46
    {
47 12
        if (is_string($geo)) return $geo;
48
49 12
        if ($geo instanceof DBGeography) $geo = $geo->getValue();
50
51 12
        $type = isset($geo['type']) ? strtoupper($geo['type']) : null;
52 12
        $srid = isset($geo['srid']) ? $geo['srid'] : Config::inst()->get(self::class, 'default_srid');
53 12
        $array = isset($geo['coordinates']) ? $geo['coordinates'] : $geo;
54
55 12
        if (!$type) {
56
            switch (true) {
57 5
                case is_numeric($array[0]): $type = 'POINT'; break;
58 1
                case is_numeric($array[0][0]): $type = 'LINESTRING'; break;
59 1
                case is_numeric($array[0][0][0]): $type = 'POLYGON'; break;
60 1
                case is_numeric($array[0][0][0][0]): $type = 'MULTIPOLYGON'; break;
61
            }
62
        }
63
64
        $replacements = [
65 12
            '/(?<=\d),(?=-|\d)/' => ' ',
66
            '/\[\[\[\[/' => '(((',
67
            '/\]\]\]\]/' => ')))',
68
            '/\[\[\[/' => '((',
69
            '/\]\]\]/' => '))',
70
            '/\[\[/' => '(',
71
            '/\]\]/' => ')',
72
            '/\[/' => '',
73
            '/\]/' => '',
74
        ];
75
76 12
        $coords = preg_replace(array_keys($replacements), array_values($replacements), json_encode($array));
77
78 12
        return sprintf('SRID=%d;%s%s', $srid, $type, $type == 'POINT' ? "($coords)" : $coords);
79
    }
80
81 11
    public static function to_array($geo)
82
    {
83 11
        if ($geo instanceof DBGeography) return $geo->getValue();
84
85 11
        if (is_array($geo)) {
86 3
            if (isset($geo['coordinates'])) return $geo;
87
            switch (true) {
88
                case is_numeric($geo[0]): $type = 'Point'; break;
89
                case is_numeric($geo[0][0]): $type = 'LineString'; break;
90
                case is_numeric($geo[0][0][0]): $type = 'Polygon'; break;
91
                case is_numeric($geo[0][0][0][0]): $type = 'MultiPolygon'; break;
92
            }
93
            return [
94
                'srid' => Config::inst()->get(self::class, 'default_srid'),
95
                '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...
96
                'cooridinates' => $geo
97
            ];
98
        }
99
100 11
        if (preg_match(self::EWKT_PATTERN, $geo, $matches)) {
101 11
            $coords = str_replace(['(', ')'], ['[', ']'], preg_replace('/([\d\.-]+)\s+([\d\.-]+)/', "[$1,$2]", $matches[4]));
102 11
            if (strtolower($matches[3]) != 'point') {
103 6
                $coords = "[$coords]";
104
            }
105
106
            return [
107 11
                'srid' => $matches[1],
108 11
                'type' => self::TYPES[strtolower($matches[3])],
109 11
                'coordinates' => json_decode($coords, true)[0],
110
            ];
111
        }
112
    }
113
114 14
    public static function split_ewkt($ewkt, $fallbackSrid = null)
115
    {
116 14
        $fallbackSrid = $fallbackSrid ?: Config::inst()->get(self::class, 'default_srid');
117
118 14
        if (preg_match(GIS::EWKT_PATTERN, $ewkt, $matches)) {
119 14
            $wkt = $matches[2];
120 14
            $srid = (int)$matches[1];
121
        } else {
122
            $wkt = $ewkt;
123
            $srid = (int)$fallbackSrid;
124
        }
125 14
        return [$wkt, $srid];
126
    }
127
128 5
    public static function get_type($geo, $useBestGuess = false)
0 ignored issues
show
Unused Code introduced by
The parameter $useBestGuess is not used and could be removed. ( Ignorable by Annotation )

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

128
    public static function get_type($geo, /** @scrutinizer ignore-unused */ $useBestGuess = false)

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
129
    {
130 5
        if (is_array($geo) && isset($geo['type'])) {
131
            return self::TYPES[strtolower($geo['type'])];
132 5
        } elseif (is_array($geo)) {
133 1
            $geo = self::to_ewkt($geo);
134
        }
135 5
        if (preg_match(
136 5
            '/;(' . implode('|', array_keys(self::TYPES)) . ')\(/i',
137 5
            strtolower(substr($geo, 8, 30)),
138 5
            $matches
139
        )) {
140 5
            return self::TYPES[$matches[1]];
141
        }
142
    }
143
144
    /**
145
     * reproject an array representation of a geometry to the given srid
146
     */
147 7
    public static function reproject($geo, $toSrid = 4326)
148
    {
149 7
        $array = self::to_array($geo);
150
151 7
        $fromSrid = $array['srid'];
152 7
        $fromCoordinates = $array['coordinates'];
153 7
        $type = $array['type'];
154
155 7
        if ($fromSrid != $toSrid) {
156 1
            $fromProj = self::get_proj4($fromSrid);
157 1
            $toProj = self::get_proj4($toSrid);
158 1
            $toCoordinates = self::reproject_array($fromCoordinates, $fromProj, $toProj);
159
        } else {
160 6
            $toCoordinates = $fromCoordinates;
161
        }
162
163
        return [
164 7
            'srid' => $toSrid,
165 7
            'type' => $type,
166 7
            'coordinates' => $toCoordinates,
167
        ];
168
    }
169
170
    /**
171
     * @var proj4php instance
172
     */
173
    protected static $proj4;
174
175 1
    protected static function get_proj4($srid)
176
    {
177 1
        self::$proj4 = self::$proj4 ?: new Proj4php();
178
179 1
        if (!self::$proj4->hasDef('EPSG:' . $srid)) {
180 1
            $projDefs = Config::inst()->get(self::class, 'projections');
181 1
            if (!isset($projDefs[$srid])) {
182
                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.");
183
            }
184 1
            self::$proj4->addDef('EPSG:' . $srid, $projDefs[$srid]);
185
        }
186
187 1
        return new Proj('EPSG:' . $srid, self::$proj4);
188
    }
189
190
    protected static function reproject_array($coordinates, $fromProj, $toProj)
191
    {
192 1
        return self::each($coordinates, function($coordinate) use ($fromProj, $toProj) {
193 1
            return array_slice(self::$proj4->transform($toProj, new Point($coordinate[0], $coordinate[1], $fromProj))->toArray(), 0, 2);
194 1
        });
195
    }
196
197 5
    public static function each($coordinates, $callback)
198
    {
199 5
        if (isset($coordinates['coordinates'])) {
200 4
            $coordinates = $coordinates['coordinates'];
201
        }
202
203 5
        if (is_array($coordinates[0])) {
204 2
            foreach ($coordinates as &$coordinate) {
205 2
                $coordinate = self::each($coordinate, $callback);
206
            }
207 2
            return $coordinates;
208
        }
209
210 5
        return $callback($coordinates);
211
    }
212
213 1
    public static function distance($geo1, $geo2)
214
    {
215 1
        return DB::query('select ' . DB::get_schema()->translateDistanceQuery($geo1, $geo2))->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

215
        return DB::query('select ' . DB::get_schema()->/** @scrutinizer ignore-call */ translateDistanceQuery($geo1, $geo2))->value();
Loading history...
216
    }
217
}
218