Plugin Directory

Changeset 3384734


Ignore:
Timestamp:
10/26/2025 01:20:46 PM (4 months ago)
Author:
spelhubben
Message:

Release 1.7.5: FMI (Finland, Open Data) as an additional free provider

Location:
spelhubben-weather/trunk
Files:
8 edited

Legend:

Unmodified
Added
Removed
  • spelhubben-weather/trunk/admin/admin.php

    r3372751 r3384734  
    236236    );
    237237    printf(
    238         '<label><input type="checkbox" name="sv_vader_options[prov_metno_nowcast]" value="1" %s/> %s</label>',
     238        '<label><input type="checkbox" name="sv_vader_options[prov_metno_nowcast]" value="1" %s/> %s</label><br>',
    239239        checked( 1, ! empty( $o['prov_metno_nowcast'] ), false ),
    240240        esc_html__( 'MET Norway Nowcast', 'spelhubben-weather' )
     241    );
     242    // NEW: FMI
     243    printf(
     244        '<label><input type="checkbox" name="sv_vader_options[prov_fmi]" value="1" %s/> %s</label>',
     245        checked( 1, ! empty( $o['prov_fmi'] ), false ),
     246        esc_html__( 'FMI (Finland, Open Data)', 'spelhubben-weather' )
    241247    );
    242248}
  • spelhubben-weather/trunk/admin/page-shortcodes.php

    r3372751 r3384734  
    1414        $nx2 = '[spelhubben_weather place="Gothenburg" layout="compact" map="1" animate="1"]';
    1515        $nx3 = '[spelhubben_weather lat="57.7089" lon="11.9746" place="Gothenburg" layout="inline" map="0" show="temp,icon"]';
    16         $nx4 = '[spelhubben_weather place="Umeå" layout="detailed" forecast="daily" days="5" providers="smhi,yr,openmeteo" units="metric_kmh"]';
     16        $nx4 = '[spelhubben_weather place="Umeå" layout="detailed" forecast="daily" days="5" providers="smhi,yr,openmeteo,fmi" units="metric_kmh"]';
    1717        $nx5 = '[spelhubben_weather place="Malmö" show="temp,wind" map="0" units="imperial"]';
    1818
  • spelhubben-weather/trunk/includes/class-renderer.php

    r3372751 r3384734  
    2424                'yr'             => $opts['prov_yr'],
    2525                'metno_nowcast'  => $opts['prov_metno_nowcast'] ?? 0,
     26                'fmi'            => $opts['prov_fmi'] ?? 0, // NEW
    2627            ]))),
    2728            'animate'    => '1',
     
    4243
    4344        $provider_list = array_filter(array_map('trim', explode(',', strtolower($a['providers']))));
    44         $allowed = ['openmeteo','smhi','yr','metno_nowcast'];
     45        $allowed = ['openmeteo','smhi','yr','metno_nowcast','fmi']; // NEW
    4546        $provider_list = array_values(array_intersect($provider_list, $allowed));
    4647        if (empty($provider_list)) $provider_list = ['openmeteo'];
     
    6162        if (is_wp_error($res)) return '<em>' . esc_html($res->get_error_message()) . '</em>';
    6263
    63         // Convert values according to selected units
     64        // Convert values according to selected units
    6465        list($t_val, $t_sym) = svv_temp($res['temp'] ?? null, $units['temp'], 0);
    6566        list($w_val, $w_u)   = svv_wind($res['wind'] ?? null, $units['wind'], 0);
  • spelhubben-weather/trunk/includes/class-sv-vader.php

    r3372751 r3384734  
    4949            if ($yr) $samples[] = $yr;
    5050        }
     51        // NEW: FMI
     52        if (in_array('fmi', $providers, true)) {
     53            $fmi = svp_fmi_current($lat, $lon);
     54            if ($fmi) $samples[] = $fmi;
     55        }
    5156
    5257        if (empty($samples)) {
     
    135140        // (unchanged inline SVGs)
    136141        $svg = '';
    137         if ($type === 'sun') {
    138             $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><g fill="#111"><circle cx="32" cy="32" r="12"/><g opacity=".9"><rect x="31" y="2" width="2" height="10"/><rect x="31" y="52" width="2" height="10"/><rect x="2" y="31" width="10" height="2"/><rect x="52" y="31" width="10" height="2"/><rect x="10.3" y="10.3" width="2" height="10" transform="rotate(-45 11.3 15.3)"/><rect x="51.7" y="43.7" width="2" height="10" transform="rotate(-45 52.7 48.7)"/><rect x="43.7" y="10.3" width="10" height="2" transform="rotate(45 48.7 11.3)"/><rect x="10.3" y="51.7" width="10" height="2" transform="rotate(45 15.3 52.7)"/></g></g></svg>';
    139         } elseif ($type === 'cloud') {
    140             $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 48h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 48z"/></svg>';
    141         } elseif ($type === 'rain') {
    142             $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 40h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 40z"/><g fill="#111" opacity=".9"><path d="M22 46l-2 6"/><path d="M30 46l-2 6"/><path d="M38 46l-2 6"/><path d="M46 46l-2 6"/></g></svg>';
    143         } elseif ($type === 'snow') {
    144             $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 40h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 40z"/><g fill="#111" opacity=".9"><circle cx="24" cy="48" r="2"/><circle cx="32" cy="48" r="2"/><circle cx="40" cy="48" r="2"/></g></svg>';
    145         } else {
    146             $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 40h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 40z"/><path fill="#111" d="M32 42l-6 12h6l-2 8 8-14h-6l2-6z"/></svg>';
    147         }
     142        if ($type === 'sun') {
     143            $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><g fill="#111"><circle cx="32" cy="32" r="12"/><g opacity=".9"><rect x="31" y="2" width="2" height="10"/><rect x="31" y="52" width="2" height="10"/><rect x="2" y="31" width="10" height="2"/><rect x="52" y="31" width="10" height="2"/><rect x="10.3" y="10.3" width="2" height="10" transform="rotate(-45 11.3 15.3)"/><rect x="51.7" y="43.7" width="2" height="10" transform="rotate(-45 52.7 48.7)"/><rect x="43.7" y="10.3" width="10" height="2" transform="rotate(45 48.7 11.3)"/><rect x="10.3" y="51.7" width="10" height="2" transform="rotate(45 15.3 52.7)"/></g></g></svg>';
     144        } elseif ($type === 'cloud') {
     145            $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 48h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 48z"/></svg>';
     146        } elseif ($type === 'rain') {
     147            $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 40h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 40z"/><g fill="#111" opacity=".9"><path d="M22 46l-2 6"/><path d="M30 46l-2 6"/><path d="M38 46l-2 6"/><path d="M46 46l-2 6"/></g></svg>';
     148        } elseif ($type === 'snow') {
     149            $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 40h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 40z"/><g fill="#111" opacity=".9"><circle cx="24" cy="48" r="2"/><circle cx="32" cy="48" r="2"/><circle cx="40" cy="48" r="2"/></g></svg>';
     150        } else {
     151            $svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><path fill="#111" d="M22 40h24a10 10 0 0 0 0-20 14 14 0 0 0-27.3-3.8A12 12 0 0 0 22 40z"/><path fill="#111" d="M32 42l-6 12h6l-2 8 8-14h-6l2-6z"/></svg>';
     152        }
    148153        return 'data:image/svg+xml;utf8,' . rawurlencode($svg);
    149154    }
  • spelhubben-weather/trunk/includes/options.php

    r3372751 r3384734  
    2121            'prov_yr'            => 1,
    2222            'prov_metno_nowcast' => 1,
     23            'prov_fmi'           => 1, // NEW
     24
    2325            'yr_contact'         => '[email protected]',
    2426
     
    7072        $out['prov_yr']            = !empty($in['prov_yr']) ? 1 : 0;
    7173        $out['prov_metno_nowcast'] = !empty($in['prov_metno_nowcast']) ? 1 : 0;
     74        $out['prov_fmi']           = !empty($in['prov_fmi']) ? 1 : 0;
    7275
    7376        $out['yr_contact'] = sanitize_text_field($in['yr_contact'] ?? $def['yr_contact']);
  • spelhubben-weather/trunk/includes/providers.php

    r3372751 r3384734  
    22// includes/providers.php - Weather data providers and normalization functions
    33if (!defined('ABSPATH')) exit;
    4 
    5 /**
    6  * Normalized result (SI):
    7  * [ 'temp' => float|null, 'wind' => float|null, 'precip' => float|null, 'cloud' => int|null (0-100), 'code' => int|null, 'desc' => string ]
    8  */
    94
    105if (!function_exists('svp_openmeteo_current')) {
     
    112107            'desc'   => null,
    113108        ];
     109    }
     110}
     111
     112/**
     113 * NEW: FMI (Finnish Meteorological Institute) via WFS timevaluepair
     114 * Uses bbox around point to pick nearest station.
     115 * Parameters:
     116 *  - t2m (°C), ws_10min (m/s), r_1h (mm), n_man (cloud oktas 0..8)
     117 */
     118if (!function_exists('svp_fmi_current')) {
     119    function svp_fmi_current($lat, $lon) {
     120        $lat = floatval($lat); $lon = floatval($lon);
     121        if (!$lat && !$lon) return null;
     122
     123        $d = 0.06; // ~ ca 6–7 km
     124        $bbox = ($lon - $d) . ',' . ($lat - $d) . ',' . ($lon + $d) . ',' . ($lat + $d) . ',epsg:4326';
     125
     126        $url = add_query_arg([
     127            'service'        => 'WFS',
     128            'version'        => '2.0.0',
     129            'request'        => 'getFeature',
     130            'storedquery_id' => 'fmi::observations::weather::timevaluepair',
     131            'parameters'     => 't2m,ws_10min,r_1h,n_man',
     132            'bbox'           => $bbox,
     133        ], 'https://opendata.fmi.fi/wfs');
     134
     135        $res = wp_remote_get($url, ['timeout'=>14,'user-agent'=>'Spelhubben-Weather/1.0 (FMI WFS)']);
     136        if (is_wp_error($res) || wp_remote_retrieve_response_code($res) !== 200) return null;
     137
     138        $xml = wp_remote_retrieve_body($res);
     139        if (!is_string($xml) || $xml==='') return null;
     140
     141        $sx = @simplexml_load_string($xml);
     142        if (!$sx) return null;
     143        $sx->registerXPathNamespace('wml2','http://www.opengis.net/waterml/2.0');
     144        $sx->registerXPathNamespace('gml', 'http://www.opengis.net/gml/3.2');
     145
     146        $out = ['temp'=>null,'wind'=>null,'precip'=>null,'cloud'=>null,'code'=>null,'desc'=>null];
     147        $series = $sx->xpath('//wml2:MeasurementTimeseries');
     148        if (is_array($series)) {
     149            foreach ($series as $ts) {
     150                $attrs = $ts->attributes('gml', true);
     151                $gid   = isset($attrs['id']) ? strtolower((string)$attrs['id']) : '';
     152                $vals  = $ts->xpath('.//wml2:point/wml2:MeasurementTVP/wml2:value');
     153                if (!$vals || !count($vals)) continue;
     154                $val = (string)$vals[count($vals)-1];
     155
     156                if (strpos($gid,'t2m')!==false)          $out['temp']   = is_numeric($val)?(float)$val:null;
     157                elseif (strpos($gid,'ws_10min')!==false) $out['wind']   = is_numeric($val)?(float)$val:null;
     158                elseif (strpos($gid,'r_1h')!==false)     $out['precip'] = is_numeric($val)?(float)$val:null;
     159                elseif (strpos($gid,'n_man')!==false) {
     160                    $oktas = is_numeric($val)?(float)$val:null;
     161                    $out['cloud'] = ($oktas!==null) ? (int)round(($oktas/8)*100) : null;
     162                }
     163            }
     164        }
     165        return ($out['temp']===null && $out['wind']===null && $out['precip']===null && $out['cloud']===null) ? null : $out;
    114166    }
    115167}
  • spelhubben-weather/trunk/readme.txt

    r3372751 r3384734  
    55Tested up to: 6.8
    66Requires PHP: 7.4
    7 Stable tag: 1.7.0
     7Stable tag: 1.7.5
    88Donate link: https://www.paypal.com/donate/?hosted_button_id=CV74CEXY5XEAU
    99License: GPLv2 or later
    1010License URI: https://www.gnu.org/licenses/gpl-2.0.html
    1111
    12 Weather widget & block with optional map and daily forecast. Can combine Open-Meteo, SMHI and Yr/MET data.
     12Weather widget & block with optional map and daily forecast. Can combine Open-Meteo, SMHI, Yr/MET and FMI data.
    1313
    1414== Description ==
    15 This plugin displays current weather and an optional forecast. It can aggregate data from northern-Europe friendly providers (Open-Meteo, SMHI, and Yr/MET Norway) and compute a simple consensus.
     15This plugin displays current weather and an optional forecast. It can aggregate data from northern-Europe friendly providers (Open-Meteo, SMHI, Yr/MET Norway, and **FMI** / Finnish Meteorological Institute) and compute a simple consensus.
    1616
    1717**Features**
     
    2323- **Included translations:** **Swedish (sv_SE), Norwegian (nb_NO), English (en_US)**
    2424- **New (1.7.0):** modern admin UI with a dedicated **Shortcodes** page (search, copy, “copy all”), and **live preview** inside WP-admin
     25- **New (1.7.5):** **FMI (Finland, Open Data)** as an additional free provider (temperature, wind, hourly precip, cloud cover via WFS)
    2526
    26 *Not affiliated with Open-Meteo, SMHI, Yr/MET Norway, Leaflet, or OpenStreetMap. Names are used for descriptive purposes only. Map data © OpenStreetMap contributors (ODbL).*
     27*Not affiliated with Open-Meteo, SMHI, Yr/MET Norway, FMI, Leaflet, or OpenStreetMap. Names are used for descriptive purposes only. Map data © OpenStreetMap contributors (ODbL).*
    2728
    2829== Installation ==
     
    4142  - Compact with map & animation: `[spelhubben_weather place="Gothenburg" layout="compact" map="1" animate="1"]`
    4243  - Inline no map: `[spelhubben_weather lat="57.7089" lon="11.9746" layout="inline" map="0" show="temp,icon"]`
    43   - Detailed + daily forecast (5 days) + provider mix: `[spelhubben_weather place="Umeå" layout="detailed" forecast="daily" days="5" providers="smhi,yr,openmeteo"]`
     44  - Detailed + daily forecast (5 days) + provider mix: `[spelhubben_weather place="Umeå" layout="detailed" forecast="daily" days="5" providers="smhi,yr,openmeteo,fmi"]`
    4445
    4546= Classic Widget =
     
    5051
    5152= Where does the data come from? =
    52 From public APIs such as Open-Meteo, SMHI and Yr/MET Norway. You choose providers under **Settings → Spelhubben Weather** or per block/shortcode/widget via the `providers` attribute.
     53From public APIs such as Open-Meteo, SMHI, Yr/MET Norway, and **FMI** (Finnish Meteorological Institute). You choose providers under **Settings → Spelhubben Weather** or per block/shortcode/widget via the `providers` attribute.
    5354
    5455= Do I need an API key? =
    55 No. For Yr/MET Norway it’s recommended to include contact info (email/URL) in **Settings → Spelhubben Weather → Yr contact/UA** so your User-Agent is compliant.
     56No. Open-Meteo, SMHI, and FMI do not require keys. For Yr/MET Norway it’s recommended to include contact info (email/URL) in **Settings → Spelhubben Weather → Yr contact/UA** so your User-Agent is compliant.
    5657
    5758= Block, shortcode or widget — what’s the difference? =
     
    8081
    8182= Can I mix providers and get a consensus? =
    82 Yes. Set `providers="smhi,yr,openmeteo"` (order doesn’t matter). The plugin calculates a simple consensus across available providers for the displayed fields.
     83Yes. Set `providers="smhi,yr,openmeteo,fmi"` (order doesn’t matter). The plugin calculates a simple consensus across available providers for the displayed fields.
    8384
    8485= Units & format? =
     
    112113
    113114== Changelog ==
     115= 1.7.5 =
     116- New: **FMI (Finnish Meteorological Institute)** as a free, optional provider (t2m, ws_10min, r_1h, n_man via WFS). Toggle in **Settings → Providers** and via `providers="…"` in block/shortcode/widget.
     117- Shortcodes/Blocks: `providers` now accepts `fmi`.
     118- Docs: Updated examples and FAQ to include FMI.
     119
    114120= 1.7.0 =
    115121- New: **Shortcodes** admin page with searchable examples, one-click copy & **copy all**.
     
    130136
    131137== Upgrade Notice ==
     138= 1.7.5 =
     139Adds **FMI** as an optional free provider. Enable it under **Settings → Spelhubben Weather → Providers**, or pass `providers="smhi,yr,openmeteo,fmi"` in blocks/shortcodes/widgets.
     140
    132141= 1.7.0 =
    133 Admin UX overhaul: new Shortcodes page with live preview, units/format settings, and cache clear. Legacy [sv_vader] is deprecated—please migrate to [spelhubben_weather]. Translations: Swedish, Norwegian, English.
     142Admin UX overhaul: new Shortcodes page with live preview, units/format settings, and cache clear. Legacy [sv_vader] is deprecated—please migrate to [spelhubben_weather].
    134143
    135144Donate link: https://paypalme/spelhubben
  • spelhubben-weather/trunk/spelhubben-weather.php

    r3372751 r3384734  
    33 * Plugin Name: Spelhubben Weather
    44 * Description: Displays current weather and an optional forecast with a simple consensus across providers (Open-Meteo, SMHI, Yr/MET Norway). Supports shortcode + Gutenberg block + classic widget. Optional Leaflet map, subtle animations, daily forecast, and multiple layouts.
    5  * Version: 1.7.0
     5 * Version: 1.7.5
    66 * Author: Spelhubben
    77 * Text Domain: spelhubben-weather
     
    1919// ── Constants (kept for backward compatibility).
    2020if ( ! defined( 'SV_VADER_VER' ) ) {
    21     define( 'SV_VADER_VER', '1.7.0' );
     21    define( 'SV_VADER_VER', '1.7.5' );
    2222}
    2323if ( ! defined( 'SV_VADER_DIR' ) ) {
Note: See TracChangeset for help on using the changeset viewer.