├── .gitignore
├── phpunit.xml
├── meteo-alarm-weather-warnings.png
├── bootstrap.php
├── src
├── Description.php
├── Result.php
├── Warning.php
├── Config.php
├── Period.php
├── AwarenessType.php
├── Warnings.php
├── Awareness.php
├── AwarenessLevel.php
├── warnings.json
└── Region.php
├── LICENSE
├── test
├── DescriptionTest.php
├── AwarenessTest.php
├── WarningTest.php
├── PeriodTest.php
├── AwarenessLevelTest.php
├── AwarenessTypeTest.php
├── RegionTest.php
└── WarningsTest.php
├── AutoLoader.php
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/meteo-alarm-weather-warnings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/waynedgrant/meteo-alarm-weather-warnings/HEAD/meteo-alarm-weather-warnings.png
--------------------------------------------------------------------------------
/bootstrap.php:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/src/Description.php:
--------------------------------------------------------------------------------
1 | A spell of heavy snow is possible over parts of Scotland during Sunday.
10 | if (trim($description_cell->textContent) !== '') {
11 | $this->text = $description_cell->textContent;
12 | }
13 | }
14 |
15 | public function text() {
16 | return $this->text;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/Result.php:
--------------------------------------------------------------------------------
1 | warnings = new Warnings($rss);
11 | }
12 |
13 | private function createServiceUrl() {
14 | return 'http'.(!empty($_SERVER['HTTPS'])?'s':'').'://'.$_SERVER['SERVER_NAME'].$_SERVER['REQUEST_URI'];
15 | }
16 |
17 | public function serialize() {
18 | return [
19 | 'endpoint' => [
20 | 'url' => $this->createServiceUrl(),
21 | 'version' => self::SERVICE_VERSION,
22 | 'github_project' => self::GITHUB_PROJECT_LINK,
23 | 'copyright' => self::COPYRIGHT_NOTICE
24 | ],
25 | 'warnings' => $this->warnings->serialize()
26 | ];
27 | }
28 | }
29 | ?>
30 |
--------------------------------------------------------------------------------
/src/Warning.php:
--------------------------------------------------------------------------------
1 |
... awareness ... ... period ...
10 | $awareness_cell = $awarness_period_row->getElementsByTagName('td')->item(0);
11 | $this->awareness = new Awareness($awareness_cell);
12 |
13 | $period_cell = $awarness_period_row->getElementsByTagName('td')->item(1);
14 | $this->period = new Period($period_cell);
15 |
16 | // e.g. ... description ...
17 | $description_cell = $description_row->getElementsByTagName('td')->item(1);
18 | $this->description = new Description($description_cell);
19 | }
20 |
21 | public function serialize() {
22 | return [
23 | 'awareness' => $this->awareness->serialize(),
24 | 'period' => $this->period->serialize(),
25 | 'description' => $this->description->text
26 | ];
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Wayne D Grant
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/test/DescriptionTest.php:
--------------------------------------------------------------------------------
1 | ' .
11 | 'A spell of heavy snow is possible over parts of Scotland during Sunday.' .
12 | '';
13 |
14 | $description_dom = DOMDocument::loadHTML($description_html);
15 | $description_cell = $description_dom->getElementsByTagName('td')->item(0);
16 |
17 | $testee = new Description($description_cell);
18 |
19 | $this->assertSame('A spell of heavy snow is possible over parts of Scotland during Sunday.', $testee->text());
20 | }
21 |
22 | public function test_no_description_present() {
23 | $description_html = ' ';
24 |
25 | $description_dom = DOMDocument::loadHTML($description_html);
26 | $description_cell = $description_dom->getElementsByTagName('td')->item(0);
27 |
28 | $testee = new Description($description_cell);
29 |
30 | $this->assertNull($testee->text());
31 | }
32 | }
33 |
34 | ?>
35 |
--------------------------------------------------------------------------------
/src/Config.php:
--------------------------------------------------------------------------------
1 |
47 |
--------------------------------------------------------------------------------
/AutoLoader.php:
--------------------------------------------------------------------------------
1 | isDir() && !$file->isLink() && !$file->isDot()) {
16 | self::registerDirectory($file->getPathname());
17 | }
18 | elseif (substr($file->getFilename(), -4) === '.php') {
19 | $className = substr($file->getFilename(), 0, -4);
20 | AutoLoader::registerClass($className, $file->getPathname());
21 | }
22 | }
23 | }
24 |
25 | public static function registerClass($className, $fileName) {
26 | AutoLoader::$classNames[$className] = $fileName;
27 | }
28 |
29 | public static function loadClass($className) {
30 | if (isset(AutoLoader::$classNames[$className])) {
31 | require_once(AutoLoader::$classNames[$className]);
32 | }
33 | }
34 |
35 | }
36 |
37 | spl_autoload_register(array('AutoLoader', 'loadClass'));
38 |
39 | ?>
40 |
--------------------------------------------------------------------------------
/src/Period.php:
--------------------------------------------------------------------------------
1 | From: 10.12.2017 05:00 CET Until: 11.12.2017 00:55 CET
12 | or if there is no period, i.e. when awareness level == NO_WARNINGS */
13 |
14 | if (trim($period_cell->textContent) !== '') {
15 | $from_date_text = $period_cell->getElementsByTagName('i')->item(0)->textContent;
16 | $until_date_text = $period_cell->getElementsByTagName('i')->item(1)->textContent;
17 |
18 | $this->from = $this->parseDateTime($from_date_text);
19 | $this->until = $this->parseDateTime($until_date_text);
20 | }
21 | }
22 |
23 | private function parseDateTime($date_text) {
24 | $date = date_create_from_format(self::DATE_TIME_FORMAT, $date_text);
25 | date_timezone_set($date, new DateTimeZone(Config::getTimeZone()));
26 | return $date->format(Config::getDateTimeFormat());
27 | }
28 |
29 | public function serialize() {
30 | return [
31 | 'from' => $this->from,
32 | 'until' => $this->until
33 | ];
34 | }
35 | }
36 |
37 | ?>
38 |
--------------------------------------------------------------------------------
/src/AwarenessType.php:
--------------------------------------------------------------------------------
1 | = sizeof($this->descriptions)) {
42 | throw new InvalidArgumentException('Awareness type ' . $type . ' is not supported.');
43 | }
44 |
45 | $this->type = $type;
46 | $this->description = $this->descriptions[$type];
47 | }
48 |
49 | public function serialize() {
50 | return [
51 | 'type' => $this->type,
52 | 'description' => $this->description
53 | ];
54 | }
55 | }
56 |
57 | ?>
58 |
--------------------------------------------------------------------------------
/src/Warnings.php:
--------------------------------------------------------------------------------
1 |
11 |
12 | -
13 |
United Kingdom
14 | ...
15 |
16 | -
17 |
Dumfries and Galloway
18 | ...
19 |
20 | -
21 |
Lothian & Borders
22 | ...
23 |
24 |
25 | */
26 |
27 | $rss_channel = $rss->channel;
28 | $this->link = (string)$rss_channel->link;
29 |
30 | if (count($rss_channel->item) > 1) { // Mutiple items == warnings for each region in a country
31 | for ($i=0; $i < count($rss_channel->item); $i++) {
32 | if ($i !== 0) { // Drop the first item which is actually for the entire country
33 | $this->regions[] = new Region($rss_channel->item[$i]);
34 | }
35 | }
36 | } else { // Single item == warning for a single region
37 | $this->regions[] = new Region($rss_channel->item);
38 | }
39 | }
40 |
41 | public function serialize() {
42 | foreach ($this->regions as $region) {
43 | $regions[] = $region->serialize();
44 | }
45 |
46 | return [
47 | 'country' => Config::getCountry(),
48 | 'regions' => $regions
49 | ];
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/test/AwarenessTest.php:
--------------------------------------------------------------------------------
1 | ' .
11 | ' ' .
12 | '';
13 |
14 | $awareness_dom = DOMDocument::loadHTML($awareness_html);
15 | $awareness_cell = $awareness_dom->getElementsByTagName('td')->item(0);
16 |
17 | $testee = new Awareness($awareness_cell);
18 | $serialized = $testee->serialize();
19 |
20 | $this->assertSame('http://web.meteoalarm.eu/documents/rss/wflag-l3-t2.jpg', $serialized['icon']);
21 | $this->assertSame(AwarenessType::SNOW_ICE, $serialized['awareness_type']['type']);
22 | $this->assertSame(AwarenessLevel::AMBER, $serialized['awareness_level']['level']);
23 | }
24 |
25 | public function test_no_warning() {
26 | // Any awareness type other than 'None' makes no sense when level is 'Green'
27 | $awareness_html =
28 | '' .
29 | ' ' .
30 | ' ';
31 |
32 | $awareness_dom = DOMDocument::loadHTML($awareness_html);
33 | $awareness_cell = $awareness_dom->getElementsByTagName('td')->item(0);
34 |
35 | $testee = new Awareness($awareness_cell);
36 | $serialized = $testee->serialize();
37 |
38 | $this->assertSame('http://web.meteoalarm.eu/documents/rss/wflag-l1-t3.jpg', $serialized['icon']);
39 | $this->assertSame(AwarenessType::NO_WARNINGS, $serialized['awareness_type']['type']);
40 | $this->assertSame(AwarenessLevel::GREEN, $serialized['awareness_level']['level']);
41 | }
42 | }
43 |
44 | ?>
45 |
--------------------------------------------------------------------------------
/src/Awareness.php:
--------------------------------------------------------------------------------
1 |
10 | $img = $awareness_cell->getElementsByTagName('img')->item(0);
11 |
12 | $this->icon = $img->getAttribute('src');
13 |
14 | $img_alt = $img->getAttribute('alt');
15 |
16 | $this->parse_awareness_level($img_alt);
17 | $this->parse_awareness_type($img_alt);
18 | }
19 |
20 | private function parse_awareness_level($img_alt) {
21 | // e.g. level value from "awt:4 level:3"
22 | $start_awareness_level_index = strrpos($img_alt, ':') + 1;
23 | $awareness_level_length = strlen($img_alt) - $start_awareness_level_index;
24 | $this->awarenessLevel = new AwarenessLevel(intval(substr($img_alt, $start_awareness_level_index, $awareness_level_length)));
25 | }
26 |
27 | private function parse_awareness_type($img_alt) {
28 | if ($this->awarenessLevel->serialize()['level'] == AwarenessLevel::GREEN) {
29 | $this->awarenessType = new AwarenessType(AwarenessType::NO_WARNINGS); // Awareness types need to be corrected to be 'no warnings' when level is green
30 | } else {
31 | // e.g. awt value from "awt:4 level:3"
32 | $start_awareness_type_index = strpos($img_alt, ':') + 1;
33 | $awareness_type_length = strpos($img_alt, ' ') - $start_awareness_type_index;
34 | $this->awarenessType = new AwarenessType(intval(substr($img_alt, $start_awareness_type_index, $awareness_type_length)));
35 | }
36 | }
37 |
38 | public function serialize() {
39 | return [
40 | 'icon' => $this->icon,
41 | 'awareness_type' => $this->awarenessType->serialize(),
42 | 'awareness_level' => $this->awarenessLevel->serialize()
43 | ];
44 | }
45 | }
46 |
47 | ?>
48 |
--------------------------------------------------------------------------------
/test/WarningTest.php:
--------------------------------------------------------------------------------
1 | ' .
16 | '' .
17 | ' ' .
18 | ' ' .
19 | '' .
20 | 'From: 10.12.2017 05:00 CET Until: 11.12.2017 00:55 CET ' .
21 | ' ' .
22 | '';
23 |
24 | $awarness_period_dom = DOMDocument::loadHTML($awarness_period_html);
25 | $awarness_period_row = $awarness_period_dom->getElementsByTagName('tr')->item(0);
26 |
27 | $description_html =
28 | '' .
29 | '' .
30 | ' ' .
31 | '' .
32 | 'A spell of heavy snow is possible over parts of Scotland during Sunday.' .
33 | ' ' .
34 | ' ';
35 |
36 | $description_dom = DOMDocument::loadHTML($description_html);
37 | $description_row = $description_dom->getElementsByTagName('tr')->item(0);
38 |
39 | $testee = new Warning($awarness_period_row, $description_row);
40 | $serialized = $testee->serialize();
41 |
42 | $this->assertSame(AwarenessType::SNOW_ICE, $serialized['awareness']['awareness_type']['type']);
43 | $this->assertSame(AwarenessLevel::AMBER, $serialized['awareness']['awareness_level']['level']);
44 | $this->assertSame('Sunday 04:00 GMT', $serialized['period']['from']);
45 | $this->assertSame('Sunday 23:55 GMT', $serialized['period']['until']);
46 | $this->assertSame('A spell of heavy snow is possible over parts of Scotland during Sunday.', $serialized['description']);
47 | }
48 | }
49 |
50 | ?>
51 |
--------------------------------------------------------------------------------
/src/AwarenessLevel.php:
--------------------------------------------------------------------------------
1 | = sizeof($this->colours)) {
25 | throw new InvalidArgumentException('Awareness level ' . $level . ' is not supported.');
26 | }
27 |
28 | $this->level = $level;
29 | $this->colour = $this->colours[$level];
30 | $this->description = $this->descriptions[$level];
31 | }
32 |
33 | public function serialize() {
34 | return [
35 | 'level' => $this->level,
36 | 'colour' => $this->colour,
37 | 'description' => $this->description
38 | ];
39 | }
40 | }
41 |
42 | ?>
43 |
--------------------------------------------------------------------------------
/test/PeriodTest.php:
--------------------------------------------------------------------------------
1 | ' .
16 | 'From: 10.12.2017 05:00 CET Until: 11.12.2017 00:55 CET ' .
17 | '';
18 |
19 | $period_dom = DOMDocument::loadHTML($period_html);
20 | $period_cell = $period_dom->getElementsByTagName('td')->item(0);
21 |
22 | $testee = new Period($period_cell);
23 | $serialized = $testee->serialize();
24 |
25 | $this->assertSame('Sunday 04:00 GMT', $serialized['from']);
26 | $this->assertSame('Sunday 23:55 GMT', $serialized['until']);
27 | }
28 |
29 | public function test_period_with_alternative_date_time_format_and_timezone() {
30 | Config::setDateTimeFormat('Y-m-d H:i T');
31 | Config::setTimeZone('America/New_York');
32 |
33 | $period_html =
34 | '' .
35 | 'From: 10.12.2017 05:00 CET Until: 11.12.2017 00:55 CET ' .
36 | ' ';
37 |
38 | $period_dom = DOMDocument::loadHTML($period_html);
39 | $period_cell = $period_dom->getElementsByTagName('td')->item(0);
40 |
41 | $testee = new Period($period_cell);
42 | $serialized = $testee->serialize();
43 |
44 | $this->assertSame('2017-12-09 23:00 EST', $serialized['from']);
45 | $this->assertSame('2017-12-10 18:55 EST', $serialized['until']);
46 | }
47 |
48 | public function test_no_period_present() {
49 | $period_html = ' ';
50 |
51 | $period_dom = DOMDocument::loadHTML($period_html);
52 | $period_cell = $period_dom->getElementsByTagName('td')->item(0);
53 |
54 | $testee = new Period($period_cell);
55 | $serialized = $testee->serialize();
56 |
57 | $this->assertNull($serialized['from']);
58 | $this->assertNull($serialized['until']);
59 | }
60 | }
61 |
62 | ?>
63 |
--------------------------------------------------------------------------------
/test/AwarenessLevelTest.php:
--------------------------------------------------------------------------------
1 | serialize();
11 |
12 | $this->assertSame(0, $serialized['level']);
13 | $this->assertSame("White", $serialized['colour']);
14 | $this->assertSame("Unknown", $serialized['description']);
15 | }
16 |
17 | public function test_green() {
18 | $testee = new AwarenessLevel(AwarenessLevel::GREEN);
19 | $serialized = $testee->serialize();
20 |
21 | $this->assertSame(1, $serialized['level']);
22 | $this->assertSame("Green", $serialized['colour']);
23 | $this->assertSame("No particular awareness of the weather is required.", $serialized['description']);
24 | }
25 |
26 | public function test_yellow() {
27 | $testee = new AwarenessLevel(AwarenessLevel::YELLOW);
28 | $serialized = $testee->serialize();
29 |
30 | $this->assertSame(2, $serialized['level']);
31 | $this->assertSame("Yellow", $serialized['colour']);
32 | $this->assertRegexp("/The weather is potentially dangerous./", $serialized['description']);
33 | }
34 |
35 | public function test_amber() {
36 | $testee = new AwarenessLevel(AwarenessLevel::AMBER);
37 | $serialized = $testee->serialize();
38 |
39 | $this->assertSame(3, $serialized['level']);
40 | $this->assertSame("Amber", $serialized['colour']);
41 | $this->assertRegexp("/The weather is dangerous./", $serialized['description']);
42 | }
43 |
44 | public function test_red() {
45 | $testee = new AwarenessLevel(AwarenessLevel::RED);
46 | $serialized = $testee->serialize();
47 |
48 | $this->assertSame(4, $serialized['level']);
49 | $this->assertSame("Red", $serialized['colour']);
50 | $this->assertRegexp("/The weather is very dangerous./", $serialized['description']);
51 | }
52 |
53 | public function test_invalid_low_level() {
54 | $this->expectException(InvalidArgumentException::class);
55 |
56 | $testee = new AwarenessLevel(-1);
57 | }
58 |
59 | public function test_invalid_high_level() {
60 | $this->expectException(InvalidArgumentException::class);
61 |
62 | $testee = new AwarenessLevel(666);
63 | }
64 | }
65 |
66 | ?>
67 |
--------------------------------------------------------------------------------
/src/warnings.json:
--------------------------------------------------------------------------------
1 | serialize());
31 |
32 | respond(200, $json);
33 |
34 | function parse_parameters() {
35 | if (!isset($_GET['country'])) {
36 | respond(400, create_error_json('GET parameter country is required'));
37 | }
38 |
39 | Config::setCountry(strtoupper($_GET['country']));
40 |
41 | if (isset($_GET['region'])) {
42 | Config::setRegion($_GET['region']);
43 | }
44 |
45 | if (isset($_GET['date_time_format'])) {
46 | Config::setDateTimeFormat($_GET['date_time_format']);
47 | }
48 |
49 | if (isset($_GET['time_zone'])) {
50 | Config::setTimeZone($_GET['time_zone']);
51 | }
52 | }
53 |
54 | function generate_rss_link() {
55 | if (!is_null(Config::getRegion())) {
56 | // Request for a single region, e.g. http://meteoalarm.eu/documents/rss/uk/UK004.rss
57 | return sprintf('http://meteoalarm.eu/documents/rss/%s/%s%s.rss', strtolower(Config::getCountry()), Config::getCountry(), Config::getRegion());
58 | } else {
59 | // Request for an entire country, e.g. http://www.meteoalarm.eu/documents/rss/uk.rss
60 | return sprintf('http://www.meteoalarm.eu/documents/rss/%s.rss', strtolower(Config::getCountry()));
61 | }
62 | }
63 |
64 | function parse_response_code($headers) {
65 |
66 | // If we don't get parseable headers we assume we have an invalid reponse from the meteoalarm server
67 | $response_code = 502;
68 |
69 | if (is_array($headers)) {
70 |
71 | foreach ($headers as $k=>$v) {
72 | if (preg_match( "#HTTP/[0-9\.]+\s+([0-9]+)#", $v, $out)) {
73 | $response_code = intval($out[1]);
74 | break;
75 | }
76 | }
77 | }
78 |
79 | return $response_code;
80 | }
81 |
82 | function fix_rss_ampersand_escaping($rss) {
83 | /* Some ampersands are escaped in the received RSS and some are not.
84 | Address this by escaping them all and then fixing any resultant double escapes */
85 | $rss = str_replace('&', '&', $rss);
86 | $rss = str_replace('&', '&', $rss);
87 | return $rss;
88 | }
89 |
90 | function create_error_json($error_message) {
91 | return json_encode(array(
92 | 'error' => $error_message
93 | ));
94 | }
95 |
96 | function respond($code, $json) {
97 | header('Status: ' . $code);
98 | echo isset($_GET['callback']) ? "{$_GET['callback']}($json)" : $json;
99 | die();
100 | }
101 |
102 | ?>
103 |
--------------------------------------------------------------------------------
/src/Region.php:
--------------------------------------------------------------------------------
1 |
13 | Lothian & Borders
14 | http://web.meteoalarm.eu/en_UK/0/0/UK004.html
15 | ![CDATA[...]]
16 | Sun, 19 Nov 2017 10:12:02 +0100
17 |
18 | */
19 |
20 | $this->name = (string)$rss_item->title;
21 | $this->link = (string)$rss_item->link;
22 | $this->code = $this->parseRegionCodeFromLink();
23 |
24 | $description_html = (string)$rss_item->description;
25 |
26 | $this->parseWarningsFromDescription($description_html);
27 |
28 | $parsed_pub_date = date_create_from_format(self::DATE_TIME_FORMAT, (string)$rss_item->pubDate);
29 | date_timezone_set($parsed_pub_date, new DateTimeZone(Config::getTimeZone()));
30 | $this->published = $parsed_pub_date->format(Config::getDateTimeFormat());
31 | }
32 |
33 | private function parseRegionCodeFromLink() {
34 | // e.g. extract '004' from http://web.meteoalarm.eu/en_UK/0/0/UK004.html
35 | $start = strripos($this->link, Config::getCountry()) + strlen(Config::getCountry());
36 | $length = strripos($this->link, '.html') - $start;
37 |
38 | return substr($this->link, $start, $length);
39 | }
40 |
41 | private function parseWarningsFromDescription($description_html) {
42 | /* e.g.
43 |
44 | Today
45 |
46 |
47 | ...
48 |
49 |
50 |
51 | Tomorrow
52 |
53 |
54 | ...
55 |
56 |
57 |
58 | */
59 |
60 | $description_dom = DOMDocument::loadHTML($description_html);
61 |
62 | $rows = $description_dom->getElementsByTagName('tr');
63 | $tomorrows_row_index = $this->findTomorrowsRowIndex($rows);
64 |
65 | $this->todaysWarnings = $this->parseWarningsFromRows($rows, 1, $tomorrows_row_index);
66 | $this->tomorrowsWarnings = $this->parseWarningsFromRows($rows, $tomorrows_row_index+1, $rows->length);
67 | }
68 |
69 | private function findTomorrowsRowIndex($rows) {
70 | for ($i = 0; $i < $rows->length; $i++) { // Find Tomorrow
71 | $row = $rows->item($i);
72 |
73 | if ((sizeof($row->childNodes->length) === 1) && ($row->childNodes->item(0)->nodeName === 'th')) {
74 |
75 | if (trim($row->childNodes->item(0)->textContent) === 'Tomorrow') {
76 | $tomorrows_row_index = $i;
77 | break;
78 | }
79 | }
80 | }
81 |
82 | return $tomorrows_row_index;
83 | }
84 |
85 | private function parseWarningsFromRows($rows, $start_index, $end_index) {
86 | $warnings = array();
87 |
88 | for ($i = $start_index; ($i + 2) <= $end_index; $i+=2) {
89 | $awarness_period_row = $rows->item($i);
90 | $description_row = $rows->item($i+1);
91 | $warnings[] = new Warning($awarness_period_row, $description_row);
92 | }
93 |
94 | return $warnings;
95 | }
96 |
97 | public function serialize() {
98 | foreach ($this->todaysWarnings as $warning) {
99 | $todaysWarnings[] = $warning->serialize();
100 | }
101 |
102 | foreach ($this->tomorrowsWarnings as $warning) {
103 | $tomorrowsWarnings[] = $warning->serialize();
104 | }
105 |
106 | return [
107 | 'name' => $this->name,
108 | 'code' => $this->code,
109 | 'link' => $this->link,
110 | 'today' => $todaysWarnings,
111 | 'tomorrow' => $tomorrowsWarnings,
112 | 'published' => $this->published
113 | ];
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/test/AwarenessTypeTest.php:
--------------------------------------------------------------------------------
1 | serialize();
11 |
12 | $this->assertSame(0, $serialized['type']);
13 | $this->assertSame("No Warnings", $serialized['description']);
14 | }
15 |
16 | public function test_wind() {
17 | $testee = new AwarenessType(AwarenessType::WIND);
18 | $serialized = $testee->serialize();
19 |
20 | $this->assertSame(1, $serialized['type']);
21 | $this->assertSame("Wind", $serialized['description']);
22 | }
23 |
24 | public function test_snow_ice() {
25 | $testee = new AwarenessType(AwarenessType::SNOW_ICE);
26 | $serialized = $testee->serialize();
27 |
28 | $this->assertSame(2, $serialized['type']);
29 | $this->assertSame("Snow/Ice", $serialized['description']);
30 | }
31 |
32 | public function test_thunderstorms() {
33 | $testee = new AwarenessType(AwarenessType::THUNDERSTORMS);
34 | $serialized = $testee->serialize();
35 |
36 | $this->assertSame(3, $serialized['type']);
37 | $this->assertSame("Thunderstorms", $serialized['description']);
38 | }
39 |
40 | public function test_fog() {
41 | $testee = new AwarenessType(AwarenessType::FOG);
42 | $serialized = $testee->serialize();
43 |
44 | $this->assertSame(4, $serialized['type']);
45 | $this->assertSame("Fog", $serialized['description']);
46 | }
47 |
48 | public function test_extreme_high_temperatures() {
49 | $testee = new AwarenessType(AwarenessType::EXTREME_HIGH_TEMPERATURES);
50 | $serialized = $testee->serialize();
51 |
52 | $this->assertSame(5, $serialized['type']);
53 | $this->assertSame("Extreme High Temperatures", $serialized['description']);
54 | }
55 |
56 | public function test_extreme_low_temperatures() {
57 | $testee = new AwarenessType(AwarenessType::EXTREME_LOW_TEMPERATURES);
58 | $serialized = $testee->serialize();
59 |
60 | $this->assertSame(6, $serialized['type']);
61 | $this->assertSame("Extreme Low Temperatures", $serialized['description']);
62 | }
63 |
64 | public function test_coastal_event() {
65 | $testee = new AwarenessType(AwarenessType::COASTAL_EVENT);
66 | $serialized = $testee->serialize();
67 |
68 | $this->assertSame(7, $serialized['type']);
69 | $this->assertSame("Costal Event", $serialized['description']);
70 | }
71 |
72 | public function test_forest_fire() {
73 | $testee = new AwarenessType(AwarenessType::FOREST_FIRE);
74 | $serialized = $testee->serialize();
75 |
76 | $this->assertSame(8, $serialized['type']);
77 | $this->assertSame("Forest Fire", $serialized['description']);
78 | }
79 |
80 | public function test_avalanche() {
81 | $testee = new AwarenessType(AwarenessType::AVALANCHE);
82 | $serialized = $testee->serialize();
83 |
84 | $this->assertSame(9, $serialized['type']);
85 | $this->assertSame("Avalanche", $serialized['description']);
86 | }
87 |
88 | public function test_rain() {
89 | $testee = new AwarenessType(AwarenessType::RAIN);
90 | $serialized = $testee->serialize();
91 |
92 | $this->assertSame(10, $serialized['type']);
93 | $this->assertSame("Rain", $serialized['description']);
94 | }
95 |
96 | public function test_unavailable() {
97 | $testee = new AwarenessType(AwarenessType::UNAVAILABLE);
98 | $serialized = $testee->serialize();
99 |
100 | $this->assertSame(11, $serialized['type']);
101 | $this->assertSame("Unavailable", $serialized['description']);
102 | }
103 |
104 | public function test_flooding() {
105 | $testee = new AwarenessType(AwarenessType::FLOODING);
106 | $serialized = $testee->serialize();
107 |
108 | $this->assertSame(12, $serialized['type']);
109 | $this->assertSame("Flooding", $serialized['description']);
110 | }
111 |
112 | public function test_rain_flooding() {
113 | $testee = new AwarenessType(AwarenessType::RAIN_FLOODING);
114 | $serialized = $testee->serialize();
115 |
116 | $this->assertSame(13, $serialized['type']);
117 | $this->assertSame("Rain/Flooding", $serialized['description']);
118 | }
119 |
120 | public function test_invalid_low_type() {
121 | $this->expectException(InvalidArgumentException::class);
122 |
123 | $testee = new AwarenessType(-1);
124 | }
125 |
126 | public function test_invalid_high_type() {
127 | $this->expectException(InvalidArgumentException::class);
128 |
129 | $testee = new AwarenessType(14);
130 | }
131 | }
132 |
133 | ?>
134 |
--------------------------------------------------------------------------------
/test/RegionTest.php:
--------------------------------------------------------------------------------
1 | ' .
10 | '' .
11 | '' .
12 | 'Today' .
13 | ' ' .
14 | ' ' .
15 | '' .
16 | '' .
17 | ' ' .
18 | ' ' .
19 | '' .
20 | 'From: 10.12.2017 05:00 CET Until: 11.12.2017 00:55 CET ' .
21 | ' ' .
22 | ' ' .
23 | '' .
24 | '' .
25 | ' ' .
26 | '' .
27 | 'A spell of heavy snow is possible over parts of Scotland during Sunday.' .
28 | ' ' .
29 | ' ' .
30 | '' .
31 | '' .
32 | 'Tomorrow' .
33 | ' ' .
34 | ' ' .
35 | '' .
36 | '' .
37 | ' ' .
38 | ' ' .
39 | '' .
40 | 'From: 11.12.2017 05:00 CET Until: 11.12.2017 00:55 CET ' .
41 | ' ' .
42 | ' ' .
43 | '' .
44 | '' .
45 | ' ' .
46 | '' .
47 | 'A spell of heavy snow is possible over parts of Scotland during Monday.' .
48 | ' ' .
49 | ' ' .
50 | '' .
51 | '' .
52 | ' ' .
53 | ' ' .
54 | '' .
55 | 'From: 11.12.2017 05:00 CET Until: 12.12.2017 12:00 CET ' .
56 | ' ' .
57 | ' ' .
58 | '' .
59 | '' .
60 | ' ' .
61 | '' .
62 | 'Ice is expected to form across many places overnight into Tuesday morning.' .
63 | ' ' .
64 | ' ' .
65 | '';
66 |
67 | protected function setUp() {
68 | Config::setCountry('UK');
69 | Config::setDateTimeFormat('l H:i T');
70 | Config::setTimezone('Europe/London');
71 | }
72 |
73 | public function test_region() {
74 | $item_xml =
75 | '- ' .
76 | '
Lothian & Borders ' .
77 | ' http://web.meteoalarm.eu/en_UK/0/0/UK004.html' .
78 | 'description_html . ']]> ' .
79 | 'Fri, 22 Dec 2017 01:00:00 +0100 ' .
80 | '418f52d19af464bc09619e79a6161d6a ' .
81 | ' ';
82 |
83 | $rss_item = simplexml_load_string($item_xml);
84 |
85 | $testee = new Region($rss_item);
86 | $serialized = $testee->serialize();
87 |
88 | $this->assertSame('004', $serialized['code']);
89 | $this->assertSame('Lothian & Borders', $serialized['name']);
90 | $this->assertSame('http://web.meteoalarm.eu/en_UK/0/0/UK004.html', $serialized['link']);
91 |
92 | $this->assertSame(1, sizeof($serialized['today']));
93 | $this->assertSame('A spell of heavy snow is possible over parts of Scotland during Sunday.', $serialized['today'][0]['description']);
94 |
95 | $this->assertSame(2, sizeof($serialized['tomorrow']));
96 | $this->assertSame('A spell of heavy snow is possible over parts of Scotland during Monday.', $serialized['tomorrow'][0]['description']);
97 | $this->assertSame('Ice is expected to form across many places overnight into Tuesday morning.', $serialized['tomorrow'][1]['description']);
98 |
99 | $this->assertSame('Friday 00:00 GMT', $serialized['published']);
100 | }
101 |
102 | public function test_region_with_alternative_date_time_format_and_timezone() {
103 | Config::setDateTimeFormat('Y-m-d H:i T');
104 | Config::setTimeZone('America/New_York');
105 |
106 | $item_xml =
107 | '- ' .
108 | '
Lothian & Borders ' .
109 | ' http://web.meteoalarm.eu/en_UK/0/0/UK004.html' .
110 | 'description_html . ']]> ' .
111 | 'Fri, 22 Dec 2017 01:00:00 +0100 ' .
112 | '418f52d19af464bc09619e79a6161d6a ' .
113 | ' ';
114 |
115 | $rss_item = simplexml_load_string($item_xml);
116 |
117 | $testee = new Region($rss_item);
118 | $serialized = $testee->serialize();
119 |
120 | $this->assertSame('2017-12-21 19:00 EST', $serialized['published']);
121 | }
122 | }
123 |
124 | ?>
125 |
--------------------------------------------------------------------------------
/test/WarningsTest.php:
--------------------------------------------------------------------------------
1 | ' .
15 | '' .
16 | '' .
17 | ' ' .
18 | 'Meteoalarm United Kingdom ' .
19 | ' http://web.meteoalarm.eu/en_UK/0/0/UK.html' .
20 | 'Metoalarm actual warnings from United Kingdom ' .
21 | '10 ' .
22 | 'eng ' .
23 | '- ' .
24 | '
United Kingdom ' .
25 | ' http://web.meteoalarm.eu/en_UK/0/0/UK.html' .
26 | 'Today Tomorrow ]]> ' .
27 | 'Fri, 22 Dec 2017 01:00:00 +0100 ' .
28 | '1fe7d2aa271d77e37ba7f402e32ed7a2 ' .
29 | ' ' .
30 | '- ' .
31 | '
Dumfries and Galloway ' .
32 | ' http://web.meteoalarm.eu/en_UK/0/0/UK006.html' .
33 | 'Today No special awareness required TomorrowNo special awareness required ]]> ' .
34 | 'Fri, 22 Dec 2017 01:00:00 +0100 ' .
35 | '1bda31bcc0ead6ce748cc1e39fa249a1 ' .
36 | ' ' .
37 | '- ' .
38 | '
Lothian & Borders ' .
39 | ' http://web.meteoalarm.eu/en_UK/0/0/UK004.html' .
40 | 'Today No special awareness required TomorrowNo special awareness required ]]> ' .
41 | 'Fri, 22 Dec 2017 01:00:00 +0100 ' .
42 | '418f52d19af464bc09619e79a6161d6a ' .
43 | ' ' .
44 | ' ' .
45 | ' ';
46 |
47 | $rss = simplexml_load_string($rss_xml);
48 |
49 | $testee = new Warnings($rss);
50 | $serialized = $testee->serialize();
51 |
52 | $this->assertSame('UK', $serialized['country']);
53 | $this->assertSame(2, sizeof($serialized['regions']));
54 | $this->assertSame('Dumfries and Galloway', $serialized['regions'][0]['name']);
55 | $this->assertSame('Lothian & Borders', $serialized['regions'][1]['name']);
56 | }
57 |
58 | public function test_warnings_for_single_region() {
59 | $rss_xml =
60 | '' .
61 | '' .
62 | '' .
63 | ' ' .
64 | 'Meteoalarm United Kingdom, Highland & Islands ' .
65 | ' http://web.meteoalarm.eu/en_UK/0/0/UK002.html' .
66 | 'Metoalarm actual warnings from United Kingdom, Highland & Islands ' .
67 | '10 ' .
68 | 'eng ' .
69 | '- ' .
70 | '
Highlands & Islands ' .
71 | ' http://web.meteoalarm.eu/en_UK/0/0/UK002.html' .
72 | 'Today No special awareness required TomorrowFrom: 24.12.2017 01:05 CET Until: 25.12.2017 00:55 CET Heavy rain will affect western parts of Scotland at times during Saturday night and Sunday. Spray and flooding on roads will probably make journey times longer. This will probably also cause delays to bus and train services. There is a chance that a few homes and businesses could be flooded. This warning has been updated to extend it further east. ]]> ' .
73 | 'Sat, 23 Dec 2017 22:00:02 +0100 ' .
74 | '0aefbef317686efce5152f0daeea5c1e ' .
75 | ' ' .
76 | ' ' .
77 | ' ';
78 |
79 | $rss = simplexml_load_string($rss_xml);
80 |
81 | $testee = new Warnings($rss);
82 | $serialized = $testee->serialize();
83 |
84 | $this->assertSame('UK', $serialized['country']);
85 | $this->assertSame(1, sizeof($serialized['regions']));
86 | $this->assertSame('Highlands & Islands', $serialized['regions'][0]['name']);
87 | }
88 | }
89 |
90 | ?>
91 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # meteo-alarm-weather-warnings
2 |
3 | Copyright © 2018 Wayne D Grant
4 |
5 | Licensed under the MIT License
6 |
7 | JSON formatted Web Service API to fetch weather warnings from [Metoalarm](http://www.meteoalarm.eu/). Written in PHP.
8 |
9 | ## Overview
10 |
11 | [Metoalarm](http://www.meteoalarm.eu/) is a web site that provides up-to-date weather warnings for most of Europe. While it provides a useful website and RSS feeds it does not, at the time of writing, provide a web service interface.
12 |
13 | **meteo-alarm-weather-warnings** remedies this by providing a simple JSON formatted Web Service API that proxies through to Meteoalarm's RSS feeds.
14 |
15 | 
16 |
17 | **meteo-alarm-weather-warnings** is especially useful for integrating weather warnings into a 3rd-party website (as has been done here for several UK regions: [https://waynedgrant.com/weather/warn.html](https://waynedgrant.com/weather/warn.html)).
18 |
19 | ## Requirements
20 |
21 | 1. PHP version 5 or above installed on the web server
22 |
23 | ## Installation
24 |
25 | * Download the source code for the [latest release](https://github.com/waynedgrant/meteo-alarm-weather-warnings/releases) and unzip it
26 |
27 | * Upload all **.php** and **.json** files in **meteo-alarm-weather-warnings/src** to a location on your web server
28 |
29 | * Modify your web server to process **.json** files using PHP. For example, for Apache add the following to your **.htaccess** file:
30 |
31 | ```
32 | AddHandler application/x-httpd-php5 .json
33 | ```
34 |
35 | * Alternatively rename the **warnings.json** file to be called **warnings.php**
36 |
37 | ## Execution
38 |
39 | Hit the URL of your deployed **warnings.json** file using a web browser or other REST client. Both JSON and JSONP are supported.
40 |
41 | The following GET parameters are supported:
42 |
43 | | GET Parameter | Description | Required? | Default Value |
44 | |------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|------------------------|
45 | | country | Country to fetch weather warnings for. e.g. UK, DE, FR, etc. See [Metoalarm](http://www.meteoalarm.eu/) for a full list of countries. | Yes | N/A |
46 | | region | Region within country to fetch weather warnings for, e.g. 004 is Lothian & Borders in the UK. See [Metoalarm](http://www.meteoalarm.eu/) for a full list of regions. | No | All regions in country |
47 | | date_time_format | Format string to use for fetched date/time values. See [datetime.createfromformat.php](http://php.net/manual/en/datetime.createfromformat.php) for details. | No | l H:i T |
48 | | time_zone | Timezone used to express fetched date/time values. See [timezones.php](http://php.net/manual/en/timezones.php) for a full list of supported values. | No | Europe/London |
49 |
50 | For example:
51 |
52 | All weather warnings for the UK:
53 |
54 | ```
55 | /warnings.json?country=UK
56 | ```
57 |
58 | Weather warnings for the Gelderland region of the Netherlands:
59 |
60 | ```
61 | /warnings.json?country=NL®ion=014
62 | ```
63 |
64 | Weather warnings for Germany with a specialized date/time format expressed in the local timezone:
65 |
66 | ```
67 | /warnings.json?country=DE&date_time_format=Y/M/d%20H:i%20T&time_zone=Europe/Berlin
68 | ```
69 |
70 | ## Response Fields
71 |
72 | ### Success Response
73 |
74 | An example of a successful reply:
75 |
76 | ```
77 | {
78 | "endpoint": {
79 | "url": "...",
80 | "version": "1.1",
81 | "github_project": "https://github.com/waynedgrant/meteo-alarm-weather-warnings",
82 | "copyright": "Copyright © 2018 Wayne D Grant (www.waynedgrant.com)"
83 | },
84 | "warnings": {
85 | "country": "UK",
86 | "regions": [
87 | {
88 | "name": "Dumfries and Galloway",
89 | "code": "006",
90 | "link": "http://web.meteoalarm.eu/en_UK/0/0/UK006.html",
91 | "today": [
92 | {
93 | "awareness": {
94 | "icon": "http://web.meteoalarm.eu/documents/rss/wflag-l3-t1.jpg",
95 | "awareness_type": {
96 | "type": 1,
97 | "description": "Wind"
98 | },
99 | "awareness_level": {
100 | "level": 3,
101 | "colour": "Amber",
102 | "description": "The weather is dangerous. Unusual meteorological phenomena have been forecast. Damage and casualties are likely to happen. Be very vigilant and keep regularly informed about the detailed expected meteorological conditions. Be aware of the risks that might be unavoidable. Follow any advice given by your authorities."
103 | }
104 | },
105 | "period": {
106 | "from": "Sunday 03:00 GMT",
107 | "until": "Sunday 12:00 GMT"
108 | },
109 | "description": "Some very strong winds are expected over parts of Northern Ireland and southwest Scotland on New Years Eve. There will probably be some damage to buildings such as tiles blown from roofs, with flying debris likely with the possibility of injuries or danger to life. In coastal areas large waves are likely as well as beach material being thrown onto coastal roads, sea fronts and perhaps some properties. Longer journey times and cancellations are likely as road, rail, air and ferry services may be affected. Some roads and bridges are likely to close. There is also a good chance of power cuts and the potential to affect other services such as mobile phone coverage."
110 | },
111 |
112 | ... any other warnings for today ...
113 | ],
114 | "tomorrow": [
115 | {
116 | ... warnings for tomorrow ...
117 | }
118 | ],
119 | "published": "Sunday 00:00 GMT"
120 | },
121 | {
122 | ... other regions ...
123 | }
124 | ]
125 | }
126 | }
127 | ```
128 |
129 | #### Awareness Types
130 |
131 | | Code | Type |
132 | |------|---------------------------|
133 | | 0 | No Warnings |
134 | | 1 | Wind |
135 | | 2 | Snow/Ice |
136 | | 3 | Thunderstorms |
137 | | 4 | Fog |
138 | | 5 | Extreme High Temperatures |
139 | | 6 | Extreme Low Temperatures |
140 | | 7 | Costal Event |
141 | | 8 | Forest Fire |
142 | | 9 | Avalanche |
143 | | 10 | Rain |
144 | | 11 | Unavailable |
145 | | 12 | Flooding |
146 | | 13 | Rain/Flooding |
147 |
148 | #### Awareness Levels
149 |
150 | | Code | Level |
151 | |------|-------------------|
152 | | 0 | White/Unknown |
153 | | 1 | Green/No Warnings |
154 | | 2 | Yellow |
155 | | 3 | Amber |
156 | | 4 | Red |
157 |
158 | ### Failure Response
159 |
160 | Examples of failure replies:
161 |
162 | ```
163 | {
164 | "error": "GET parameter country is required"
165 | }
166 | ```
167 |
168 | ```
169 | {
170 | "error": "Response code 404 returned by http://www.meteoalarm.eu/documents/rss/NARNIA.rss"
171 | }
172 | ```
173 |
174 | ## Unit Testing
175 |
176 | * Install [PHPUnit](https://phpunit.de/)
177 | * cd meteo-alarm-weather-warnings
178 | * phpunit --bootstrap bootstrap.php test/
179 |
--------------------------------------------------------------------------------