HarmonyOS智慧农业管理应用开发教程--高高种地--第20篇:天气服务与气象数据

第20篇:天气服务与气象数据

📚 本篇导读

天气对农业生产至关重要,及时准确的天气信息可以帮助农户做出正确的农事决策。本篇教程将详细讲解如何集成高德地图的天气API,实现实时天气查询、天气预报展示,并结合农业场景提供智能的农事建议。

本篇将实现

  • 🌤️ 实时天气查询(基于高德地图Weather API)
  • 📅 天气预报展示(7天预报、24小时预报)
  • ⚠️ 气象预警功能(恶劣天气提醒)
  • 🌾 农事建议关联(根据天气推荐农事活动)
  • 💾 天气数据缓存(减少API调用,提升性能)

🎯 学习目标

完成本篇教程后,你将掌握:

  1. 如何使用高德地图Weather API查询天气
  2. 如何设计和实现天气数据模型
  3. 如何展示实时天气和天气预报
  4. 如何根据天气提供农事建议
  5. 天气数据的缓存策略

一、天气服务架构设计

1.1 功能架构

复制代码
天气服务模块
├── 数据获取层
│   ├── 高德Weather API(实时天气)
│   ├── 逆地理编码(获取城市名)
│   └── 数据缓存(减少API调用)
│
├── 数据处理层
│   ├── 天气数据解析
│   ├── 天气图标映射
│   └── 农事建议生成
│
├── 展示层
│   ├── 地图页天气卡片
│   ├── 天气详情页面
│   └── 天气预报列表
│
└── 应用层
    ├── 农事提醒(根据天气)
    ├── 浇水建议(根据降雨)
    └── 病虫害预警(根据温湿度)

1.2 数据流程

复制代码
用户定位
  ↓
逆地理编码获取城市
  ↓
查询天气API
  ↓
解析天气数据
  ↓
缓存天气信息
  ↓
展示天气 + 生成农事建议
  ↓
语音播报(可选)

1.3 数据模型

实时天气接口

typescript 复制代码
/**
 * 当前天气信息
 */
export interface CurrentWeather {
  temperature: number;        // 当前温度(℃)
  feelsLike: number;         // 体感温度(℃)
  weatherType: WeatherType;  // 天气类型
  weatherDesc: string;       // 天气描述
  humidity: number;          // 湿度(%)
  windSpeed: number;         // 风速(km/h)
  windDirection: string;     // 风向
  pressure: number;          // 气压(hPa)
  uvIndex: number;           // 紫外线指数
  visibility: number;        // 可见度(km)
  airQuality: AirQuality;    // 空气质量
  airQualityIndex: number;   // 空气质量指数AQI
  sunrise: string;           // 日出时间
  sunset: string;            // 日落时间
  updateTime: number;        // 更新时间戳
}

/**
 * 天气类型枚举
 */
export enum WeatherType {
  SUNNY = 'sunny',              // 晴天
  CLOUDY = 'cloudy',            // 多云
  OVERCAST = 'overcast',        // 阴天
  RAINY = 'rainy',              // 雨天
  HEAVY_RAIN = 'heavy_rain',    // 大雨
  THUNDERSTORM = 'thunderstorm', // 雷阵雨
  SNOWY = 'snowy',              // 雪天
  FOGGY = 'foggy'               // 雾天
}

天气预报接口

typescript 复制代码
/**
 * 每日天气预报
 */
export interface DailyForecast {
  date: number;              // 日期时间戳
  dateStr: string;           // 日期字符串
  weatherType: WeatherType;  // 天气类型
  weatherDesc: string;       // 天气描述
  tempMax: number;           // 最高温度
  tempMin: number;           // 最低温度
  humidity: number;          // 湿度
  rainfall: number;          // 降雨量(mm)
  windSpeed: number;         // 风速
  uvIndex: number;           // 紫外线指数
}

/**
 * 每小时天气预报
 */
export interface HourlyForecast {
  time: number;              // 时间戳
  timeStr: string;           // 时间字符串
  temperature: number;       // 温度
  weatherType: WeatherType;  // 天气类型
  weatherDesc: string;       // 天气描述
  rainfall: number;          // 降雨概率(%)
  windSpeed: number;         // 风速
}

二、高德地图Weather API集成

2.1 导入Weather API

在页面中导入高德地图搜索SDK的天气相关类:

typescript 复制代码
import { 
  WeatherSearch,           // 天气搜索
  WeatherSearchQuery,      // 天气查询
  LocalWeatherLive,        // 实时天气
  LocalWeatherLiveResult,  // 实时天气结果
  OnWeatherSearchListener, // 天气搜索监听器
  AMapException            // 异常码
} from '@amap/amap_lbs_search';

2.2 实现天气查询

在FieldMapPage中实现天气查询

typescript 复制代码
/**
 * 查询当前城市天气
 * @param cityName 城市名称(如:武汉市)
 */
private searchWeather(cityName: string): void {
  if (!cityName) {
    console.warn('[FieldMapPage] 城市名称为空,无法查询天气');
    return;
  }
  
  try {
    const context = getContext(this) as Context;
    
    // 1. 创建天气搜索监听器
    const weatherSearchListener: OnWeatherSearchListener = {
      // 实时天气查询回调
      onWeatherLiveSearched: (
        weatherLiveResult: LocalWeatherLiveResult | undefined, 
        errorCode: number
      ): void => {
        if (errorCode === AMapException.CODE_AMAP_SUCCESS) {
          if (weatherLiveResult && weatherLiveResult.getLiveResult()) {
            const liveWeather: LocalWeatherLive = weatherLiveResult.getLiveResult();
            
            // 提取天气信息
            this.currentWeather = liveWeather.getWeather() || '';
            this.currentTemperature = liveWeather.getTemperature() || '';
            this.windDirection = liveWeather.getWindDirection() || '';
            this.windPower = liveWeather.getWindPower() || '';
            this.humidity = liveWeather.getHumidity() || '';
            this.reportTime = liveWeather.getReportTime() || '';
            
            console.info(`[FieldMapPage] ✅ 天气查询成功: ${this.currentWeather} ${this.currentTemperature}°C`);
            
            // 可选:语音播报天气
            this.speakWeatherInfo();
          }
        } else {
          console.error(`[FieldMapPage] ❌ 天气查询失败,错误码: ${errorCode}`);
          
          promptAction.showToast({
            message: '天气查询失败',
            duration: 2000
          });
        }
      },
      
      // 天气预报查询回调(本例不使用)
      onWeatherForecastSearched: (): void => {
        // 不处理预报天气
      }
    };
    
    // 2. 创建天气查询
    const query = new WeatherSearchQuery(
      cityName,                              // 城市名称
      WeatherSearchQuery.WEATHER_TYPE_LIVE   // 查询类型:实时天气
    );
    
    // 3. 创建天气搜索对象
    const weatherSearch = new WeatherSearch(context);
    weatherSearch.setOnWeatherSearchListener(weatherSearchListener);
    weatherSearch.setQuery(query);
    
    // 4. 执行异步查询
    weatherSearch.searchWeatherAsyn();
    
    console.info(`[FieldMapPage] 🔍 开始查询天气: ${cityName}`);
    
  } catch (error) {
    console.error('[FieldMapPage] 天气查询异常:', error);
    
    promptAction.showToast({
      message: '天气服务异常',
      duration: 2000
    });
  }
}

关键步骤

  1. 创建OnWeatherSearchListener监听器
  2. 创建WeatherSearchQuery查询对象
  3. 创建WeatherSearch搜索对象
  4. 设置监听器和查询参数
  5. 调用searchWeatherAsyn()执行异步查询

2.3 获取城市名称

通过逆地理编码获取当前位置的城市名:

typescript 复制代码
/**
 * 获取定位城市信息并查询天气
 * @param latitude 纬度
 * @param longitude 经度
 */
private async getLocationCity(latitude: number, longitude: number): Promise<void> {
  try {
    // 1. 创建逆地理编码请求
    const reverseGeoCodeRequest: geoLocationManager.ReverseGeoCodeRequest = {
      latitude: latitude,
      longitude: longitude,
      maxItems: 1,
      locale: 'zh'
    };
    
    // 2. 执行逆地理编码
    geoLocationManager.getAddressesFromLocation(reverseGeoCodeRequest)
      .then((addresses) => {
        if (addresses && addresses.length > 0) {
          const geoAddress = addresses[0];
          
          // 3. 提取城市信息
          const city = geoAddress.locality || geoAddress.subAdministrativeArea || '';
          const district = geoAddress.subLocality || '';
          const province = geoAddress.administrativeArea || '';
          
          console.info(`[FieldMapPage] 逆地理编码成功: ${province} ${city} ${district}`);
          
          // 4. 查询天气
          if (city) {
            this.searchWeather(city);
          }
        }
      })
      .catch((error: BusinessError) => {
        console.error('[FieldMapPage] 逆地理编码失败:', error.message);
      });
      
  } catch (error) {
    console.error('[FieldMapPage] 逆地理编码异常:', error);
  }
}

使用场景

  • 在定位成功后调用,自动查询当前位置的天气
  • 用户移动到新位置时更新天气信息

2.4 天气图标映射

根据天气描述返回对应的emoji图标:

typescript 复制代码
/**
 * 根据天气描述返回对应图标
 * @param weather 天气描述
 * @returns emoji图标
 */
private getWeatherIcon(weather: string): string {
  if (weather.includes('晴')) return '☀️';
  if (weather.includes('云') || weather.includes('阴')) return '☁️';
  if (weather.includes('雨')) return '🌧️';
  if (weather.includes('雪')) return '❄️';
  if (weather.includes('雾') || weather.includes('霾')) return '🌫️';
  if (weather.includes('雷')) return '⛈️';
  if (weather.includes('风')) return '💨';
  return '🌤️';  // 默认图标
}

三、天气展示UI实现

3.1 地图页天气卡片

在地图页面右上角显示天气信息:

typescript 复制代码
// 在FieldMapPage的build方法中
if (this.currentWeather) {
  Row() {
    // 天气图标
    Text(this.getWeatherIcon(this.currentWeather))
      .fontSize(24)

    // 天气信息
    Column() {
      Text(`${this.currentWeather} ${this.currentTemperature}°C`)
        .fontSize(14)
        .fontWeight(FontWeight.Medium)
        .fontColor('#333333')

      Text(`${this.windDirection}风 ${this.windPower}级`)
        .fontSize(11)
        .fontColor('#999999')
        .margin({ top: 4 })
    }
    .alignItems(HorizontalAlign.Start)
    .margin({ left: 8 })
  }
  .padding(12)
  .backgroundColor('#FFFFFF')
  .borderRadius(8)
  .shadow({ radius: 4, color: '#22000000' })
  .margin({ top: 80, right: 16 })
  .onClick(() => {
    // 点击显示天气详情
    this.showWeatherDialog = true;
  })
}

3.2 天气详情对话框

typescript 复制代码
// 天气详情对话框
if (this.showWeatherDialog) {
  Column() {
    // 标题栏
    Row() {
      Text('天气详情')
        .fontSize(18)
        .fontWeight(FontWeight.Bold)
        .fontColor('#333333')

      Blank()

      Image($r('sys.media.ohos_ic_public_cancel'))
        .width(24)
        .height(24)
        .fillColor('#666666')
        .onClick(() => {
          this.showWeatherDialog = false;
        })
    }
    .width('100%')
    .padding(16)

    Divider()

    // 天气信息
    Column({ space: 16 }) {
      // 主要信息
      Row() {
        Text(this.getWeatherIcon(this.currentWeather))
          .fontSize(64)

        Column() {
          Text(`${this.currentTemperature}°C`)
            .fontSize(48)
            .fontWeight(FontWeight.Bold)
            .fontColor('#333333')

          Text(this.currentWeather)
            .fontSize(16)
            .fontColor('#666666')
        }
        .alignItems(HorizontalAlign.Start)
        .margin({ left: 24 })
      }
      .width('100%')
      .justifyContent(FlexAlign.Center)
      .padding({ top: 24, bottom: 24 })

      // 详细信息
      Column({ space: 12 }) {
        this.buildWeatherInfoRow('风向风力', `${this.windDirection}风 ${this.windPower}级`);
        this.buildWeatherInfoRow('湿度', `${this.humidity}%`);
        this.buildWeatherInfoRow('更新时间', this.reportTime);
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#F5F5F5')
      .borderRadius(12)

      // 农事建议
      this.buildAgricultureAdvice();
    }
    .width('100%')
    .padding(16)
  }
  .width('90%')
  .backgroundColor('#FFFFFF')
  .borderRadius(16)
  .shadow({ radius: 16, color: '#33000000' })
}

/**
 * 构建天气信息行
 */
@Builder
buildWeatherInfoRow(label: string, value: string) {
  Row() {
    Text(label)
      .fontSize(14)
      .fontColor('#666666')
      .width(80)

    Text(value)
      .fontSize(14)
      .fontColor('#333333')
      .fontWeight(FontWeight.Medium)
      .layoutWeight(1)
  }
  .width('100%')
}

3.3 农事建议生成

根据天气条件生成农事建议:

typescript 复制代码
/**
 * 构建农事建议
 */
@Builder
buildAgricultureAdvice() {
  Column({ space: 12 }) {
    Text('🌾 农事建议')
      .fontSize(16)
      .fontWeight(FontWeight.Bold)
      .fontColor('#333333')
      .width('100%')

    Column({ space: 8 }) {
      ForEach(this.getAgricultureAdvice(), (advice: string, index: number) => {
        Row({ space: 8 }) {
          Text(`${index + 1}.`)
            .fontSize(14)
            .fontColor('#666666')
            .width(24)

          Text(advice)
            .fontSize(14)
            .fontColor('#333333')
            .lineHeight(22)
            .layoutWeight(1)
        }
        .width('100%')
      }, (advice: string, index: number) => `advice_${index}`)
    }
    .width('100%')
    .padding(12)
    .backgroundColor('#FFF9E6')
    .borderRadius(8)
  }
  .width('100%')
}

/**
 * 根据天气生成农事建议
 */
private getAgricultureAdvice(): string[] {
  const advice: string[] = [];
  const weather = this.currentWeather;
  const temp = parseInt(this.currentTemperature);
  const humidity = parseInt(this.humidity);

  // 根据天气类型给建议
  if (weather.includes('晴')) {
    advice.push('天气晴朗,适合进行田间作业');
    if (temp > 30) {
      advice.push('温度较高,注意及时浇水,避免作物缺水');
      advice.push('高温天气,建议早晚进行农事活动');
    } else if (temp > 20) {
      advice.push('温度适宜,可以进行播种、移栽等工作');
    }
  } else if (weather.includes('雨')) {
    advice.push('雨天不宜进行田间作业');
    advice.push('注意排水,防止田间积水');
    advice.push('雨后注意病虫害防治');
  } else if (weather.includes('云') || weather.includes('阴')) {
    advice.push('多云天气,适合进行大部分农事活动');
    advice.push('光照较弱,注意观察作物生长情况');
  }

  // 根据湿度给建议
  if (humidity > 80) {
    advice.push('湿度较高,注意通风,预防病害发生');
  } else if (humidity < 40) {
    advice.push('空气干燥,注意增加灌溉频次');
  }

  // 根据风力给建议
  const windLevel = parseInt(this.windPower);
  if (windLevel >= 5) {
    advice.push('风力较大,不宜喷洒农药,避免药液飘移');
  }

  return advice.length > 0 ? advice : ['当前天气条件良好,可正常进行农事活动'];
}

四、天气数据缓存

4.1 缓存策略设计

为了减少API调用和提升性能,实现天气数据缓存:

typescript 复制代码
/**
 * 天气缓存接口
 */
interface WeatherCache {
  weather: string;
  temperature: string;
  windDirection: string;
  windPower: string;
  humidity: string;
  reportTime: string;
  cacheTime: number;  // 缓存时间戳
}

/**
 * 缓存天气数据
 */
private async cacheWeatherData(): Promise<void> {
  const cache: WeatherCache = {
    weather: this.currentWeather,
    temperature: this.currentTemperature,
    windDirection: this.windDirection,
    windPower: this.windPower,
    humidity: this.humidity,
    reportTime: this.reportTime,
    cacheTime: Date.now()
  };

  await StorageUtil.saveObject('weather_cache', cache);
  console.info('[FieldMapPage] 天气数据已缓存');
}

/**
 * 读取缓存的天气数据
 */
private async loadCachedWeather(): Promise<boolean> {
  try {
    const cache = await StorageUtil.getObject<WeatherCache>('weather_cache', null);

    if (!cache) {
      return false;
    }

    // 检查缓存是否过期(30分钟)
    const cacheAge = Date.now() - cache.cacheTime;
    const CACHE_DURATION = 30 * 60 * 1000;  // 30分钟

    if (cacheAge > CACHE_DURATION) {
      console.info('[FieldMapPage] 天气缓存已过期');
      return false;
    }

    // 使用缓存数据
    this.currentWeather = cache.weather;
    this.currentTemperature = cache.temperature;
    this.windDirection = cache.windDirection;
    this.windPower = cache.windPower;
    this.humidity = cache.humidity;
    this.reportTime = cache.reportTime;

    console.info('[FieldMapPage] 使用缓存的天气数据');
    return true;

  } catch (error) {
    console.error('[FieldMapPage] 读取天气缓存失败:', error);
    return false;
  }
}

缓存策略

  • 缓存有效期:30分钟
  • 优先使用缓存数据
  • 缓存过期后重新查询
  • 查询成功后更新缓存

五、语音播报天气

结合TTS服务播报天气信息:

typescript 复制代码
/**
 * 语音播报天气信息
 */
private async speakWeatherInfo(): Promise<void> {
  try {
    const text = `当前天气${this.currentWeather},温度${this.currentTemperature}度,` +
                 `${this.windDirection}风${this.windPower}级,湿度${this.humidity}%`;

    await ttsService.speak(text, 1.0, 0.9);
    console.info('[FieldMapPage] 天气信息已播报');
  } catch (error) {
    console.error('[FieldMapPage] 天气播报失败:', error);
  }
}

六、实操练习

练习1:查看实时天气

任务:在地图页面查看当前位置的天气

步骤

  1. 运行应用,进入地图页面
  2. 确保定位权限已授权
  3. 等待定位成功
  4. 观察右上角的天气卡片
  5. 点击天气卡片查看详情

预期结果

  • 显示当前天气、温度
  • 显示风向、风力、湿度
  • 显示农事建议

练习2:测试天气缓存

任务:验证天气数据缓存功能

步骤

  1. 首次打开应用,查询天气
  2. 关闭应用
  3. 30分钟内重新打开应用
  4. 观察控制台日志

预期结果

  • 首次查询调用API
  • 30分钟内使用缓存数据
  • 30分钟后重新查询API

练习3:根据天气调整建议

任务:修改农事建议逻辑

代码

typescript 复制代码
private getAgricultureAdvice(): string[] {
  const advice: string[] = [];

  // 添加自定义建议逻辑
  if (this.currentWeather.includes('雨')) {
    advice.push('雨天适合室内整理农具');
    advice.push('检查排水系统是否正常');
  }

  return advice;
}

七、总结

本篇教程完成了天气服务的集成,主要包括:

✅ 已实现功能

功能 说明
实时天气查询 基于高德Weather API
逆地理编码 获取当前位置城市名
天气展示 地图页卡片 + 详情对话框
农事建议 根据天气生成建议
数据缓存 30分钟缓存策略
语音播报 TTS播报天气信息

🎯 核心技术点

  1. WeatherSearch API:高德天气查询
  2. 逆地理编码:坐标转城市名
  3. 数据缓存:Preferences存储
  4. 智能建议:天气与农事关联
  5. UI设计:天气卡片与详情页

🚀 下一步

在下一篇教程中,我们将学习二十四节气与农事指导系统。


恭喜! 🎉 你已经完成了天气服务的集成,为智慧农业管理增添了重要的气象数据支持。

相关推荐
爱吃大芒果2 小时前
Flutter for OpenHarmony 适配:mango_shop 页面布局的鸿蒙多设备屏幕适配方案
flutter·华为·harmonyos
前端不太难2 小时前
没有文档模型,HarmonyOS PC 应用会发生什么?
华为·状态模式·harmonyos
2601_949593652 小时前
高级进阶 React Native 鸿蒙跨平台开发:LinearGradient 玻璃拟态卡片
react native·react.js·harmonyos
摘星编程2 小时前
在OpenHarmony上用React Native:TopTab顶部标签页
react native·react.js·harmonyos
大雷神2 小时前
HarmonyOS智慧农业管理应用开发教程--高高种地--第25篇:学习中心 - 课程详情与学习
学习·华为·harmonyos
一起养小猫3 小时前
Flutter for OpenHarmony 实战:从零开发一款五子棋游戏
android·前端·javascript·flutter·游戏·harmonyos
BlackWolfSky3 小时前
鸿蒙中级课程笔记8—Native适配开发
笔记·华为·harmonyos
一起养小猫3 小时前
Flutter for OpenHarmony 实战:天气预报应用UI设计与主题切换
jvm·数据库·spring·flutter·ui·harmonyos
BlackWolfSky3 小时前
鸿蒙中级课程笔记7—给应用添加通知
笔记·华为·harmonyos