Passed
Push — master ( b03c4d...7cc0ae )
by Andreas
05:12
created

GIS::isNull()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 3

Importance

Changes 0
Metric Value
cc 3
eloc 1
nc 3
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 3
rs 10
c 0
b 0
f 0
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
class GIS
17
{
18
    use Configurable;
19
20
    use Injectable;
21
22
    const WKT_PATTERN = '/^(([A-Z]+)\s*(\(.+\)))$/i';
23
    const EWKT_PATTERN = '/^SRID=(\d+);(([A-Z]+)\s*(\(.+\)))$/i';
24
25
    const TYPES = [
26
        'point' => 'Point',
27
        'linestring' => 'LineString',
28
        'polygon' => 'Polygon',
29
        'multipoint' => 'MultiPoint',
30
        'multilinestring' => 'MultiLineString',
31
        'multipolygon' => 'MultiPolygon',
32
    ];
33
34
    private static $default_srid = 4326;
35
36
    protected $value;
37
38
    /**
39
     * Constructor
40
     *
41
     * @param $value mixed geo value:
42
     *      string: wkt (default srid) or ewkt
43
     *      array: just coordinates (default srid, autodetect shape) or assoc with type, srid and coordinates
44
     *      GIS: extract value
45
     *      DBGeography: extract value
46
     */
47 28
    public function __construct($value)
48
    {
49 28
        if ($value instanceof GIS) {
50 3
            $this->value = $value->value;
51 28
        } else if ($value instanceof DBGeography) {
52
            $this->value = $value->getValue();
53 28
        } else if (is_string($value) && preg_match(self::WKT_PATTERN, $value)) {
54
            $this->value = 'SRID=' . self::config()->default_srid . ';' . $value;
55
        } else if (
56 28
            (is_array($value) && count($value) == 3 && isset($value['type']))
57 25
            || (is_string($value) && preg_match(self::EWKT_PATTERN, $value))
58
        ) {
59 26
            $this->value = $value;
60 8
        } else if (empty($value) || isset($value['coordinates']) && empty($value['coordinates'])) {
61
            $this->value = null;
62 8
        } else if (is_array($value) && !isset($value['type'])) {
63
            switch (true) {
64 8
                case is_numeric($value[0]): $type = 'Point'; break;
65 1
                case is_numeric($value[0][0]): $type = 'LineString'; break;
66 1
                case is_numeric($value[0][0][0]): $type = 'Polygon'; break;
67 1
                case is_numeric($value[0][0][0][0]): $type = 'MultiPolygon'; break;
68
            }
69 8
            $this->value = [
70 8
                'srid' => GIS::config()->default_srid,
71 8
                '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...
72 8
                'coordinates' => $value,
73
            ];
74
        } else {
75
            throw new Exception('Invalid geo value');
76
        }
77 28
    }
78
79
    public function __isset($property)
80
    {
81
        return array_search($offset, ['array', 'ewkt', 'srid', 'type', 'coordinates']) !== false;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $offset seems to be never defined.
Loading history...
82
    }
83
84 28
    public function __get($property)
85
    {
86 28
        if (isset($this->value[$property])) {
87 13
            return $this->value[$property];
88
        }
89
90
        switch ($property) {
91 26
            case 'array': return ['srid' => $this->srid, 'type' => $this->type, 'coordinates' => $this->coordinates];
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property srid does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property coordinates does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
92 26
            case 'ewkt': return (string)$this;
93 23
            case 'wkt': return explode(';', (string)$this)[1];
94 22
            case 'srid': return preg_match('/^SRID=(\d+);/i', $this->value, $matches) ? (int)$matches[1] : null;
95 16
            case 'type': return preg_match('/^SRID=\d+;(' . implode('|', array_change_key_case(self::TYPES, CASE_UPPER)) . ')/i', $this->value, $matches) ? self::TYPES[strtolower($matches[1])] : null;
96 15
            case 'coordinates':
97 15
                if (preg_match(self::EWKT_PATTERN, $this->value, $matches)) {
98
99 15
                    $coords = str_replace(['(', ')'], ['[', ']'], preg_replace('/([\d\.-]+)\s+([\d\.-]+)/', "[$1,$2]", $matches[4]));
100
101 15
                    if (strtolower($matches[3]) != 'point') {
102 9
                        $coords = "[$coords]";
103
                    }
104
105 15
                    return json_decode($coords, true)[0];
106
                } else return null;
107
            default: throw new Exception('Unkown property ' . $property);
108
        }
109
    }
110
111 19
    public function __toString()
112
    {
113 19
        if (is_string($this->value)) return $this->value;
114
115 13
        $type = isset($this->value['type']) ? strtoupper($this->value['type']) : null;
116 13
        $srid = isset($this->value['srid']) ? $this->value['srid'] : GIS::config()->default_srid;
117 13
        $array = isset($this->value['coordinates']) ? $this->value['coordinates'] : $this->value;
118
119
        $replacements = [
120 13
            '/(?<=\d),(?=-|\d)/' => ' ',
121
            '/\[\[\[\[/' => '(((',
122
            '/\]\]\]\]/' => ')))',
123
            '/\[\[\[/' => '((',
124
            '/\]\]\]/' => '))',
125
            '/\[\[/' => '(',
126
            '/\]\]/' => ')',
127
            '/\[/' => '',
128
            '/\]/' => '',
129
        ];
130
131 13
        $coords = preg_replace(array_keys($replacements), array_values($replacements), json_encode($array));
132
133 13
        return sprintf('SRID=%d;%s%s', $srid, $type, $type == 'POINT' ? "($coords)" : $coords);
134
    }
135
136 9
    public function isNull()
137
    {
138 9
        return empty($this->value) || isset($this->value['coordinates']) && empty($this->value['coordinates']);
139
    }
140
141 9
    public static function of($dataObjectClass)
142
    {
143 9
        if ($field = $dataObjectClass::config()->get('default_geo_field')) {
144
            return $field;
145
        }
146
147 9
        foreach ($dataObjectClass::config()->get('db') ?: [] as $field => $type) {
148 8
            if ($type == 'Geography' || $type == 'Geometry') {
149 8
                return $field;
150
            }
151
        }
152 1
    }
153
154
    /**
155
     * reproject an array representation of a geometry to the given srid
156
     */
157 12
    public function reproject($toSrid = 4326)
158
    {
159 12
        $fromSrid = $this->srid;
0 ignored issues
show
Bug Best Practice introduced by
The property srid does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
160 12
        $fromCoordinates = $this->coordinates;
0 ignored issues
show
Bug Best Practice introduced by
The property coordinates does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
161 12
        $type = $this->type;
0 ignored issues
show
Bug Best Practice introduced by
The property type does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
162
163 12
        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 11
            $toCoordinates = $fromCoordinates;
169
        }
170
171 12
        return GIS::create([
172 12
            'srid' => $toSrid,
173 12
            'type' => $type,
174 12
            '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
189 1
            $projDefs = Config::inst()->get(self::class, 'projections');
190
191 1
            if (!isset($projDefs[$srid])) {
192
                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.");
193
            }
194
195 1
            self::$proj4->addDef('EPSG:' . $srid, $projDefs[$srid]);
196
        }
197
198 1
        return new Proj('EPSG:' . $srid, self::$proj4);
199
    }
200
201
    protected static function reproject_array($coordinates, $fromProj, $toProj)
202
    {
203 1
        return self::each($coordinates, function($coordinate) use ($fromProj, $toProj) {
204 1
            return array_slice(self::$proj4->transform($toProj, new Point($coordinate[0], $coordinate[1], $fromProj))->toArray(), 0, 2);
205 1
        });
206
    }
207
208 5
    public static function each($coordinates, $callback)
209
    {
210 5
        if ($coordinates instanceof GIS) {
211 4
            $coordinates = $coordinates->coordinates;
0 ignored issues
show
Bug Best Practice introduced by
The property coordinates does not exist on Smindel\GIS\GIS. Since you implemented __get, consider adding a @property annotation.
Loading history...
212
        }
213
214 5
        if (is_array($coordinates[0])) {
215
216 2
            foreach ($coordinates as &$coordinate) {
217 2
                $coordinate = self::each($coordinate, $callback);
218
            }
219
220 2
            return $coordinates;
221
        }
222
223 5
        return $callback($coordinates);
224
    }
225
226 1
    public function distance($geo)
227
    {
228 1
        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

228
        return DB::query('select ' . DB::get_schema()->/** @scrutinizer ignore-call */ translateDistanceQuery($this, GIS::create($geo)))->value();
Loading history...
229
    }
230
}
231