RidgeUI脚本开发系列:编写天气预报脚本库

RidgeUI脚本开发系列:编写天气预报脚本库

首先老规矩,看下天气预报页面的主要功能。
在这里插入图片描述

天气预报通常会显示区域、天气、温湿度、风力风向及未来几天的温度和天气。另外区域也可以进行切换甚至支持显示多个区域。

我们这个脚本目标暂时先支持显示天气即可。

虽然页面交互较少,但是真正完成应用还有很多问题:最首要的是如何获取天气数据。

获取天气数据

通过询问AI,国内应该没有免费的API可以调用, 所以获取天气数据成了一个难题。

好在国外网站中,终于找到一个免费并且不需要提供token的服务,那就是 open-meteo。 我们访问 api.open-meteo.com

它有个生成调用地址的页面,结合询问AI,我们得到这个查询地址

https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&hourly=temperature_2m,relativehumidity_2m,precipitation,weathercode,windspeed_10m,winddirection_10m&daily=temperature_2m_max,temperature_2m_min,weathercode,windspeed_10m_max,winddirection_10m_dominant&timezone=auto&forecast_days=4

这样就能拿到我们页面所需所有数据。

另外这个接口需要传入查询地点的经纬度,而你总不能让用户去查经纬度。 虽然oepn-meteo提供了查询坐标的API,但是它只支持英文,例如

https://geocoding-api.open-meteo.com/v1/search?name=beijing

因为是按拼音查,还是有很多beijing的地址,所以这个方案只是个备选做法。

好在要查到区县以上的区域坐标还是可以做到的,那就是 china-regions-lat 库。 这个库是我根据行政区划结合地理位置处理生成的,根据地址就可以查询区号和中心经纬度

访问这个地址就可以

https://unpkg.com/china-regions-lat@1.0.1/lib/lats.js

其片段如下:

[
    {
      pid: 110000,
      code: 110119,
      fullname: '延庆区',
      center: [
        115.985006,
        40.465325
      ]
    },
    {
      pid: 120000,
      code: 120101,
      fullname: '和平区',
      center: [
        117.195907,
        39.118327
      ]
    },
]

这样我们就可以很开心的根据区域获取坐标了。

const searchAndFindParent = (data, searchTerm) => {
  const result = []
  // 先找出符合搜索条件的项目
  const matchedItems = data.filter(item => item.fullname.includes(searchTerm)).splice(0, 10)
  for (const item of matchedItems) {
    const currentResult = {
      ...item,
      parents: []
    }
    // 递归查找所有父级节点
    findAllParents(data, item.pid, currentResult.parents)
    result.push(currentResult)
  }
  return result
}

const findAllParents = (data, pid, parents) => {
  const parent = data.find(parentItem => parentItem.code === pid)
  if (parent) {
    parents.unshift(parent)
    // 继续递归查找父级的父级
    this._findAllParents(data, parent.pid, parents)
  }
}

上面这个方法就是根据给出的区域数据和用户输入的名称,查找符合条件的列表,每项都包含省市县便于用户选择

当用户选定区域后,我们得到坐标,然后调用获取天气接口,就能拿到数据了


// 根据经纬度获取天气信息
const getWeather = async (latitude, longitude) => {
  // 修正后的 API 请求地址,去除了错误的参数
  const url = `https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}&current_weather=true&hourly=temperature_2m,relativehumidity_2m,precipitation,weathercode,windspeed_10m,winddirection_10m&daily=temperature_2m_max,temperature_2m_min,weathercode,windspeed_10m_max,winddirection_10m_dominant&timezone=auto&forecast_days=4`
  try {
    const response = await fetch(url)
    if (response.ok) {
      return await response.json()
    }
  } catch (error) {
    console.error('获取天气信息时出错:', error)
  }
  return null
}

格式化返回数据

你是否注意到,表示天气的代码是一个数字,是世界气象组织(WMO)代码。这个是专业代码对天气划分非常细致,但这一方面不适合普通人直观感受,另一方面,和最终界面展示组件的属性也无法一一对应

我们找到了这个气象图标组件支持像 ‘Clear’ ‘Overcast’ ‘Broken Clouds’ 这样的天气名称,并且还支持夜晚图标,用户通过图标就能很直观了解当前天气

借助AI的辅助,我们生成了一个对照关系表,将天气代码转换为图标名称

// 天气代码对应的含义
const weatherCodeMap = {
  0: 'Clear',
  1: 'Clear',
  2: 'Broken Clouds',
  3: 'Cloudy',
  45: 'Fog',
  48: 'Fog',
  51: 'Light Drizzle',
  53: 'Drizzle',
  55: 'Heavy Drizzle',
  56: 'Sleet',
  57: 'Sleet',
  61: 'Light Snow',
  63: 'Snow',
  65: 'Heavy Snow',
  66: 'Sleet',
  67: 'Sleet',
  71: 'Light Snow',
  73: 'Snow',
  75: 'Heavy Snow',
  77: 'Hail',
  80: 'Light Rain Showers',
  81: 'Rain Showers',
  82: 'Heavy Rain Showers',
  85: 'Light Snow Showers',
  86: 'Heavy Snow Showers',
  95: 'Thunder Storm',
  96: 'Thunder Storm Light Rain',
  99: 'Thunder Storm Heavy Rain'
}

这样,从上述API拿到天气值就可以通过这个表转为具体数据,进而给到我们上个组件了

同样风力、风向描述也要转换,下面是风向的转换,将风向角度(360度)转化为八个方位

const getWindDirectionDescription = (angle) => {
    const directions = ['北', '东北', '东', '东南', '南', '西南', '西', '西北']
    const index = Math.round((angle % 360) / 45)
    return directions[index % 8]
}

将风速(米每秒)转换为一到十二级风


const getWindForceLevel = (speed) => {
  const windLevels = [
    { min: 0, max: 0.2, level: 0 },
    { min: 0.3, max: 1.5, level: 1 },
    { min: 1.6, max: 3.3, level: 2 },
    { min: 3.4, max: 5.4, level: 3 },
    { min: 5.5, max: 7.9, level: 4 },
    { min: 8.0, max: 10.7, level: 5 },
    { min: 10.8, max: 13.8, level: 6 },
    { min: 13.9, max: 17.1, level: 7 },
    { min: 17.2, max: 20.7, level: 8 },
    { min: 20.8, max: 24.4, level: 9 },
    { min: 24.5, max: 28.4, level: 10 },
    { min: 28.5, max: 32.6, level: 11 },
    { min: 32.7, max: Infinity, level: 12 }
  ]
  for (let i = 0; i < windLevels.length; i++) {
    const level = windLevels[i]
    if (speed >= level.min && speed <= level.max) {
      return level.level
    }
  }
  return null
}

当然请注意,上述脚本大部分都利用AI协助生成,大家开发也尽量善用AI辅助

编写脚本库主体

下面我们编写脚本库主体代码。

export default {
  name: 'WeatherForcast',
  externals: ['/china-regions-lat/lib/lats.js'],
  state : () => {
    return {
      currentRegionName: '北京市', // 当前位置名称
      currentRegionPos: [116.405285,39.904989], // 当前位置坐标
      weatherData: { // 天气数据
        currentWeather: { // 现在天气
          temperature: '--', // 温度
          humidity: '--', // 湿度
          windDirection: '-',  // 风向
          windSpeed: '-',
          windForce: '-',   // 风力
          weather: '--' // 天气
        },
        next5Days: []
      }
    }
  },

  setup() {
    this.fetchWeather()
  },

  actions: {
    async fetchWeather () {
       const result = await getWeather(this.currentRegionPos[1], this.currentRegionPos[0])
       this.weatherData = convertWeatherData(result)
    }
  }
}

脚本库命名为WeatherForcast,通过externals引入china-regions-lat库。

在状态字段,提供当前位置名称和当前经纬度。 天气数据是一个完整对象, 里面提供现在天气情况还有未来五日天气概览。

现在天气情况中,提供温度、湿度、风向、风力等。 未来五日,我们提供天气和温度即可。

在setup时,初始化和获取天气信息,并将接口返回的数据转换为页面状态数据

const convertWeatherData = weatherData => {
   const result = {
    currentWeather: {
      temperature: weatherData.current_weather.temperature + '℃', // 温度
      humidity: weatherData.hourly.relativehumidity_2m[0] + '%',  // 湿度
      windDirection: getWindDirectionDescription(weatherData.current_weather.winddirection), // 风力
      windSpeed: weatherData.current_weather.windspeed,
      windForce: getWindForceLevel(weatherData.current_weather.windspeed) + '级',
      weather: weatherCodeMap[weatherData.current_weather.weathercode] || '未知天气'
    },
    next10Hours: [],
    next5Days: []
  }
  const dailyData = weatherData.daily
  for (let i = 0; i < dailyData.time.length; i++) {
    result.next5Days.push({
      date: dailyData.time[i],
      maxTemperature: dailyData.temperature_2m_max[i]  + '℃',
      weather: weatherCodeMap[dailyData.weathercode[i]] || '未知天气'
    })
  }
  return result
}

小重点: 我们设计前端页面时,所使用的数据结构应该怎样? 在ridgeui界面脚本开发时,数据结构必须满足界面显示的要求。但是我们获得的后端数据往往是个另外的形式。

通常情况下
1、如果前后独立开发、或者后端数据已经是成型的专业业务。 那么我们需要在脚本里进行转换
2、如果后端只为前端服务,一起开发,那么后端提供数据尽量向前端靠齐,避免转换成本。

如何选择也不必纠结,前端的特点就是需求变化快,最初的决策很可能经过时间的推移而逐步调整,我们能随时选择一个最合适当下的方案就可以了。

通过页面状态层,前端可以不随着后端进行独立演进,甚至可以更换后端服务,像我们这个天气组件,未来如果open-meteo服务不可用,我们还可以更换使用其他API

切换位置

接下来提供用户选择区域功能。 渲染列表并响应列表项点击功能我们已经多次实现,这里不再赘述

先实现方法更新候选地址列表,当用户输入名称时,过滤显示满足名称的列表

  async filterPosition () {
      this.filterResult = searchAndFindParent(CHINA_REGIONS_LAT, this.currentRegionName)
  }

然后提供选择方法,当用户点击列表项内按钮时,保存地址并刷新天气信息

  confirmRegion(scope) { // 区域确认
      this.currentRegionName = scope.item.fullname
      localStorage.setItem('currentRegionName', scope.item.fullname)
      this.currentRegionPos = scope.item.center
      localStorage.setItem('currentRegionPos', JSON.stringify(scope.item.center))
      this.showEditLocation = false
      this.fetchWeather()
  }

这里,将地区和位置保存到了浏览器本地缓存中。
那么只需要在初始化时去读取这个地址,用户重新进入页面就会使用之前位置的天气了

  setup () {
    this.currentRegionName = localStorage.getItem('currentRegionName') || '北京市'
    this.currentRegionPos = JSON.parse(localStorage.getItem('currentRegionPos') || '[116.405285,39.904989]')
    this.fetchWeather()
  }

现在,我们连接上位置、天气还有未来几天列表,同时增加对话框配置提供位置修改,一个比较完整的天气预报程序就完成了。

你可以通过访问ridgeui.com网站示例例中看到这个全部代码和配置示例

页面地址: https://ridgeui.com/npm/ridge-app-weather/
页面编辑: https://ridgeui.com/npm/ridge-editor/?import=ridge-app-weather

您也可以使用这个脚本定制化自己的天气组件

最后的问题

最后,给大家留一个问题:上述获取天气代码中,还同时得到了最近2天按小时的气象数据。 是否可以就此实现一个按小时的温度变化曲线功能呢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值