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.

Indexer::removeLevelFromLookup()   A
last analyzed

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

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