Changeset 3384734
- Timestamp:
- 10/26/2025 01:20:46 PM (4 months ago)
- Location:
- spelhubben-weather/trunk
- Files:
-
- 8 edited
-
admin/admin.php (modified) (1 diff)
-
admin/page-shortcodes.php (modified) (1 diff)
-
includes/class-renderer.php (modified) (3 diffs)
-
includes/class-sv-vader.php (modified) (2 diffs)
-
includes/options.php (modified) (2 diffs)
-
includes/providers.php (modified) (2 diffs)
-
readme.txt (modified) (7 diffs)
-
spelhubben-weather.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
spelhubben-weather/trunk/admin/admin.php
r3372751 r3384734 236 236 ); 237 237 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>', 239 239 checked( 1, ! empty( $o['prov_metno_nowcast'] ), false ), 240 240 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' ) 241 247 ); 242 248 } -
spelhubben-weather/trunk/admin/page-shortcodes.php
r3372751 r3384734 14 14 $nx2 = '[spelhubben_weather place="Gothenburg" layout="compact" map="1" animate="1"]'; 15 15 $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"]'; 17 17 $nx5 = '[spelhubben_weather place="Malmö" show="temp,wind" map="0" units="imperial"]'; 18 18 -
spelhubben-weather/trunk/includes/class-renderer.php
r3372751 r3384734 24 24 'yr' => $opts['prov_yr'], 25 25 'metno_nowcast' => $opts['prov_metno_nowcast'] ?? 0, 26 'fmi' => $opts['prov_fmi'] ?? 0, // NEW 26 27 ]))), 27 28 'animate' => '1', … … 42 43 43 44 $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 45 46 $provider_list = array_values(array_intersect($provider_list, $allowed)); 46 47 if (empty($provider_list)) $provider_list = ['openmeteo']; … … 61 62 if (is_wp_error($res)) return '<em>' . esc_html($res->get_error_message()) . '</em>'; 62 63 63 // Convert values according to selected units64 // Convert values according to selected units 64 65 list($t_val, $t_sym) = svv_temp($res['temp'] ?? null, $units['temp'], 0); 65 66 list($w_val, $w_u) = svv_wind($res['wind'] ?? null, $units['wind'], 0); -
spelhubben-weather/trunk/includes/class-sv-vader.php
r3372751 r3384734 49 49 if ($yr) $samples[] = $yr; 50 50 } 51 // NEW: FMI 52 if (in_array('fmi', $providers, true)) { 53 $fmi = svp_fmi_current($lat, $lon); 54 if ($fmi) $samples[] = $fmi; 55 } 51 56 52 57 if (empty($samples)) { … … 135 140 // (unchanged inline SVGs) 136 141 $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 } 148 153 return 'data:image/svg+xml;utf8,' . rawurlencode($svg); 149 154 } -
spelhubben-weather/trunk/includes/options.php
r3372751 r3384734 21 21 'prov_yr' => 1, 22 22 'prov_metno_nowcast' => 1, 23 'prov_fmi' => 1, // NEW 24 23 25 'yr_contact' => '[email protected]', 24 26 … … 70 72 $out['prov_yr'] = !empty($in['prov_yr']) ? 1 : 0; 71 73 $out['prov_metno_nowcast'] = !empty($in['prov_metno_nowcast']) ? 1 : 0; 74 $out['prov_fmi'] = !empty($in['prov_fmi']) ? 1 : 0; 72 75 73 76 $out['yr_contact'] = sanitize_text_field($in['yr_contact'] ?? $def['yr_contact']); -
spelhubben-weather/trunk/includes/providers.php
r3372751 r3384734 2 2 // includes/providers.php - Weather data providers and normalization functions 3 3 if (!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 */9 4 10 5 if (!function_exists('svp_openmeteo_current')) { … … 112 107 'desc' => null, 113 108 ]; 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 */ 118 if (!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; 114 166 } 115 167 } -
spelhubben-weather/trunk/readme.txt
r3372751 r3384734 5 5 Tested up to: 6.8 6 6 Requires PHP: 7.4 7 Stable tag: 1.7. 07 Stable tag: 1.7.5 8 8 Donate link: https://www.paypal.com/donate/?hosted_button_id=CV74CEXY5XEAU 9 9 License: GPLv2 or later 10 10 License URI: https://www.gnu.org/licenses/gpl-2.0.html 11 11 12 Weather widget & block with optional map and daily forecast. Can combine Open-Meteo, SMHI and Yr/METdata.12 Weather widget & block with optional map and daily forecast. Can combine Open-Meteo, SMHI, Yr/MET and FMI data. 13 13 14 14 == 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.15 This 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. 16 16 17 17 **Features** … … 23 23 - **Included translations:** **Swedish (sv_SE), Norwegian (nb_NO), English (en_US)** 24 24 - **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) 25 26 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).* 27 28 28 29 == Installation == … … 41 42 - Compact with map & animation: `[spelhubben_weather place="Gothenburg" layout="compact" map="1" animate="1"]` 42 43 - 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"]` 44 45 45 46 = Classic Widget = … … 50 51 51 52 = 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.53 From 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. 53 54 54 55 = 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.56 No. 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. 56 57 57 58 = Block, shortcode or widget — what’s the difference? = … … 80 81 81 82 = 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.83 Yes. Set `providers="smhi,yr,openmeteo,fmi"` (order doesn’t matter). The plugin calculates a simple consensus across available providers for the displayed fields. 83 84 84 85 = Units & format? = … … 112 113 113 114 == 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 114 120 = 1.7.0 = 115 121 - New: **Shortcodes** admin page with searchable examples, one-click copy & **copy all**. … … 130 136 131 137 == Upgrade Notice == 138 = 1.7.5 = 139 Adds **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 132 141 = 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.142 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]. 134 143 135 144 Donate link: https://paypalme/spelhubben -
spelhubben-weather/trunk/spelhubben-weather.php
r3372751 r3384734 3 3 * Plugin Name: Spelhubben Weather 4 4 * 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. 05 * Version: 1.7.5 6 6 * Author: Spelhubben 7 7 * Text Domain: spelhubben-weather … … 19 19 // ── Constants (kept for backward compatibility). 20 20 if ( ! defined( 'SV_VADER_VER' ) ) { 21 define( 'SV_VADER_VER', '1.7. 0' );21 define( 'SV_VADER_VER', '1.7.5' ); 22 22 } 23 23 if ( ! defined( 'SV_VADER_DIR' ) ) {
Note: See TracChangeset
for help on using the changeset viewer.