Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
97.73% covered (success)
97.73%
43 / 44
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
Intensity
97.73% covered (success)
97.73%
43 / 44
83.33% covered (warning)
83.33%
5 / 6
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 getIterator
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 fetch
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
4
 fetchFromLocalPath
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 fetchFromUrl
94.12% covered (success)
94.12%
16 / 17
0.00% covered (danger)
0.00%
0 / 1
3.00
 isCurlCloseSupported
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2namespace Shakemaps\Webservice;
3
4use ArrayIterator;
5use IteratorAggregate;
6use Traversable;
7
8use Fdsn\Webservices\Event\Structs\Id as DS_Id;
9use Fdsn\Webservices\Event\Structs\LatLon as DS_LatLon;
10
11/**
12 * Fetch grid.xml intensity file from Shakemaps portal 
13 * 
14 * @param Fdsn\Webservices\Event\Structs\Id $eventObj Quake to scan for data 
15 * @param string $shakemapsServer (nullable) Server FQDN to connect to (default is shakemaps.rm.ingv.it)
16 * @param string $localFileFullPath (nullable) path to fetch local file  (if exists AND is readable)
17 */
18class Intensity implements IteratorAggregate {
19    private const dataFile = 'grid.xml';
20
21    private const curlCloceLatestPhpVersion = 7;
22
23    private string $server;
24    private DS_Id $eventId;
25    
26    private ?string $localFileFullPath = null;
27
28    private string $curlErrInfo;
29    private int $curlErrNum;
30    private array $places = array();
31
32    function __construct ( DS_Id $eventId, ?string $shakemapsServer = Settings::defaultShakemapsServer, ?string $localFileFullPath = null ) {
33
34        $this->eventId = $eventId;
35        $this->server = empty($shakemapsServer) ? Settings::defaultShakemapsServer : $shakemapsServer;
36
37        if ( is_null($localFileFullPath) )
38            return;
39        
40                $this->localFileFullPath = $localFileFullPath;
41    }
42
43        /**
44         * get all places intensity
45         * 
46         * @return array locality intensity (array of associative arrays[DS_LatLon, mmi, pga, pgv, psa03, psa10, psa30, svel])
47         */
48        public function getIterator():Traversable{ return new ArrayIterator($this->places); }
49 
50        /**
51         * Fetch data (remote or local - if $localFileFullPath is set in __construct)
52         * 
53         * @return int number of data found
54         * 
55         * @internal DOMDocument::load(): <grid_data> contains a HUGE CSV text and xmlSAX2Characters crash
56         *         so i must parse it like an array
57         */
58    public function fetch():int{
59        $xmlString = is_null($this->localFileFullPath) ? $this->fetchFromUrl() : $this->fetchFromLocalPath();
60
61        $xmlAsArray = preg_split("/\n/", $xmlString);
62        foreach($xmlAsArray as $line){
63            if( ! preg_match('/^[0-9]/', $line) ) 
64                continue;
65
66            list($lon, $lat, $mmi, $pga, $pgv, $psa03, $psa10, $psa30, $svel) = preg_split('[\s]', trim($line) );
67            $this->places[] = array(
68                'point' => new DS_LatLon($lat, $lon),
69                'mmi' => $mmi, 
70                'pga' => $pga, 
71                'pgv' => $pgv, 
72                'psa03' => $psa03, 
73                'psa10' => $psa10, 
74                'psa30' => $psa30, 
75                'svel' => $svel
76            );
77        }
78
79        return count($this->places);
80    }
81
82
83        /**
84         * fetch data from local path - if $localFileFullPath is set in __construct()
85         * 
86         * @return string Datafile read in a string or, on missing/unreadable file a RuntimeException is thrown
87         */
88    private function fetchFromLocalPath():string { 
89                if ( ! file_exists($this->localFileFullPath) || ! is_readable($this->localFileFullPath) )
90                        throw new \RuntimeException(sprintf("[%s] file %s not exists or is not readable", __METHOD__, $this->localFileFullPath));
91
92        return file_get_contents( $this->localFileFullPath); 
93    }
94
95        /**
96         * fetch data from url  (remote or local - if $localFileFullPath is set in __construct)
97         * 
98         * @return int number of data found
99         */
100    private function fetchFromUrl():string{
101        $curlSession = curl_init();
102        curl_setopt_array($curlSession, array(
103            CURLOPT_URL             => sprintf(Settings::basePath, $this->server, $this->eventId->value(), self::dataFile),
104            CURLOPT_HEADER          => false,
105            CURLOPT_CUSTOMREQUEST   => 'GET',
106            CURLOPT_RETURNTRANSFER  => 1,
107            CURLOPT_TIMEOUT         => 60
108            )
109        );
110
111        $response = curl_exec($curlSession);
112        $this->curlErrNum = curl_errno($curlSession);
113        $this->curlErrInfo = curl_error($curlSession);
114        if ( $this->isCurlCloseSupported() )
115            curl_close($curlSession);
116
117        if(CURLE_OK != $this->curlErrNum)
118            throw new \RuntimeException( sprintf("[%s] [CurlErr: %d] %s", __METHOD__, $this->curlErrNum, $this->curlErrInfo));
119
120        return $response;
121    }
122
123    /**
124     * Check if curl_close is still supported
125     * @return bool true is supported, false otherwise
126     */
127    private function isCurlCloseSupported():bool{ return PHP_MAJOR_VERSION <= self::curlCloceLatestPhpVersion; }
128}
129?>