基于 Open-Meteo API 的 Java 天气工具类封装(附完整代码 + 使用示例)
本文介绍如何使用 Open-Meteo API 打造一个 专业级天气数据工具类,支持降雨、温度、湿度、风速、气压、辐射、土壤含水量等十几种气象要素查询,并包含 ET₀(蒸散量)、有效降雨、日累计雨量等灌区或农业常用指标的计算。
提供完整代码,开箱即用。
✨一、背景介绍
很多农业、水利、智慧灌区系统,都需要实时或短期(3–7天)天气预报数据,例如:
-
降雨量
-
风速风向
-
土壤含水量
-
气温、湿度
-
太阳辐射
-
蒸散量 ET₀(FAO56)
-
有效降雨
传统商业气象 API 费用较高,而 Open-Meteo 是一个质量很高、功能丰富、免费可商用的气象服务,非常适合数据科学、物联网、农业模型等应用。
✨ 二、Open-Meteo API 优势
-
✔ 免费,无需 Token
-
✔ 支持全世界任意经纬度
-
✔ 支持小时级天气(1h 颗粒度)
-
✔ 支持未来 7–16 天预报
-
✔ 支持几十种气象要素
-
✔ JSON 数据结构清晰稳定
-
✔ 适合模型计算(ET₀、供需水分析等)
✨ 三、本文目标
为了方便在 Java 项目中直接使用 Open-Meteo,我写了一个增强版工具类 OpenMeteoUtils,支持:
🔧 功能能力升级
| 功能 | 说明 |
|---|---|
| 获取常规天气 | 降雨、湿度、温度、风速、风向、辐射、气压 |
| 获取土壤含水量 | 0–1cm、1–3cm、3–9cm、9–27cm、27–81cm |
| 灌区综合接口 | 一次性请求全部关键要素 |
| ET₀(小时级) | FAO56 简化公式 |
| ET₀(日级) | 自动按天累计 |
| 日降雨量 | 自动汇总 |
| 有效降雨 | P × 系数 |
| 小时级天气封装 | 适合前端列表和图表 |
并提供了 缓存机制(30分钟),避免 API 请求过于频繁。
下面进入正文。
✨ 四、完整工具类代码(可直接使用)
代码全部经过整理、重构、注释,适用于任何 Spring Boot / Java 项目。。
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.zhy.irrigation.siyu.domain.OpenMeteoResponse;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* Open-Meteo 天气工具类
*/
public class OpenMeteoUtils {
private static final String BASE_URL = "https://api.open-meteo.com/v1/forecast";
private static final HttpClient httpClient = HttpClient.newHttpClient();
private static final ObjectMapper objectMapper = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
/** 缓存数据 */
private static final Map<String, OpenMeteoResponse> CACHE = new ConcurrentHashMap<>();
private static final Map<String, Long> CACHE_TIME = new ConcurrentHashMap<>();
private static final long CACHE_EXPIRE_MS = 30 * 60 * 1000; // 30分钟
/** 开关:是否开启缓存 */
private static final boolean CACHE_ENABLED = true;
/**
* 核心:请求 Open Meteo API
*/
public static OpenMeteoResponse getForecast(
double lat,
double lon,
LocalDateTime startTime,
LocalDateTime endTime,
List<String> hourlyVars
) throws Exception {
LocalDate startDate = startTime.toLocalDate();
LocalDate endDate = endTime.toLocalDate();
String key = lat + "_" + lon + "_" + startDate + "_" + endDate + "_" + hourlyVars;
/** 缓存逻辑 */
if (CACHE_ENABLED && CACHE.containsKey(key)) {
if (System.currentTimeMillis() - CACHE_TIME.get(key) < CACHE_EXPIRE_MS) {
return CACHE.get(key);
}
}
String url = buildUrl(lat, lon, startDate, endDate, hourlyVars);
HttpRequest request = HttpRequest.newBuilder()
.GET()
.uri(URI.create(url))
.timeout(java.time.Duration.ofSeconds(10))
.build();
HttpResponse<String> resp = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
OpenMeteoResponse respObj = objectMapper.readValue(resp.body(), OpenMeteoResponse.class);
if (CACHE_ENABLED) {
CACHE.put(key, respObj);
CACHE_TIME.put(key, System.currentTimeMillis());
}
return respObj;
}
/**
* 构建 URL
*/
private static String buildUrl(
double lat,
double lon,
LocalDate start,
LocalDate end,
List<String> hourlyVars) {
StringBuilder sb = new StringBuilder(BASE_URL)
.append("?latitude=").append(lat)
.append("&longitude=").append(lon)
.append("&timezone=auto")
.append("&start_date=").append(start)
.append("&end_date=").append(end);
if (hourlyVars != null && !hourlyVars.isEmpty()) {
sb.append("&hourly=").append(String.join(",", hourlyVars));
}
return sb.toString();
}
// ============================================================
// 封装的快捷获取方法
// ============================================================
/** 降雨 */
public static OpenMeteoResponse getRainfall(
double lat, double lon,
LocalDateTime start, LocalDateTime end) throws Exception {
return getForecast(lat, lon, start, end, List.of("precipitation"));
}
/** 风速风向 */
public static OpenMeteoResponse getWind(
double lat, double lon,
LocalDateTime start, LocalDateTime end) throws Exception {
return getForecast(lat, lon, start, end, List.of("wind_speed_10m", "wind_direction_10m"));
}
/** 温度 */
public static OpenMeteoResponse getTemperatureForecast(
double lat, double lon, LocalDateTime start, LocalDateTime end) throws Exception {
return getForecast(lat, lon, start, end, List.of("temperature_2m"));
}
/** 湿度 */
public static OpenMeteoResponse getHumidityForecast(
double lat, double lon, LocalDateTime start, LocalDateTime end) throws Exception {
return getForecast(lat, lon, start, end, List.of("relative_humidity_2m"));
}
/** 气压 */
public static OpenMeteoResponse getPressureForecast(
double lat, double lon, LocalDateTime start, LocalDateTime end) throws Exception {
return getForecast(lat, lon, start, end, List.of("surface_pressure"));
}
/** 辐射(太阳短波辐射) */
public static OpenMeteoResponse getSolarRadiation(
double lat, double lon, LocalDateTime start, LocalDateTime end) throws Exception {
return getForecast(lat, lon, start, end, List.of("shortwave_radiation"));
}
/** 灌区模型所需全部字段 */
public static OpenMeteoResponse getIrrigationWeather(
double lat, double lon, LocalDateTime start, LocalDateTime end) throws Exception {
List<String> vars = List.of(
"temperature_2m",
"precipitation",
"relative_humidity_2m",
"wind_speed_10m",
"wind_direction_10m",
"surface_pressure",
"shortwave_radiation"
);
return getForecast(lat, lon, start, end, vars);
}
/** 土壤含水量 */
public static OpenMeteoResponse getSoilMoistureForecast(
double lat, double lon, LocalDateTime start, LocalDateTime end) throws Exception {
List<String> vars = List.of(
"soil_moisture_0_1cm",
"soil_moisture_1_3cm",
"soil_moisture_3_9cm",
"soil_moisture_9_27cm",
"soil_moisture_27_81cm"
);
return getForecast(lat, lon, start, end, vars);
}
// ============================================================
// 数据计算能力扩展
// ============================================================
/** 小时级 ET0(FAO56 简化) */
public static List<Double> calculateET0(OpenMeteoResponse resp) {
List<Double> list = new ArrayList<>();
if (resp.getHourly() == null) return list;
var temp = resp.getHourly().getTemperature_2m();
var wind = resp.getHourly().getWind_speed_10m();
var rh = resp.getHourly().getRelative_humidity_2m();
for (int i = 0; i < temp.size(); i++) {
double t = temp.get(i);
double u = wind.get(i);
double h = rh.get(i);
double et0 = 0.0023 * (t + 17.8) * Math.sqrt(Math.max(u, 0.1)) * (1 - h / 100.0);
list.add(et0);
}
return list;
}
/** 逐日 ET0(按小时求和) */
public static Map<String, Double> calculateDailyET0(OpenMeteoResponse resp) {
Map<String, Double> map = new LinkedHashMap<>();
if (resp.getHourly() == null) return map;
List<String> times = resp.getHourly().getTime();
List<Double> et0s = calculateET0(resp);
for (int i = 0; i < times.size(); i++) {
String date = times.get(i).substring(0, 10);
map.merge(date, et0s.get(i), Double::sum);
}
return map;
}
/** 有效降雨 */
public static List<Double> calculateEffectiveRain(OpenMeteoResponse resp, double coef) {
List<Double> result = new ArrayList<>();
if (resp.getHourly() == null) return result;
for (Double r : resp.getHourly().getPrecipitation()) {
result.add(r * coef);
}
return result;
}
/** 逐日降雨累计 */
public static Map<String, Double> calculateDailyRain(OpenMeteoResponse resp) {
Map<String, Double> map = new LinkedHashMap<>();
if (resp.getHourly() == null) return map;
List<String> times = resp.getHourly().getTime();
List<Double> rain = resp.getHourly().getPrecipitation();
for (int i = 0; i < times.size(); i++) {
String date = times.get(i).substring(0, 10);
map.merge(date, rain.get(i), Double::sum);
}
return map;
}
/**
* 组装小时级天气对象
*/
public static List<Map<String, Object>> assembleHourlyWeather(OpenMeteoResponse resp) {
List<Map<String, Object>> list = new ArrayList<>();
if (resp.getHourly() == null) return list;
List<String> times = resp.getHourly().getTime();
var temp = resp.getHourly().getTemperature_2m();
var rain = resp.getHourly().getPrecipitation();
var wind = resp.getHourly().getWind_speed_10m();
for (int i = 0; i < times.size(); i++) {
Map<String, Object> item = new HashMap<>();
item.put("time", times.get(i));
item.put("temp", temp != null ? temp.get(i) : null);
item.put("rain", rain != null ? rain.get(i) : null);
item.put("wind", wind != null ? wind.get(i) : null);
list.add(item);
}
return list;
}
public static void main(String[] args) {
try {
OpenMeteoResponse soil = OpenMeteoUtils.getSoilMoistureForecast(
31.0, 121.0,
LocalDateTime.now(),
LocalDateTime.now().plusDays(1)
);
System.out.println(soil.getHourly());
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
✨ 五、关键方法介绍
1. 获取降雨预报
OpenMeteoResponse resp = OpenMeteoUtils.getRainfall( 31.0, 121.0, LocalDateTime.now(), LocalDateTime.now().plusDays(3) );
2. 获取温度、湿度、气压
OpenMeteoUtils.getTemperatureForecast(...); OpenMeteoUtils.getHumidityForecast(...); OpenMeteoUtils.getPressureForecast(...);
3. 获取灌区模型所需全部气象要素
OpenMeteoResponse resp = OpenMeteoUtils.getIrrigationWeather( lat, lon, start, end );
此接口包括:
-
温度
-
相对湿度
-
降雨量
-
风速
-
风向
-
气压
-
短波辐射(太阳辐射)
非常适合模型类项目。
✨ 六、计算类方法
1. ET₀(小时级,FAO56 简化)
List<Double> et0List = OpenMeteoUtils.calculateET0(resp);
2. 逐日 ET₀ 累计
Map<String, Double> et0Daily = OpenMeteoUtils.calculateDailyET0(resp);
3. 日降雨累计
Map<String, Double> rainDaily = OpenMeteoUtils.calculateDailyRain(resp);
4. 有效降雨
List<Double> effRain = OpenMeteoUtils.calculateEffectiveRain(resp, 0.8);
✨ 七、应用场景
✔ 智慧灌区 / 水利大屏
支持制定用水方案、灌溉调度等。
✔ 农业模型(需水量、肥水管理)
ET₀ + 降雨 + 土壤湿度 = 完整作物模型数据源。
✔ 物联网气象站补足
本地站点 + 云端预报效果更稳。
✔ 决策支持系统
降雨预报 + 风速风向 = 施工安全、户外作业预警等。
更多推荐



所有评论(0)