Completed
Branch master (b03c4d)
by Andreas
06:02
created

GIS::to_array()   B

Complexity

Conditions 10
Paths 18

Size

Total Lines 34
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 10.4632

Importance

Changes 0
Metric Value
cc 10
eloc 20
nc 18
nop 1
dl 0
loc 34
ccs 15
cts 18
cp 0.8333
crap 10.4632
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 9
    public static function of($dataObjectClass)
33
    {
34 9
        if ($field = $dataObjectClass::config()->get('default_geo_field')) {
35
            return $field;
36
        }
37
38 9
        foreach ($dataObjectClass::config()->get('db') ?: [] as $field => $type) {
39 8
            if ($type == 'Geography' || $type == 'Geometry') {
40 8
                return $field;
41
            }
42
        }
43 1
    }
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 13
    public static function to_array($geo)
82
    {
83 13
        if ($geo instanceof DBGeography) $geo = $geo->getValue();
84
85 13
        if (is_array($geo)) {
86
87 5
            if (isset($geo['coordinates'])) return $geo;
88
89
            switch (true) {
90 2
                case is_numeric($geo[0]): $type = 'Point'; break;
91
                case is_numeric($geo[0][0]): $type = 'LineString'; break;
92
                case is_numeric($geo[0][0][0]): $type = 'Polygon'; break;
93
                case is_numeric($geo[0][0][0][0]): $type = 'MultiPolygon'; break;
94
            }
95
96
            return [
97 2
                'srid' => Config::inst()->get(self::class, 'default_srid'),
98 2
                '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...
99 2
                'coordinates' => $geo
100
            ];
101
        }
102
103 11
        if (preg_match(self::EWKT_PATTERN, $geo, $matches)) {
104
105 11
            $coords = str_replace(['(', ')'], ['[', ']'], preg_replace('/([\d\.-]+)\s+([\d\.-]+)/', "[$1,$2]", $matches[4]));
106
107 11
            if (strtolower($matches[3]) != 'point') {
108 6
                $coords = "[$coords]";
109
            }
110
111
            return [
112 11
                'srid' => $matches[1],
113 11
                'type' => self::TYPES[strtolower($matches[3])],
114 11
                'coordinates' => json_decode($coords, true)[0],
115
            ];
116
        }
117
    }
118
119 15
    public static function split_ewkt($ewkt, $fallbackSrid = null)
120
    {
121 15
        $fallbackSrid = $fallbackSrid ?: Config::inst()->get(self::class, 'default_srid');
122
123 15
        if (preg_match(GIS::EWKT_PATTERN, $ewkt, $matches)) {
124 15
            $wkt = $matches[2];
125 15
            $srid = (int)$matches[1];
126
        } else {
127
            $wkt = $ewkt;
128
            $srid = (int)$fallbackSrid;
129
        }
130
131 15
        return [$wkt, $srid];
132
    }
133
134 5
    public static function get_type($geo)
135
    {
136 5
        if (is_array($geo) && isset($geo['type'])) {
137
            return self::TYPES[strtolower($geo['type'])];
138 5
        } elseif (is_array($geo)) {
139 1
            $geo = self::to_ewkt($geo);
140
        }
141
142 5
        if (preg_match(
143 5
            '/;(' . implode('|', array_keys(self::TYPES)) . ')\(/i',
144 5
            strtolower(substr($geo, 8, 30)),
145 5
            $matches
146
        )) {
147 5
            return self::TYPES[$matches[1]];
148
        }
149
    }
150
151
    /**
152
     * reproject an array representation of a geometry to the given srid
153
     */
154 8
    public static function reproject($geo, $toSrid = 4326)
155
    {
156 8
        $array = self::to_array($geo);
157
158 8
        $fromSrid = $array['srid'];
159 8
        $fromCoordinates = $array['coordinates'];
160 8
        $type = $array['type'];
161
162 8
        if ($fromSrid != $toSrid) {
163 1
            $fromProj = self::get_proj4($fromSrid);
164 1
            $toProj = self::get_proj4($toSrid);
165 1
            $toCoordinates = self::reproject_array($fromCoordinates, $fromProj, $toProj);
166
        } else {
167 7
            $toCoordinates = $fromCoordinates;
168
        }
169
170
        return [
171 8
            'srid' => $toSrid,
172 8
            'type' => $type,
173 8
            'coordinates' => $toCoordinates,
174
        ];
175
    }
176
177
    /**
178
     * @var proj4php instance
179
     */
180
    protected static $proj4;
181
182 1
    protected static function get_proj4($srid)
183
    {
184 1
        self::$proj4 = self::$proj4 ?: new Proj4php();
185
186 1
        if (!self::$proj4->hasDef('EPSG:' . $srid)) {
187
188 1
            $projDefs = Config::inst()->get(self::class, 'projections');
189
190 1
            if (!isset($projDefs[$srid])) {
191
                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.");
192
            }
193
194 1
            self::$proj4->addDef('EPSG:' . $srid, $projDefs[$srid]);
195
        }
196
197 1
        return new Proj('EPSG:' . $srid, self::$proj4);
198
    }
199
200
    protected static function reproject_array($coordinates, $fromProj, $toProj)
201
    {
202 1
        return self::each($coordinates, function($coordinate) use ($fromProj, $toProj) {
203 1
            return array_slice(self::$proj4->transform($toProj, new Point($coordinate[0], $coordinate[1], $fromProj))->toArray(), 0, 2);
204 1
        });
205
    }
206
207 5
    public static function each($coordinates, $callback)
208
    {
209 5
        if (isset($coordinates['coordinates'])) {
210 4
            $coordinates = $coordinates['coordinates'];
211
        }
212
213 5
        if (is_array($coordinates[0])) {
214
215 2
            foreach ($coordinates as &$coordinate) {
216 2
                $coordinate = self::each($coordinate, $callback);
217
            }
218
219 2
            return $coordinates;
220
        }
221
222 5
        return $callback($coordinates);
223
    }
224
225 1
    public static function distance($geo1, $geo2)
226
    {
227 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

227
        return DB::query('select ' . DB::get_schema()->/** @scrutinizer ignore-call */ translateDistanceQuery($geo1, $geo2))->value();
Loading history...
228
    }
229
}
230