乐于分享
好东西不私藏

Java 下载 ERA5 数据:HttpClient + Gson,企业级开发者的选择

Java 下载 ERA5 数据:HttpClient + Gson,企业级开发者的选择

Python 统治了气象科研圈,但企业级系统大量运行在 Java 上。风电监控、光伏运维、农业平台、保险系统——很多需要气象数据的后端服务都是 Java 写的。今天用 Java 来获取 ERA5 数据。


核心思路

Java 调用 ERA5 数据最直接的方式就是 HTTP API

Java 应用 → HTTP GET → 镜像地球 API → JSON 响应 → 解析为 Java 对象

不需要安装复杂的科学计算库,用标准 HTTP 客户端 + JSON 解析就够了。


准备工作

依赖(Maven)

<dependencies>    <!-- Java 11+ 自带 HttpClient,无需额外依赖 -->    <!-- JSON 解析 -->    <dependency>        <groupId>com.google.code.gson</groupId>        <artifactId>gson</artifactId>        <version>2.10.1</version>    </dependency>    <!-- CSV 导出(可选) -->    <dependency>        <groupId>com.opencsv</groupId>        <artifactId>opencsv</artifactId>        <version>5.8</version>    </dependency></dependencies>

数据模型

public class HourlyData {    private List<String> time;    private List<Double> temperature_2m;    private List<Double> precipitation;    private List<Double> wind_speed_10m;    // getters...}

方法一:基础调用(HttpClient)

最简单的单次查询,Java 11+ 原生 HttpClient:

import java.net.URI;import java.net.http.*;import java.net.http.HttpResponse.BodyHandlers;public class Era5Client {    private static final String BASE_URL =        "https://api.mirror-earth.com/v1/archive";    private final String apiKey;    public Era5Client(String apiKey) {        this.apiKey = apiKey;    }    public String fetchRaw(double lat, double lon,                          String start, String end,                          String hourlyVars) throws Exception {        String url = BASE_URL + "?"            + "latitude=" + lat            + "&longitude=" + lon            + "&hourly=" + hourlyVars            + "&start_date=" + start            + "&end_date=" + end            + "&apikey=" + apiKey;        HttpClient client = HttpClient.newHttpClient();        HttpRequest request = HttpRequest.newBuilder()            .uri(URI.create(url))            .GET()            .build();        HttpResponse<String> response = client.send(            request, BodyHandlers.ofString());        if (response.statusCode() == 200) {            return response.body();        } else {            throw new RuntimeException(                "API 请求失败: " + response.statusCode()                + " " + response.body());        }    }    public static void main(String[] args) throws Exception {        Era5Client client = new Era5Client("your_api_key");        String json = client.fetchRaw(            39.9, 116.4,            "2023-06-01", "2023-06-30",            "temperature_2m,precipitation"        );        System.out.println("获取到 " + json.length() + " 字节数据");    }}

方法二:带 JSON 解析(Gson)

import com.google.gson.Gson;import com.google.gson.JsonObject;import com.google.gson.JsonArray;import java.util.*;public class Era5Parser {    public static List<Map<String, Object>> parseHourly(String json) {        Gson gson = new Gson();        JsonObject root = gson.fromJson(json, JsonObject.class);        JsonObject hourly = root.getAsJsonObject("hourly");        List<String> time = parseList(hourly, "time");        List<Double> temp = parseDoubleList(hourly, "temperature_2m");        List<Map<String, Object>> records = new ArrayList<>();        for (int i = 0; i < time.size(); i++) {            Map<String, Object> record = new LinkedHashMap<>();            record.put("time", time.get(i));            record.put("temperature_2m", temp.get(i));            records.add(record);        }        return records;    }    public static void main(String[] args) throws Exception {        Era5Client client = new Era5Client("your_key");        String json = client.fetchRaw(            39.9, 116.4,            "2023-06-01", "2023-06-30",            "temperature_2m,precipitation,wind_speed_10m"        );        List<Map<String, Object>> data = parseHourly(json);        System.out.println("共 " + data.size() + " 条逐小时数据");        // 找出最高温时刻        data.stream()            .maxBy(Comparator.comparingDouble(                m -> (Double) m.get("temperature_2m")))            .ifPresent(max -> System.out.println(                "最高温: " + max.get("time") + " → "                + max.get("temperature_2m") + "°C"));    }}

方法三:批量查询多城市

import java.util.concurrent.*;public class BatchEra5Fetcher {    private final Era5Client client;    private final ExecutorService executor;    public BatchEra5Fetcher(String apiKey, int threads) {        this.client = new Era5Client(apiKey);        this.executor = Executors.newFixedThreadPool(threads);    }    public Map<String, String> fetchCities(            List<String[]> cities, String start, String end)            throws Exception {        Map<String, Future<String>> futures = new LinkedHashMap<>();        for (String[] city : cities) {            String name = city[0];            double lat = Double.parseDouble(city[1]);            double lon = Double.parseDouble(city[2]);            futures.put(name, executor.submit(() ->                client.fetchRaw(lat, lon, start, end,                    "temperature_2m,precipitation")));        }        Map<String, String> results = new LinkedHashMap<>();        for (var entry : futures.entrySet()) {            results.put(entry.getKey(), entry.getValue().get());            System.out.println("✓ " + entry.getKey() + " 完成");        }        return results;    }    public void shutdown() {        executor.shutdown();    }    public static void main(String[] args) throws Exception {        var fetcher = new BatchEra5Fetcher("your_key", 5);        List<String[]> cities = List.of(            new String[]{"北京", "39.9", "116.4"},            new String[]{"上海", "31.2", "121.5"},            new String[]{"广州", "23.1", "113.3"},            new String[]{"成都", "30.6", "104.1"},            new String[]{"武汉", "30.6", "114.3"}        );        Map<String, String> results = fetcher.fetchCities(            cities, "2023-06-01", "2023-06-30");        fetcher.shutdown();        System.out.println("全部完成,共 " + results.size() + " 个城市");    }}

方法四:导出 CSV

import com.opencsv.CSVWriter;import java.io.*;public class CsvExporter {    public static void export(List<Map<String, Object>> data,                             String filePath) throws IOException {        try (CSVWriter writer = new CSVWriter(                new FileWriter(filePath))) {            // 表头            String[] header = data.get(0).keySet()                .toArray(new String[0]);            writer.writeNext(header);            // 数据行            for (Map<String, Object> record : data) {                String[] row = record.values().stream()                    .map(Object::toString)                    .toArray(String[]::new);                writer.writeNext(row);            }        }        System.out.println("已导出 " + data.size()            + " 条数据到 " + filePath);    }}

方法五:Spring Boot 集成

企业项目中最常见的场景——把 ERA5 数据集成到 Spring Boot:

@Servicepublic class WeatherDataService {    @Value("${mirror-earth.api-key}")    private String apiKey;    private final RestTemplate restTemplate;    public WeatherDataService(RestTemplateBuilder builder) {        this.restTemplate = builder.build();    }    public Map<String, Object> getWeather(double lat, double lon,                                          String start, String end) {        String url = "https://api.mirror-earth.com/v1/archive?"            + "latitude=" + lat + "&longitude=" + lon            + "&hourly=temperature_2m,precipitation"            + "&start_date=" + start + "&end_date=" + end            + "&apikey=" + apiKey;        return restTemplate.getForObject(url, Map.class);    }}
@RestController@RequestMapping("/api/weather")public class WeatherController {    @Autowired    private WeatherDataService weatherService;    @GetMapping("/history")    public Map<String, Object> history(            @RequestParam double lat,            @RequestParam double lon,            @RequestParam String start,            @RequestParam String end) {        return weatherService.getWeather(lat, lon, start, end);    }}

对比:Java vs Python 获取 ERA5

维度
Java
Python
HTTP 调用
HttpClient(内置)
requests
JSON 解析
Gson / Jackson
内置 json
数据处理
需手动转换
pandas 开箱即用
科学计算
ND4J / Apache Commons
xarray / numpy
批量下载
线程池 + Future
asyncio / 多线程
部署
企业标准,性能强
脚本化,开发快
适合场景
后端服务、微服务
数据分析、科研

Java 的优势在于集成到现有企业系统——如果你的系统本身就是 Spring Boot / Java 生态,直接用 HTTP API 获取 ERA5 数据是最自然的选择。


完整项目模板

建议的项目结构:

era5-java-client/├── pom.xml├── src/main/java/com/example/era5/│   ├── Era5Client.java          # HTTP 客户端│   ├── Era5Parser.java          # JSON 解析│   ├── CsvExporter.java         # CSV 导出│   └── WeatherDataService.java  # Spring Boot 服务├── src/main/resources/│   └── application.properties   # API Key 配置└── src/test/java/    └── Era5ClientTest.java