为什么挑选WeatherKit

最近个人在开发一个Flutter项目,是垂钓相关的,供给水文查询,钓点记载,垂钓记载等功用。其中垂钓记载需求关联到气候相关的内容,主要是要用到前史气候数据。网上供给气候API的服务商许多,我查了一些材料,终究发现visualcrossing这家的接口规划和数据比较契合我的要求,可是这些气候API的免费额度都很低,假如只是个人玩玩是够用,发布出去商用肯定是需求充值的。后边查材料发现AppleWWDC22新推出了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权限的项目:

Flutter接入Apple的WeatherKit

进入后,挑选App Service栏目并勾选WeatherKit选项:

留意这儿AppID Prefix便是你账户的Team ID

2. 注册WeatherKit密钥

Certificates, Identifiers, and Profiles页面挑选Keys选项,然后挑选Keys右边的加号,进入新建页面,勾选WeatherKit 选项,注册一个Key。

Flutter接入Apple的WeatherKit

这儿特别需求留意的是,系统会提示下载一个密钥文件,是签名WeatherKit服务的私钥,只能下载一次,一定要妥善保管到安全的位置且不能走漏。注册后咱们需求拿到Key ID,在后续签名时需求用到:

Flutter接入Apple的WeatherKit

完成以上操作后会在Services选项里边看到WeatherKit,点击View后滑到页面底部,能够看到WeatherKit serviceidentifier:

Flutter接入Apple的WeatherKit

Flutter接入Apple的WeatherKit

完成上述操作后,咱们得到以下内容:

  1. Team ID
  2. 密钥
  3. WeatherKit Service identifier
  4. 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的封装。

# 在 pubspec.yaml中引入dart_jsonwebtoken结构
dependencies:
  flutter:
    sdk: flutter
  dart_jsonwebtoken: ^2.6.2

签名部分的代码封装如下:

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");
}

生成签名:

final token = AppleWeatherKitSign.fromDefaultConstants()
          .sign(DateTime.now().add(const Duration(days: 1)));
print('Signed token: $token');
// Signed token: eyJhbGciOiJFUzI1NiIsImtpZCI6IjY1UDk5NExBTlAiLCJpZCI6IlBHTFI4U1hQUVAuY29tLmRldmx4eC5maXNoaW5nV2VhdGhlclNlcnZpY2UiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJQR0xSOFNYUFFQIiwiaWF0IjoxNjcyNTgzMTY3LCJleHAiOjE2NzEyOTY5ODYsInN1YiI6ImNvbS5kZXZseHguZmlzaGluZ1dlYXRoZXJTZXJ2aWNlIn0.Ca7BY43zbtmSZTPD6zDBKIjiS8w45txCYBK3zOmgSwgUI-u8Z2-UlXH-7Xo1aQ0MUipvj8uYjekSdxB2FOI6eB

Weather Kit REST API调用

总共供给了两个接口,第一个是查询某地的气候数据集的可用状态,详细运用方法如下:

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"}));
   // 这儿的country参数需求契合规范 ISO Alpha-2 country code: https://www.iban.com/country-codes
   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); // [currentWeather, forecastDaily, forecastHourly]
   print(response2.data); // [currentWeather, forecastDaily, forecastHourly]
   expect(response1.data, response2.data);
 } catch (e) {
   print(e);
   expect(true, false);
 }
});

极点气候警报和空气质量等数据集不是所有国家可用,因而需求额定供给国家代码进行查询。现在测验中国(CN)返回currentWeather/forecastDaily/forecastHourly, 美国(US)会多返回 weatherAlerts

第二个是查询某地的气候数据,详细调用样例如下:

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);
 }
});

上述接口调用后,返回的数据样例如下:

{
  forecastHourly:
    {
      name: HourlyForecast,
      metadata:
        {
          attributionURL: https://weatherkit.apple.com/legal-attribution.html,
          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问题,欢迎在评论区奉告。

参考材料:

  1. Apple WeatherKit REST API 上手
  2. 开始运用 WeatherKit
  3. Request authentication for WeatherKit REST API
  4. weatherkitrestapi
  5. Hourly history with WeatherKit REST API
  6. Apple“气候”App 和第三方归因

by 星的天空