Passed
Push — master ( 894176...e93055 )
by Andreas
08:32 queued 03:25
created

GIS::to_array()   B

Complexity

Conditions 10
Paths 10

Size

Total Lines 33
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 12
CRAP Score 16.4

Importance

Changes 0
Metric Value
cc 10
eloc 22
nc 10
nop 1
dl 0
loc 33
ccs 12
cts 20
cp 0.6
crap 16.4
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)) {
48
            return $geo;
49
        }
50
51 12
        if ($geo instanceof DBGeography) {
52
            $geo = $geo->getValue();
53
        }
54
55 12
        $type = isset($geo['type']) ? strtoupper($geo['type']) : null;
56 12
        $srid = isset($geo['srid']) ? $geo['srid'] : Config::inst()->get(self::class, 'default_srid');
57 12
        $array = isset($geo['coordinates']) ? $geo['coordinates'] : $geo;
58
59 12
        if (!$type) {
60
            switch (true) {
61 5
                case is_numeric($array[0]): $type = 'POINT'; break;
62 1
                case is_numeric($array[0][0]): $type = 'LINESTRING'; break;
63 1
                case is_numeric($array[0][0][0]): $type = 'POLYGON'; break;
64 1
                case is_numeric($array[0][0][0][0]): $type = 'MULTIPOLYGON'; break;
65
            }
66
        }
67
68
        $replacements = [
69 12
            '/(?<=\d),(?=-|\d)/' => ' ',
70
            '/\[\[\[\[/' => '(((',
71
            '/\]\]\]\]/' => ')))',
72
            '/\[\[\[/' => '((',
73
            '/\]\]\]/' => '))',
74
            '/\[\[/' => '(',
75
            '/\]\]/' => ')',
76
            '/\[/' => '',
77
            '/\]/' => '',
78
        ];
79
80 12
        $coords = preg_replace(array_keys($replacements), array_values($replacements), json_encode($array));
81
82 12
        return sprintf('SRID=%d;%s%s', $srid, $type, $type == 'POINT' ? "($coords)" : $coords);
83
    }
84
85 11
    public static function to_array($geo)
86
    {
87 11
        if ($geo instanceof DBGeography) {
88
            return $geo->getValue();
89
        }
90
91 11
        if (is_array($geo)) {
92 3
            if (isset($geo['coordinates'])) {
93 3
                return $geo;
94
            }
95
            switch (true) {
96
                case is_numeric($geo[0]): $type = 'Point'; break;
97
                case is_numeric($geo[0][0]): $type = 'LineString'; break;
98
                case is_numeric($geo[0][0][0]): $type = 'Polygon'; break;
99
                case is_numeric($geo[0][0][0][0]): $type = 'MultiPolygon'; break;
100
            }
101
            return [
102
                'srid' => Config::inst()->get(self::class, 'default_srid'),
103
                '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...
104
                'cooridinates' => $geo
105
            ];
106
        }
107
108 11
        if (preg_match(self::EWKT_PATTERN, $geo, $matches)) {
109 11
            $coords = str_replace(['(', ')'], ['[', ']'], preg_replace('/([\d\.-]+)\s+([\d\.-]+)/', "[$1,$2]", $matches[4]));
110 11
            if (strtolower($matches[3]) != 'point') {
111 6
                $coords = "[$coords]";
112
            }
113
114
            return [
115 11
                'srid' => $matches[1],
116 11
                'type' => self::TYPES[strtolower($matches[3])],
117 11
                'coordinates' => json_decode($coords, true)[0],
118
            ];
119
        }
120
    }
121
122 14
    public static function split_ewkt($ewkt, $fallbackSrid = null)
123
    {
124 14
        $fallbackSrid = $fallbackSrid ?: Config::inst()->get(self::class, 'default_srid');
125
126 14
        if (preg_match(GIS::EWKT_PATTERN, $ewkt, $matches)) {
127 14
            $wkt = $matches[2];
128 14
            $srid = (int)$matches[1];
129
        } else {
130
            $wkt = $ewkt;
131
            $srid = (int)$fallbackSrid;
132
        }
133 14
        return [$wkt, $srid];
134
    }
135
136 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

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

223
        return DB::query('select ' . DB::get_schema()->/** @scrutinizer ignore-call */ translateDistanceQuery($geo1, $geo2))->value();
Loading history...
224
    }
225
}
226