Passed
Pull Request — master (#4)
by Mark
01:50
created

TWKB::getGeometry()   F

Complexity

Conditions 20
Paths 4352

Size

Total Lines 96
Code Lines 73

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 73
nc 4352
nop 0
dl 0
loc 96
rs 0
c 0
b 0
f 0

How to fix   Long Method    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
/**
4
 * This file contains the BinaryReader class.
5
 * For more information see the class description below.
6
 *
7
 * @author Peter Bathory <[email protected]>
8
 * @since 2016-02-18
9
 *
10
 * This code is open-source and licenced under the Modified BSD License.
11
 * For the full copyright and license information, please view the LICENSE
12
 * file that was distributed with this source code.
13
 */
14
15
namespace geoPHP\Adapter;
16
17
use geoPHP\Geometry\Collection;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Collection was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
18
use geoPHP\Geometry\Geometry;
19
use geoPHP\Geometry\GeometryCollection;
20
use geoPHP\Geometry\LineString;
21
use geoPHP\Geometry\MultiGeometry;
22
use geoPHP\Geometry\MultiLineString;
23
use geoPHP\Geometry\MultiPoint;
24
use geoPHP\Geometry\MultiPolygon;
25
use geoPHP\Geometry\Point;
0 ignored issues
show
Bug introduced by
The type geoPHP\Geometry\Point was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
26
use geoPHP\Geometry\Polygon;
27
28
/**
29
 * PHP Geometry <-> TWKB encoder/decoder
30
 *
31
 * "Tiny Well-known Binary is is a multi-purpose format for serializing vector geometry data into a byte buffer,
32
 * with an emphasis on minimizing size of the buffer."
33
 * @see https://github.com/TWKB/Specification/blob/master/twkb.md
34
 *
35
 * This implementation supports:
36
 * - reading and writing all geometry types (1-7)
37
 * - empty geometries
38
 * - extended precision (Z, M coordinates; custom precision)
39
 * Partially supports:
40
 * - bounding box: can read and write, but don't store readed boxes (API missing)
41
 * - size attribute: can read and write size attribute, but seeking is not supported
42
 * - ID list: can read and write, but API is completely missing
43
 */
44
class TWKB implements GeoAdapter
45
{
46
47
    protected $writeOptions = [
48
            'decimalDigitsXY' => 5,
49
            'decimalDigitsZ' =>  0,
50
            'decimalDigitsM' =>  0,
51
            'includeSize' => false,
52
            'includeBoundingBoxes' => false,
53
    ];
54
55
    /** @var Point|null  */
56
    private $lastPoint = null;
57
58
    /** @var  BinaryReader $reader */
59
    private $reader;
60
61
    /** @var  BinaryWriter $writer */
62
    private $writer;
63
64
    /** @var array Maps Geometry types to TWKB type codes */
65
    protected static $typeMap = [
66
            Geometry::POINT               => 1,
67
            Geometry::LINE_STRING         => 2,
68
            Geometry::POLYGON             => 3,
69
            Geometry::MULTI_POINT         => 4,
70
            Geometry::MULTI_LINE_STRING   => 5,
71
            Geometry::MULTI_POLYGON       => 6,
72
            Geometry::GEOMETRY_COLLECTION => 7
73
    ];
74
75
    /**
76
     * Read TWKB into geometry objects
77
     *
78
     * @param string $twkb        Tiny Well-known-binary string
79
     * @param bool   $isHexString If this is a hexadecimal string that is in need of packing
80
     *
81
     * @return Geometry
82
     *
83
     * @throws \Exception
84
     */
85
    public function read($twkb, $isHexString = false)
86
    {
87
        if ($isHexString) {
88
            $twkb = @pack('H*', $twkb);
89
        }
90
91
        if (empty($twkb)) {
92
            throw new \Exception('Cannot read empty TWKB. Found ' . gettype($twkb));
93
        }
94
95
        $this->reader = new BinaryReader($twkb);
96
97
        $geometry = $this->getGeometry();
98
99
        $this->reader->close();
100
101
        return $geometry;
102
    }
103
104
    protected function getGeometry()
105
    {
106
        $options = [];
107
        $type = $this->reader->readUInt8();
108
        $metadataHeader = $this->reader->readUInt8();
109
110
        $geometryType = $type & 0x0F;
111
        $options['precision'] = BinaryReader::zigZagDecode($type >> 4);
112
        $options['precisionFactor'] = pow(10, $options['precision']);
113
114
        $options['hasBoundingBox'] = ($metadataHeader >> 0 & 1) == 1;
115
        $options['hasSizeAttribute'] = ($metadataHeader >> 1 & 1) == 1;
116
        $options['hasIdList'] = ($metadataHeader >> 2 & 1) == 1;
117
        $options['hasExtendedPrecision'] = ($metadataHeader >> 3 & 1) == 1;
118
        $options['isEmpty'] = ($metadataHeader >> 4 & 1) == 1;
119
        $options['unused1'] = ($metadataHeader >> 5 & 1) == 1;
120
        $options['unused2'] = ($metadataHeader >> 6 & 1) == 1;
121
        $options['unused3'] = ($metadataHeader >> 7 & 1) == 1;
122
123
        if ($options['hasExtendedPrecision']) {
124
            $extendedPrecision = $this->reader->readUInt8();
125
126
            $options['hasZ'] = ($extendedPrecision & 0x01) === 0x01;
127
            $options['hasM'] = ($extendedPrecision & 0x02) === 0x02;
128
129
            $options['zPrecision'] = ($extendedPrecision & 0x1C) >> 2;
130
            $options['zPrecisionFactor'] = pow(10, $options['zPrecision']);
131
132
            $options['mPrecision'] = ($extendedPrecision & 0xE0) >> 5;
133
            $options['mPrecisionFactor'] = pow(10, $options['mPrecision']);
134
        } else {
135
            $options['hasZ'] = false;
136
            $options['hasM'] = false;
137
        }
138
        if ($options['hasSizeAttribute']) {
139
            $options['remainderSize'] = $this->reader->readUVarInt();
140
        }
141
        if ($options['hasBoundingBox']) {
142
            $dimension = 2 + ($options['hasZ'] ? 1 : 0) + ($options['hasM'] ? 1 : 0);
143
            $precisions = [
144
                $options['precisionFactor'],
145
                $options['precisionFactor'],
146
                $options['hasZ'] ? $options['zPrecisionFactor'] : 0,
147
                $options['hasM'] ? $options['mPrecisionFactor'] : 0
148
            ];
149
            $bBoxMin = $bBoxMax = [];
150
            for ($i = 0; $i < $dimension; $i++) {
151
                $bBoxMin[$i] = $this->reader->readUVarInt() / $precisions[$i];
152
                $bBoxMax[$i] = $this->reader->readUVarInt() / $precisions[$i] + $bBoxMin[$i];
153
            }
154
            /** @noinspection PhpUndefinedVariableInspection (minimum 2 dimension) */
155
            $options['boundingBox'] = ['minXYZM' => $bBoxMin, 'maxXYZM' => $bBoxMax];
156
        }
157
158
        if ($options['unused1']) {
159
            $this->reader->readUVarInt();
160
        }
161
        if ($options['unused2']) {
162
            $this->reader->readUVarInt();
163
        }
164
        if ($options['unused3']) {
165
            $this->reader->readUVarInt();
166
        }
167
168
        $this->lastPoint = new Point(0, 0, 0, 0);
169
170
        switch ($geometryType) {
171
            case 1:
172
                $geometry = $this->getPoint($options);
173
                break;
174
            case 2:
175
                $geometry = $this->getLineString($options);
176
                break;
177
            case 3:
178
                $geometry = $this->getPolygon($options);
179
                break;
180
            case 4:
181
                $geometry = $this->getMulti('Point', $options);
182
                break;
183
            case 5:
184
                $geometry = $this->getMulti('LineString', $options);
185
                break;
186
            case 6:
187
                $geometry = $this->getMulti('Polygon', $options);
188
                break;
189
            case 7:
190
                $geometry = $this->getMulti('Geometry', $options);
191
                break;
192
            default:
193
                throw new \Exception(
194
                    'Geometry type ' . $geometryType .
195
                        ' (' . (array_search($geometryType, self::$typeMap) ?: 'unknown') . ') not supported'
196
                );
197
        }
198
199
        return $geometry;
200
    }
201
202
    /**
203
     * @param array $options
204
     *
205
     * @return Point
206
     * @throws \Exception
207
     */
208
    protected function getPoint($options)
209
    {
210
        if ($options['isEmpty']) {
211
            return new Point();
212
        }
213
        $x = round(
214
            $this->lastPoint->x() + $this->reader->readSVarInt() / $options['precisionFactor'],
0 ignored issues
show
Bug introduced by
The method x() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

214
            $this->lastPoint->/** @scrutinizer ignore-call */ 
215
                              x() + $this->reader->readSVarInt() / $options['precisionFactor'],

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
215
            $options['precision']
216
        );
217
        $y = round(
218
            $this->lastPoint->y() + $this->reader->readSVarInt() / $options['precisionFactor'],
219
            $options['precision']
220
        );
221
        $z = $options['hasZ'] ? round(
222
            $this->lastPoint->z() + $this->reader->readSVarInt() / $options['zPrecisionFactor'],
223
            $options['zPrecision']
224
        ) : null;
225
        $m = $options['hasM'] ? round(
226
            $this->lastPoint->m() + $this->reader->readSVarInt() / $options['mPrecisionFactor'],
227
            $options['mPrecision']
228
        ) : null;
229
230
        $this->lastPoint = new Point($x, $y, $z, $m);
231
        return $this->lastPoint;
232
    }
233
234
    /**
235
     * @param array $options
236
     *
237
     * @return LineString
238
     * @throws \Exception
239
     */
240
    protected function getLineString($options)
241
    {
242
        if ($options['isEmpty']) {
243
            return new LineString();
244
        }
245
246
        $pointCount = $this->reader->readUVarInt();
247
248
        $points = [];
249
        for ($i = 0; $i < $pointCount; $i++) {
250
            $points[] = $this->getPoint($options);
251
        }
252
253
        return new LineString($points);
254
    }
255
256
    /**
257
     * @param array $options
258
     *
259
     * @return Polygon
260
     * @throws \Exception
261
     */
262
    protected function getPolygon($options)
263
    {
264
        if ($options['isEmpty']) {
265
            return new Polygon();
266
        }
267
268
        $ringCount = $this->reader->readUVarInt();
269
270
        $rings = [];
271
        for ($i = 0; $i < $ringCount; $i++) {
272
            $rings[] = $this->getLineString($options);
273
        }
274
275
        return new Polygon($rings, true);
276
    }
277
278
    /**
279
     * @param string $type
280
     * @param array $options
281
     *
282
     * @return MultiGeometry|null
283
     * @throws \Exception
284
     */
285
    protected function getMulti($type, $options)
286
    {
287
        $multiLength = $this->reader->readUVarInt();
288
289
        if ($options['hasIdList']) {
290
            for ($i = 0; $i < $multiLength; $i++) {
291
                $idList[] = $this->reader->readSVarInt();
292
            }
293
        }
294
295
        $components = [];
296
        for ($i = 0; $i < $multiLength; $i++) {
297
            if ($type !== 'Geometry') {
298
                $func = 'get' . $type;
299
                $components[] = $this->$func($options);
300
            } else {
301
                $components[] = $this->getGeometry();
302
            }
303
        }
304
        switch ($type) {
305
            case 'Point':
306
                return new MultiPoint($components);
307
            case 'LineString':
308
                return new MultiLineString($components);
309
            case 'Polygon':
310
                return new MultiPolygon($components);
311
            case 'Geometry':
312
                return new GeometryCollection($components);
313
        }
314
        return null;
315
    }
316
317
318
/******* WRITER *******/
319
320
    /**
321
     * Serialize geometries into TWKB string.
322
     *
323
     * @return string The WKB string representation of the input geometries
324
     * @param Geometry $geometry The geometry
325
     * @param bool|true $writeAsHex Write the result in binary or hexadecimal system
326
     * @param null $decimalDigitsXY Coordinate precision of X and Y. Default is 5 decimals
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $decimalDigitsXY is correct as it would always require null to be passed?
Loading history...
327
     * @param null $decimalDigitsZ Coordinate precision of Z. Default is 0 decimal
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $decimalDigitsZ is correct as it would always require null to be passed?
Loading history...
328
     * @param null $decimalDigitsM Coordinate precision of M. Default is 0 decimal
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $decimalDigitsM is correct as it would always require null to be passed?
Loading history...
329
     * @param bool $includeSizes Includes the size in bytes of the remainder of the geometry after the size attribute. Default is false
330
     * @param bool $includeBoundingBoxes Includes the coordinates of bounding box' two corner. Default is false
331
     *
332
     * @return string binary or hexadecimal representation of TWKB
333
     */
334
    public function write(Geometry $geometry, $writeAsHex = false, $decimalDigitsXY = null, $decimalDigitsZ = null, $decimalDigitsM = null, $includeSizes = false, $includeBoundingBoxes = false)
335
    {
336
        $this->writer = new BinaryWriter();
337
338
        $this->writeOptions = [
339
                'decimalDigitsXY' => $decimalDigitsXY !== null ? $decimalDigitsXY : $this->writeOptions['decimalDigitsXY'],
0 ignored issues
show
introduced by
The condition $decimalDigitsXY !== null is always false.
Loading history...
340
                'decimalDigitsZ' => $decimalDigitsZ !== null ? $decimalDigitsZ : $this->writeOptions['decimalDigitsZ'],
0 ignored issues
show
introduced by
The condition $decimalDigitsZ !== null is always false.
Loading history...
341
                'decimalDigitsM' => $decimalDigitsM !== null ? $decimalDigitsM : $this->writeOptions['decimalDigitsM'],
0 ignored issues
show
introduced by
The condition $decimalDigitsM !== null is always false.
Loading history...
342
                'includeSize' => $includeSizes ? true : $this->writeOptions['includeSize'],
343
                'includeBoundingBoxes' => $includeBoundingBoxes ? true : $this->writeOptions['includeBoundingBoxes']
344
        ];
345
        $this->writeOptions = array_merge(
346
            $this->writeOptions,
347
            [
348
                'xyFactor' => pow(10, $this->writeOptions['decimalDigitsXY']),
349
                'zFactor' => pow(10, $this->writeOptions['decimalDigitsZ']),
350
                'mFactor' => pow(10, $this->writeOptions['decimalDigitsM'])
351
            ]
352
        );
353
354
        $twkb = $this->writeGeometry($geometry);
355
356
        return $writeAsHex ? current(unpack('H*', $twkb)) : $twkb;
357
    }
358
359
    /**
360
     * @param Geometry $geometry
361
     * @return string
362
     */
363
    protected function writeGeometry($geometry)
364
    {
365
        $this->writeOptions['hasZ'] = $geometry->hasZ();
366
        $this->writeOptions['hasM'] = $geometry->isMeasured();
367
368
        // Type and precision
369
        $type = self::$typeMap[$geometry->geometryType()] +
370
                (BinaryWriter::zigZagEncode($this->writeOptions['decimalDigitsXY']) << 4);
371
        $twkbHead = $this->writer->writeUInt8($type);
372
373
        // Is there extended precision information?
374
        $metadataHeader = $this->writeOptions['includeBoundingBoxes'] << 0;
375
        // Is there extended precision information?
376
        $metadataHeader += $this->writeOptions['includeSize'] << 1;
377
        // Is there an ID list?
378
        // TODO: implement this (needs metadata support in geoPHP)
379
        //$metadataHeader += $this->writeOptions['hasIdList'] << 2;
380
        // Is there extended precision information?
381
        $metadataHeader += ($geometry->hasZ() || $geometry->isMeasured()) << 3;
382
        // Is this an empty geometry?
383
        $metadataHeader += $geometry->isEmpty() << 4;
384
385
        $twkbHead .= $this->writer->writeUInt8($metadataHeader);
386
387
        $twkbGeom = '';
388
        if (!$geometry->isEmpty()) {
389
            $this->lastPoint = new Point(0, 0, 0, 0);
390
391
            switch ($geometry->geometryType()) {
392
                case Geometry::POINT:
393
                    /** @var Point $geometry */
394
                    $twkbGeom .= $this->writePoint($geometry);
395
                    break;
396
                case Geometry::LINE_STRING:
397
                    /** @var LineString $geometry */
398
                    $twkbGeom .= $this->writeLineString($geometry);
399
                    break;
400
                case Geometry::POLYGON:
401
                    /** @var Polygon $geometry */
402
                    $twkbGeom .= $this->writePolygon($geometry);
403
                    break;
404
                case Geometry::MULTI_POINT:
405
                case Geometry::MULTI_LINE_STRING:
406
                case Geometry::MULTI_POLYGON:
407
                case Geometry::GEOMETRY_COLLECTION:
408
                    /** @var Collection $geometry */
409
                    $twkbGeom .= $this->writeMulti($geometry);
410
                    break;
411
            }
412
        }
413
414
        if ($this->writeOptions['includeBoundingBoxes']) {
415
            $bBox = $geometry->getBoundingBox();
416
            // X
417
            $twkbBox = $this->writer->writeSVarInt($bBox['minx'] * $this->writeOptions['xyFactor']);
418
            $twkbBox .= $this->writer->writeSVarInt(($bBox['maxx'] - $bBox['minx']) * $this->writeOptions['xyFactor']);
419
            // Y
420
            $twkbBox .= $this->writer->writeSVarInt($bBox['miny'] * $this->writeOptions['xyFactor']);
421
            $twkbBox .= $this->writer->writeSVarInt(($bBox['maxy'] - $bBox['miny']) * $this->writeOptions['xyFactor']);
422
            if ($geometry->hasZ()) {
423
                $bBox['minz'] = $geometry->minimumZ();
424
                $bBox['maxz'] = $geometry->maximumZ();
425
                $twkbBox .= $this->writer->writeSVarInt(round($bBox['minz'] * $this->writeOptions['zFactor']));
0 ignored issues
show
Bug introduced by
round($bBox['minz'] * $t...riteOptions['zFactor']) of type double is incompatible with the type integer expected by parameter $value of geoPHP\Adapter\BinaryWriter::writeSVarInt(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

425
                $twkbBox .= $this->writer->writeSVarInt(/** @scrutinizer ignore-type */ round($bBox['minz'] * $this->writeOptions['zFactor']));
Loading history...
426
                $twkbBox .= $this->writer->writeSVarInt(round(($bBox['maxz'] - $bBox['minz']) * $this->writeOptions['zFactor']));
427
            }
428
            if ($geometry->isMeasured()) {
429
                $bBox['minm'] = $geometry->minimumM();
430
                $bBox['maxm'] = $geometry->maximumM();
431
                $twkbBox .= $this->writer->writeSVarInt($bBox['minm'] * $this->writeOptions['mFactor']);
432
                $twkbBox .= $this->writer->writeSVarInt(($bBox['maxm'] - $bBox['minm']) * $this->writeOptions['mFactor']);
433
            }
434
            $twkbGeom = $twkbBox . $twkbGeom;
435
        }
436
437
        if ($geometry->hasZ() || $geometry->isMeasured()) {
438
            $extendedPrecision = 0;
439
            if ($geometry->hasZ()) {
440
                $extendedPrecision |= ($geometry->hasZ() ? 0x1 : 0) | ($this->writeOptions['decimalDigitsZ'] << 2);
441
            }
442
            if ($geometry->isMeasured()) {
443
                $extendedPrecision |= ($geometry->isMeasured() ? 0x2 : 0) | ($this->writeOptions['decimalDigitsM'] << 5);
444
            }
445
            $twkbHead .= $this->writer->writeUInt8($extendedPrecision);
446
        }
447
        if ($this->writeOptions['includeSize']) {
448
            $twkbHead .= $this->writer->writeUVarInt(strlen($twkbGeom));
449
        }
450
451
        return $twkbHead . $twkbGeom;
452
    }
453
454
    /**
455
     * @param Point $geometry
456
     * @return string
457
     */
458
    protected function writePoint($geometry)
459
    {
460
        $x = round($geometry->x() * $this->writeOptions['xyFactor']);
461
        $y = round($geometry->y() * $this->writeOptions['xyFactor']);
462
        $z = round($geometry->z() * $this->writeOptions['zFactor']);
463
        $m = round($geometry->m() * $this->writeOptions['mFactor']);
464
465
        $twkb = $this->writer->writeSVarInt($x - $this->lastPoint->x());
0 ignored issues
show
Bug introduced by
$x - $this->lastPoint->x() of type double is incompatible with the type integer expected by parameter $value of geoPHP\Adapter\BinaryWriter::writeSVarInt(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

465
        $twkb = $this->writer->writeSVarInt(/** @scrutinizer ignore-type */ $x - $this->lastPoint->x());
Loading history...
466
        $twkb .= $this->writer->writeSVarInt($y - $this->lastPoint->y());
467
        if ($this->writeOptions['hasZ']) {
468
            $twkb .= $this->writer->writeSVarInt($z - $this->lastPoint->z());
469
        }
470
        if ($this->writeOptions['hasM']) {
471
            $twkb .= $this->writer->writeSVarInt($m - $this->lastPoint->m());
472
        }
473
474
        $this->lastPoint = new Point($x, $y, $this->writeOptions['hasZ'] ? $z : null, $this->writeOptions['hasM'] ? $m : null);
475
476
        return $twkb;
477
    }
478
479
    /**
480
     * @param LineString $geometry
481
     * @return string
482
     */
483
    protected function writeLineString($geometry)
484
    {
485
        $twkb = $this->writer->writeUVarInt($geometry->numPoints());
486
        foreach ($geometry->getComponents() as $component) {
487
            $twkb .= $this->writePoint($component);
488
        }
489
        return $twkb;
490
    }
491
492
    /**
493
     * @param Polygon $geometry
494
     * @return string
495
     */
496
    protected function writePolygon($geometry)
497
    {
498
        $twkb = $this->writer->writeUVarInt($geometry->numGeometries());
499
        foreach ($geometry->getComponents() as $component) {
500
            $twkb .= $this->writeLineString($component);
501
        }
502
        return $twkb;
503
    }
504
505
    /**
506
     * @param Collection $geometry
507
     * @return string
508
     */
509
    protected function writeMulti($geometry)
510
    {
511
        $twkb = $this->writer->writeUVarInt($geometry->numGeometries());
512
        //if ($geometry->hasIdList()) {
513
        //  foreach ($geometry->getComponents() as $component) {
514
        //      $this->writer->writeUVarInt($component->getId());
515
        //  }
516
        //}
517
        foreach ($geometry->getComponents() as $component) {
518
            if ($geometry->geometryType() !== Geometry::GEOMETRY_COLLECTION) {
519
                $func = 'write' . $component->geometryType();
520
                $twkb .= $this->$func($component);
521
            } else {
522
                $twkb .= $this->writeGeometry($component);
523
            }
524
        }
525
        return $twkb;
526
    }
527
}
528