GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 864bdc...94a255 )
by Francisco
03:51
created

Indexer::getAssociatedZonesAndNextQuadrants()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 32
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 32
ccs 0
cts 32
cp 0
rs 8.439
cc 5
eloc 25
nc 4
nop 3
crap 30
1
<?php
2
3
namespace GeoTimeZone\Quadrant;
4
5
use ErrorException;
6
7
class Indexer extends Tree
8
{
9
    const TARGET_INDEX_PERCENT = 0.91;
10
    const DEFAULT_ZONE_RESULT = -1;
11
    const LEVEL_DELIMITER_SYMBOL = ".";
12
    const TOTAL_LEVELS = 4;
13
    
14
    protected $dataSourcePath = null;
15
    protected $dataSource;
16
    protected $timezones = array();
17
    protected $lookup = array();
18
    protected $currentQuadrants = array();
19
    
20
    
21
    public function __construct($dataDirectory = null, $dataSourcePath = null)
22
    {
23
        parent::__construct($dataDirectory);
24
        $this->setGeoDataSource($dataSourcePath);
25
    }
26
    
27
    /**
28
     * Initialize the current quadrants attribute for the first indexing iteration
29
     */
30
    protected function initCurrentQuadrants()
31
    {
32
        $this->currentQuadrants = array(
33
            array(
34
                "id" => Tree::LEVEL_A,
35
                "bounds" => array(0, 0, Tree::ABS_LONGITUDE_LIMIT, Tree::ABS_LATITUDE_LIMIT)
36
            ),
37
            array(
38
                "id" => Tree::LEVEL_B,
39
                "bounds" => array(-Tree::ABS_LONGITUDE_LIMIT, 0, 0, Tree::ABS_LATITUDE_LIMIT)
40
            ),
41
            array(
42
                "id" => Tree::LEVEL_C,
43
                "bounds" => array(-Tree::ABS_LONGITUDE_LIMIT, -Tree::ABS_LATITUDE_LIMIT, 0, 0)
44
            ),
45
            array(
46
                "id" => Tree::LEVEL_D,
47
                "bounds" => array(0, -Tree::ABS_LATITUDE_LIMIT, Tree::ABS_LONGITUDE_LIMIT, 0)
48
            )
49
        );
50
    }
51
    
52
    /**
53
     * Read the new timezones json to be indexed
54
     */
55
    protected function readDataSource()
56
    {
57
        if (isset($this->dataSourcePath) && is_file($this->dataSourcePath)) {
58
            $jsonData = file_get_contents($this->dataSourcePath);
59
            $this->dataSource = json_decode($jsonData, true);
60
        }else{
61
            throw new ErrorException("ERROR: Data source path not found.");
62
        }
63
    }
64
    
65
    /**
66
     * Save timezones values from the reference file (timezones json) to timezones array attribute
67
     */
68
    protected function setTimezonesArray()
69
    {
70
        foreach ($this->dataSource['features'] as $feature) {
71
            $this->timezones[] = $feature['properties']['tzid'];
72
        }
73
    }
74
    
75
    /**
76
     * Find the timezones that intersect with or are within the quadrant polygon
77
     * @param $timezonesToInspect
78
     * @param $quadrantBounds
79
     * @return array
80
     */
81
    protected function whichTimeZonesIntersect($timezonesToInspect, $quadrantBounds)
82
    {
83
        $intersectedZones = [];
84
        $foundExactMatch = false;
85
        for ($inspectIdx = count($timezonesToInspect) - 1; $inspectIdx >= 0; $inspectIdx--) {
86
            $zoneIdx = $timezonesToInspect[$inspectIdx];
87
            $zoneJson = $this->dataSource['features'][$zoneIdx]['geometry'];
88
            if ($this->utils->intersectsPolygons($zoneJson, $quadrantBounds)) {
89
                if ($this->utils->withinPolygon($quadrantBounds, $zoneJson)) {
90
                    $intersectedZones = $zoneIdx;
91
                    $foundExactMatch = true;
92
                    break;
93
                } else {
94
                    $intersectedZones[] = $zoneIdx;
95
                }
96
            }
97
        }
98
        return array(
99
            'foundExactMatch' => $foundExactMatch,
100
            'intersectedZones' => $intersectedZones
101
        );
102
    }
103
    
104
    /**
105
     * Create new level of quadrants from the previous bounds, the intersected found timezones and the previous zone ID
106
     * @param $zoneId
107
     * @param $intersectedZones
108
     * @param $quadrantBounds
109
     * @return array
110
     */
111
    protected function getNextQuadrants($zoneId, $intersectedZones, $quadrantBounds)
112
    {
113
        $topRight = array(
114
            'id' => $zoneId . '.a',
115
            'timezones' => $intersectedZones,
116
            'bounds' => [
117
                (float)($quadrantBounds[0] + $quadrantBounds[2]) / 2,
118
                (float)($quadrantBounds[1] + $quadrantBounds[3]) / 2,
119
                $quadrantBounds[2],
120
                $quadrantBounds[3]
121
            ]
122
        );
123
        
124
        $topLeft = array(
125
            'id' => $zoneId . '.b',
126
            'timezones' => $intersectedZones,
127
            'bounds' => [
128
                $quadrantBounds[0],
129
                (float)($quadrantBounds[1] + $quadrantBounds[3]) / 2.0,
130
                (float)($quadrantBounds[0] + $quadrantBounds[2]) / 2.0,
131
                $quadrantBounds[3]
132
            ]
133
        );
134
        
135
        $bottomLeft = array(
136
            'id' => $zoneId . '.c',
137
            'timezones' => $intersectedZones,
138
            'bounds' => [
139
                $quadrantBounds[0],
140
                $quadrantBounds[1],
141
                (float)($quadrantBounds[0] + $quadrantBounds[2]) / 2.0,
142
                (float)($quadrantBounds[1] + $quadrantBounds[3]) / 2.0
143
            ]
144
        );
145
        
146
        $bottomRight = array(
147
            'id' => $zoneId . '.d',
148
            'timezones' => $intersectedZones,
149
            'bounds' => [
150
                (float)($quadrantBounds[0] + $quadrantBounds[2]) / 2.0,
151
                $quadrantBounds[1],
152
                $quadrantBounds[2],
153
                (float)($quadrantBounds[1] + $quadrantBounds[3]) / 2.0
154
            ]
155
        );
156
        
157
        return array($topRight, $topLeft, $bottomLeft, $bottomRight);
158
    }
159
    
160
    /**
161
     * Select timezones to find the intersections
162
     * @param $previousTimezone
163
     * @return array
164
     */
165
    protected function selectTimeZonesToInspect($previousTimezone)
166
    {
167
        $timezonesToInspect = [];
168
        if (isset($previousTimezone['timezones'])) {
169
            $timezonesToInspect = $previousTimezone['timezones'];
170
        } else {
171
            for ($zoneIdx = count($this->dataSource['features']) - 1; $zoneIdx >= 0; $zoneIdx--) {
172
                $timezonesToInspect[] = $zoneIdx;
173
            }
174
        }
175
        return $timezonesToInspect;
176
    }
177
    
178
    /**
179
     * Update the lookup table
180
     * @param $zoneResult
181
     * @param $curQuadrantId
182
     */
183
    protected function updateLookup($zoneResult, $curQuadrantId)
184
    {
185
        $levelPath = explode(self::LEVEL_DELIMITER_SYMBOL, $curQuadrantId);
186
        if ($zoneResult !== self::DEFAULT_ZONE_RESULT) {
187
            $this->addLevelToLookup($zoneResult, $levelPath);
188
        } else {
189
            $this->removeLevelFromLookup($levelPath);
190
        }
191
    }
192
    
193
    /**
194
     * Get intersection features from current quadrant and each intersected timezone areas
195
     * @param $intersectionResult
196
     * @param $curQuadrant
197
     * @return array
198
     */
199
    protected function getIntersectionFeatures($intersectionResult, $curQuadrant)
200
    {
201
        $features = [];
202
        for ($zoneIdx = count($intersectionResult['intersectedZones']) - 1; $zoneIdx >= 0; $zoneIdx--) {
203
            $tzIdx = $intersectionResult['intersectedZones'][$zoneIdx];
204
            $quadrantBoundsGeoJson = $this->utils->createPolygonJsonFromPoints(
205
                $this->utils->adaptQuadrantBoundsToPolygon($curQuadrant['bounds'])
206
            );
207
            $intersectedArea = $this->utils->intersection(
208
                json_encode($this->dataSource['features'][$tzIdx]['geometry']),
209
                $quadrantBoundsGeoJson);
210
            if ($intersectedArea) {
211
                $intersectedArea['properties']['tzid'] = $this->timezones[$tzIdx];
212
                $features[] = $intersectedArea;
213
            }
214
        }
215
        return $features;
216
    }
217
    
218
    /**
219
     * Find the associated zones to the current quadrants and the next quadrants to be evaluated
220
     * @param $intersectionResult
221
     * @param $curQuadrant
222
     * @param $lastLevelFlag
223
     * @return array
224
     */
225
    protected function getAssociatedZonesAndNextQuadrants($intersectionResult, $curQuadrant, $lastLevelFlag)
226
    {
227
        $zoneResult = self::DEFAULT_ZONE_RESULT;
228
        $nextQuadrants = [];
229
        if (count($intersectionResult['intersectedZones']) === 1 && $intersectionResult['foundExactMatch']) {
230
            $zoneResult = $intersectionResult['intersectedZones'];
231
        } elseif (count($intersectionResult['intersectedZones']) > 0) {
232
            if ($lastLevelFlag) {
233
                $features = $this->getIntersectionFeatures($intersectionResult, $curQuadrant);
234
                $featuresCollection = $this->utils->getFeatureCollection($features);
235
                $featuresPath = $this->dataDirectory . str_replace('.', "/", $curQuadrant['id']) . "/";
236
                $this->writeGeoFeaturesToJson($featuresCollection, $featuresPath);
237
                $zoneResult = 'f';
238
            } else {
239
                $nextQuadrants = $this->getNextQuadrants(
240
                    $curQuadrant['id'],
241
                    $intersectionResult['intersectedZones'],
242
                    $curQuadrant['bounds']
243
                );
244
                $zoneResult = array(
245
                    'a' => $intersectionResult['intersectedZones'],
246
                    'b' => $intersectionResult['intersectedZones'],
247
                    'c' => $intersectionResult['intersectedZones'],
248
                    'd' => $intersectionResult['intersectedZones']
249
                );
250
            }
251
        }
252
        return array(
253
            'zoneResult' => $zoneResult,
254
            'nextQuadrants' => $nextQuadrants
255
        );
256
    }
257
    
258
    /**
259
     * Check if the current indexing iteration should be carry on or not
260
     * @param $curLevel
261
     * @param $numQuadrants
262
     * @return bool
263
     */
264
    protected function validIndexingPercentage($curLevel, $numQuadrants)
265
    {
266
        $expectedAtLevel = pow(self::TOTAL_LEVELS, $curLevel + 1);
267
        $curPctIndexed = ($expectedAtLevel - $numQuadrants) / $expectedAtLevel;
268
        echo "Iteration " . $curLevel . "\n Num quadrants: " . $numQuadrants . "\n";
269
        echo " Indexing percentage: " . $curPctIndexed . "\n";
270
        return $curPctIndexed < self::TARGET_INDEX_PERCENT;
271
    }
272
    
273
    /**
274
     * Index current quadrants and get the new ones
275
     * @param $lastLevelFlag
276
     * @return array
277
     */
278
    protected function indexQuadrants($lastLevelFlag)
279
    {
280
        $nextQuadrants = array();
281
        for ($levelIdx = count($this->currentQuadrants) - 1; $levelIdx >= 0; $levelIdx--) {
282
            $curQuadrant = $this->currentQuadrants[$levelIdx];
283
            $nextStep = $this->findTimezonesAndNextQuadrants($lastLevelFlag, $curQuadrant);
284
            $this->updateLookup($nextStep['zoneResult'], $curQuadrant['id']);
285
            $nextQuadrants = array_merge($nextQuadrants, $nextStep['nextQuadrants']);
286
        }
287
        return $nextQuadrants;
288
    }
289
    
290
    /**
291
     * Main function that run all index processing
292
     */
293
    protected function generateIndexes()
294
    {
295
        $this->initCurrentQuadrants();
296
        $curLevel = 1;
297
        $numQuadrants = 16;
298
        $lastLevel = 0;
299
        
300
        while ($this->validIndexingPercentage($curLevel, $numQuadrants)) {
301
            $curLevel += 1;
302
            $this->currentQuadrants = $this->indexQuadrants($lastLevel);
303
            $numQuadrants = count($this->currentQuadrants);
304
        }
305
        echo "Last iteration... \n";
306
        $lastLevel = 1;
307
        $this->currentQuadrants = $this->indexQuadrants($lastLevel);
308
        echo "Writing quadrant tree json...\n";
309
        $this->writeQuadrantTreeJson();
310
    }
311
    
312
    /**
313
     * Create the directory tree
314
     * @param $path
315
     */
316
    protected function createDirectoryTree($path)
317
    {
318
        $directories = explode($this->dataDirectory, $path)[1];
319
        $directories = explode("/", $directories);
320
        $currentDir = $this->dataDirectory;
321
        foreach ($directories as $dir) {
322
            $currentDir = $currentDir . "/" . $dir;
323
            if (!is_dir($currentDir)) {
324
                mkdir($currentDir);
325
            }
326
        }
327
    }
328
    
329
    /**
330
     * Create json file from timezone features
331
     * @param $features
332
     * @param $path
333
     * @return bool|int
334
     */
335
    protected function writeGeoFeaturesToJson($features, $path)
336
    {
337
        $writtenBytes = false;
338
        $this->createDirectoryTree($path);
339
        if ($path && is_writable($path)) {
340
            $full = $path . DIRECTORY_SEPARATOR . Tree::GEO_FEATURE_FILENAME;
341
            $writtenBytes = file_put_contents($full, json_encode($features));
342
        }
343
        return $writtenBytes;
344
    }
345
    
346
    /**
347
     * Build tree array to be save in a json file later
348
     * @return array
349
     */
350
    protected function buildTree()
351
    {
352
        $tree = array(
353
            'timezones' => $this->timezones,
354
            'lookup' => $this->lookup
355
        );
356
        return $tree;
357
    }
358
    
359
    /**
360
     * Write the quadrant tree in a json file
361
     * @return bool|int
362
     */
363
    protected function writeQuadrantTreeJson()
364
    {
365
        $writtenBytes = false;
366
        $tree = $this->buildTree();
367
        $path = realpath($this->dataDirectory);
368
        if ($path && is_writable($path)) {
369
            $full = $path . DIRECTORY_SEPARATOR . Tree::DATA_TREE_FILENAME;
370
            $writtenBytes = file_put_contents($full, json_encode($tree));
371
        }
372
        return $writtenBytes;
373
    }
374
    
375
    /**
376
     * Set the input data source
377
     * @param $path
378
     * @throws ErrorException
379
     */
380
    public function setGeoDataSource($path)
381
    {
382
        if (isset($path) && is_file($path)) {
383
            $this->dataSourcePath = $path;
384
        }else{
385
            throw new ErrorException("ERROR: Geo data source path not found.");
386
        }
387
    }
388
    
389
    /**
390
     * Main public function that starts all processing
391
     */
392
    public function createQuadrantTreeData()
393
    {
394
        echo "Reading data source...\n";
395
        $this->readDataSource();
396
        echo "Saving timezones array...\n";
397
        $this->setTimezonesArray();
398
        echo "Generating indexes...\n";
399
        $this->generateIndexes();
400
    }
401
    
402
    /**
403
     * Find the intersected timezones and the next quadrant to be evaluated
404
     * @param $lastLevelFlag
405
     * @param $curQuadrant
406
     * @return array
407
     */
408
    protected function findTimezonesAndNextQuadrants($lastLevelFlag, $curQuadrant)
409
    {
410
        $quadrantBounds = $curQuadrant['bounds'];
411
        $timezonesToInspect = $this->selectTimeZonesToInspect($curQuadrant);
412
        $intersectionResult = $this->whichTimeZonesIntersect($timezonesToInspect, $quadrantBounds);
413
        $zonesAndNextQuadrants = $this->getAssociatedZonesAndNextQuadrants(
414
            $intersectionResult,
415
            $curQuadrant,
416
            $lastLevelFlag);
417
        return $zonesAndNextQuadrants;
418
    }
419
    
420
    /**
421
     * Add level to the lookup table where the quadrant tree is being defined
422
     * @param $zoneResult
423
     * @param $levelPath
424
     * @return mixed
425
     */
426
    protected function addLevelToLookup($zoneResult, $levelPath)
427
    {
428
        $level = &$this->lookup;
429
        foreach ($levelPath as $levelId) {
430
            $level = &$level[$levelId];
431
        }
432
        $level = $zoneResult;
433
    }
434
    
435
    /**
436
     * Remove level from the lookup table where the quadrant tree is being defined
437
     * @param $levelPath
438
     */
439
    protected function removeLevelFromLookup($levelPath)
440
    {
441
        $level = &$this->lookup;
442
        $levelId = "a";
443
        foreach ($levelPath as $idx => $levelId) {
444
            if (isset($level[$levelId])) {
445
                if ($idx < count($levelPath) - 1) {
446
                    $level = &$level[$levelId];
447
                }
448
            }
449
        }
450
        unset($level[$levelId]);
451
    }
452
}
453