为什么选择WeatherKit
最近个人在开发一个Flutter项目,是钓鱼相关的,提供水文查询,钓点记录,钓鱼记录等功能。其中钓鱼记录需要关联到天气相关的内容,主要是要用到历史天气数据。网上提供天气API的服务商很多,我查了一些资料,最终发现visualcrossing这家的接口设计和数据比较符合我的要求,但是这些天气API的免费额度都很低,如果只是个人玩玩是够用,发布出去商用肯定是需要充值的。后面查资料发现Apple在WWDC22
新推出了WeatherKit
,除了系统原生库WeatherKit
外(仅支持Apple平台,iOS 16.0+,iPadOS 16.0+,macOS 13.0+,Mac Catalyst 16.0+,tvOS 16.0+,watchOS 9.0+),还提供了Weather Kit REST API,而这个是全平台支持的,同时Apple开发者会员资格提供50万次调用/月
的额度,并且支持历史天气数据的查询,刚好满足我Flutter项目的要求。
WeatherKit也是收费的,会员资格提供50万次调用/月
的额度,超过需要额外付费,详细内容见:开始使用 WeatherKit
如何接入WeatherKit
1. 添加WeatherKit权限
在开发者中心网站Certificates, Identifiers & Profiles
页面中选择Identifiers
栏目,然后选择需要开启WeatherKit权限的项目:
进入后,选择App Service
栏目并勾选WeatherKit
选项:
注意这里AppID Prefix
就是你账户的Team ID
。
2. 注册WeatherKit
密钥
在Certificates, Identifiers, and Profiles
页面选择Keys
选项,然后选择Keys右边的加号,进入新建页面,勾选WeatherKit 选项,注册一个Key。
这里特别需要注意的是,系统会提示下载一个密钥文件,是签名WeatherKit
服务的私钥,只能下载一次,一定要妥善保管到安全的位置且不能泄露。注册后我们需要拿到Key ID
,在后续签名时需要用到:
完成以上操作后会在Services选项里面看到WeatherKit
,点击View
后滑到页面底部,可以看到WeatherKit service
的identifier
:
完成上述操作后,我们得到以下内容:
- Team ID
- 密钥
- WeatherKit Service identifier
- WeatherKit Key ID
Weather Kit REST API签名
Weather Kit REST API
使用JWT(JSON Web Token)
和ES256
算法作为请求授权认证,通常这一步应该在后端服务器上完成。具体内容可以在官方文档Request authentication for WeatherKit REST API中看到。由于现在开发的项目还没有做后台开发,为了方便调用,我先用Dart语言编写签名代码。
首先我们需要引入dart_jsonwebtoken框架,它提供了JWT的封装。
1 2 3 4 5 6
| dependencies: flutter: sdk: flutter
dart_jsonwebtoken: ^2.6.2
|
签名部分的代码封装如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
class AppleWeatherKitSign { final String secretKey; final String iss; final String sub; final String kid;
AppleWeatherKitSign( {required this.secretKey, required this.iss, required this.sub, required this.kid});
String sign(DateTime expirationDate) { final id = "$iss.$sub"; final jwt = JWT({ "iss": iss, "iat": DateTime.now().millisecondsSinceEpoch ~/ 1000, "exp": expirationDate.millisecondsSinceEpoch ~/ 1000, "sub": sub }, header: { "alg": "ES256", "kid": kid, "id": id });
final key = ECPrivateKey(secretKey); final token = jwt.sign(key, algorithm: JWTAlgorithm.ES256); return token; }
factory AppleWeatherKitSign.fromDefaultConstants() => AppleWeatherKitSign( secretKey: "填入:注册Key时下载的密钥", iss: "填入:Team ID", sub: "填入:WeatherKit Service identifier", kid: "填入:WeatherKit Key ID"); }
|
生成签名:
1 2 3 4
| final token = AppleWeatherKitSign.fromDefaultConstants() .sign(DateTime.now().add(const Duration(days: 1))); print('Signed token: $token');
|
Weather Kit REST API
调用
总共提供了两个接口,第一个是查询某地的天气数据集的可用状态,具体使用方式如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import 'package:tuple/tuple.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
test("request weather kit availability", () async { try { final token = AppleWeatherKitSign.fromDefaultConstants() .sign(DateTime.now().add(const Duration(days: 1))); const coordinate = Tuple2(28.212151, 112.955606); var dio = Dio(); final response1 = await dio.get( 'https://weatherkit.apple.com/api/v1/availability/${coordinate.item1}/${coordinate.item2}', options: Options(headers: {"Authorization": "Bearer $token"})); final response2 = await dio.get( 'https://weatherkit.apple.com/api/v1/availability/${coordinate.item1}/${coordinate.item2}?country=CN', options: Options(headers: {"Authorization": "Bearer $token"})); print(response1.data); print(response2.data); expect(response1.data, response2.data); } catch (e) { print(e); expect(true, false); } });
|
极端天气警报和空气质量等数据集不是所有国家可用,因此需要额外提供国家代码进行查询。目前测试中国(CN)返回currentWeather/forecastDaily/forecastHourly
, 美国(US)会多返回 weatherAlerts
。
第二个是查询某地的天气数据,具体调用样例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import 'package:intl/intl.dart'; import 'package:intl/date_symbol_data_local.dart'; import 'package:tuple/tuple.dart'; import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
test("request weather data", () async { try { initializeDateFormatting(); final token = AppleWeatherKitSign.fromDefaultConstants() .sign(DateTime.now().add(const Duration(days: 1))); const language = "zh-CN"; const coordinate = Tuple2(28.212151, 112.955606);
final dateFormat = DateFormat("yyyy-MM-dd'T'HH:mm:ss", "zh-CN"); Map<String, dynamic> parameters = {}; parameters["countryCode"] = "zh-CN"; parameters["timeZone"] = "Asia/Shanghai"; const dataSets = ["forecastHourly"]; parameters["dataSets"] = dataSets.map((e) => e).join(","); final hourlyStart = DateTime.now().subtract(const Duration(hours: 1)); final hourlyEnd = DateTime.now(); parameters["hourlyStart"] = "${dateFormat.format(hourlyStart)}Z"; parameters["hourlyEnd"] = "${dateFormat.format(hourlyEnd)}Z";
var dio = Dio(); final response1 = await dio.get( 'https://weatherkit.apple.com/api/v1/weather/$language/${coordinate.item1}/${coordinate.item2}', queryParameters: parameters, options: Options(headers: {"Authorization": "Bearer $token"})); print(response1.data); expect(response1.data["forecastHourly"] != null, true); } catch (e) { print(e); expect(true, false); } });
|
上述接口调用后,返回的数据样例如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| { forecastHourly: { name: HourlyForecast, metadata: { attributionURL: https: expireTime: 2023-01-01T16:02:18Z, latitude: 28.212, longitude: 112.956, readTime: 2023-01-01T15:02:18Z, reportedTime: 2023-01-01T14:00:00Z, units: m, version: 1, }, hours: [ { forecastStart: 2023-01-01T22:00:00Z, cloudCover: 0.98, conditionCode: Cloudy, daylight: false, humidity: 0.92, precipitationAmount: 0.0, precipitationIntensity: 0.0, precipitationChance: 0.0, precipitationType: clear, pressure: 1031.96, pressureTrend: steady, snowfallIntensity: 0.0, snowfallAmount: 0.0, temperature: 4.93, temperatureApparent: 1.97, temperatureDewPoint: 3.71, uvIndex: 0, visibility: 14391.75, windDirection: 339, windGust: 25.43, windSpeed: 13.11, }, ... ], }, }
|
上述内容只提供了访问历史数据的样例,当然WeatherKit
也提供了获取未来天气数据的功能,具体可以查看API文档,根据文档内容依葫芦画瓢填入相应参数即可,这里不做特别说明。
归因要求
无论是App或者Web网站,只要用到了WeatherKit
,都必须清晰地展示 Apple“天气”商标 (天气) 以及指向其他数据来源的法律链接。具体查看:Apple“天气”App 和第三方归因
问题
对于Weather Kit
现在还没有过多使用,毕竟项目还没有正式上线,但是测试时有发现它存在数据不准确的问题。我在获取当前定位位置的历史天气数据时,发现与实际有4摄氏度左右的差别,REST API
现在还是Beta
状态,期望后续能修复这个问题。另外就是Weather Kit
只能提供2021-08-01
以后的数据,更早的历史记录是无法获取的。
如果您知道更多的Weather Kit
问题,欢迎在评论区告知。
参考资料:
- Apple WeatherKit REST API 上手
- 开始使用 WeatherKit
- Request authentication for WeatherKit REST API
- weatherkitrestapi
- Hourly history with WeatherKit REST API
- Apple“天气”App 和第三方归因