历史天气数据BigQuery

时间:2017-07-27 05:39:15

标签: google-bigquery

我尝试在给定日期之前获取7天的天气数据并接近某些坐标(lat,lon)。像半径20公里的东西。如果有多个电台,我可能想要按天分组数据。

有没有办法直接使用BigQuery计算所有这些?为了测试,我计算了最小和最大坐标并创建了以下查询

SELECT
  *
FROM
  [bigquery-public-data:noaa_gsod.gsod2016] a
JOIN
  [bigquery-public-data:noaa_gsod.stations] b
ON
  a.stn=b.usaf
  AND a.wban=b.wban
WHERE
  (b.lat >= 46.248332
    AND b.lat <= 47.147654)
  AND (b.lon >= 5.689853
    AND b.lon <= 7.001115)
  AND a.mo='03'

我对查询还不是很满意

  • 它不是每天通过多个站平均它选择所有 给定月份的数据。
  • 如何在特定日期过后7天?
  • 可以通过查询直接计算max和min lat / lon吗?
  • 通常它找不到任何数据,因为很可能是半径 20公里太小,找不到车站。如何将查询修改为 如果在半径20公里范围内找不到它,找到最近的车站?
  • 我能获得更好,免费的历史天气数据吗?

这就是我计算最小最大坐标的方法:

maxLat = lat + math.degrees(searchRadius / earthRadius)
minLat = lat - math.degrees(searchRadius / earthRadius)
maxLon = lon + math.degrees(searchRadius / earthRadius) / math.cos(math.radians(lat))
minLon = lon - math.degrees(searchRadius / earthRadius) / math.cos(math.radians(lat))

2 个答案:

答案 0 :(得分:2)

这是我能提出的最佳解决方案:

#standardSQL
CREATE TEMP FUNCTION distance(lat1 FLOAT64, lat2 FLOAT64, lon1 FLOAT64, lon2 FLOAT64) AS((
WITH data AS(
SELECT POW(SIN((ACOS(-1) / 180 * (lat1 -lat2)) / 2), 2) + COS(ACOS(-1) / 180 * (lat1)) * COS(ACOS(-1) / 180 * (lat2)) * POW(SIN((ACOS(-1) / 180 * (lon1 -lon2)) / 2), 2) a
)
SELECT 6371 * 2 * ATAN2(SQRT((SELECT a FROM data)), SQRT(1 - (SELECT a FROM data)))
));

WITH temperature_data AS(
SELECT
  CONCAT(year, mo, da) date,
  temp,
  b.lat lat,
  b.lon lon
FROM `bigquery-public-data.noaa_gsod.gsod2016` a
JOIN `bigquery-public-data.noaa_gsod.stations` b
ON a.stn = b.usaf AND a.wban = b.wban
WHERE concat(year, mo, da) BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(PARSE_DATE('%Y%m%d', '20160725'), INTERVAL 7 DAY)) AND '20160725'
)

SELECT
  date,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS std_temp) data_20km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS std_temp) data_50km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS std_temp) data_100km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS std_temp) data_200km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS std_temp) data_500km
FROM temperature_data t
WHERE
distance(t.lat, 10.1, t.lon, 10.2) < 2000
GROUP BY date
ORDER BY date

我会尝试解释你的问题:

  

如何在特定日期过后7天?

在查询temperature_data内,注意WHERE子句的条件是:

WHERE concat(year, mo, da) BETWEEN FORMAT_DATE('%Y%m%d', DATE_SUB(PARSE_DATE('%Y%m%d', '20160725'), INTERVAL 7 DAY)) AND '20160725'

这是从给定日期开始选择过去7天的地方。您可以通过更改值'20160725'来选择要分析的日期。

  

可以通过查询直接计算max和min lat / lon吗?

是。我想你的意思是,如果可以选择给定范围内的空间点(例如20公里)。 一种方法是定义一个临时函数来计算所需点和站点之间的距离,这在查询中表示为:

CREATE TEMP FUNCTION distance(lat1 FLOAT64, lat2 FLOAT64, lon1 FLOAT64, lon2 FLOAT64) AS((
WITH data AS(
SELECT POW(SIN((ACOS(-1) / 180 * (lat1 -lat2)) / 2), 2) + COS(ACOS(-1) / 180 * (lat1)) * COS(ACOS(-1) / 180 * (lat2)) * POW(SIN((ACOS(-1) / 180 * (lon1 -lon2)) / 2), 2) a
)
SELECT 6371 * 2 * ATAN2(SQRT((SELECT a FROM data)), SQRT(1 - (SELECT a FROM data)))
));

你可以玩并测试这个功能,例如:

SELECT distance(50, 60, 30, 10) # result is ~ 1680km

此处使用此功能:

WHERE
distance(t.lat, 10.1, t.lon, 10.2) < 2000

过滤距离(10.1°,10.2°)更远2000公里以上的点。在您的查询中,您可以选择不同的输入值而不是(10.1°,10.2°)。

  

通常它找不到任何数据,因为很可能是半径   20公里太小,找不到车站。如何将查询修改为   如果在半径20公里范围内找不到它,找到最近的车站?

一种可能的解决方案是同时查询几个不同的距离:

SELECT
  date,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 20, temp, NULL)) AS std_temp) data_20km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 50, temp, NULL)) AS std_temp) data_50km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 100, temp, NULL)) AS std_temp) data_100km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 200, temp, NULL)) AS std_temp) data_200km,
  STRUCT(AVG(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS avg_temp, STDDEV_SAMP(IF(distance(t.lat, 10.1, t.lon, 10.2) < 500, temp, NULL)) AS std_temp) data_500km
FROM temperature_data t
WHERE
distance(t.lat, 10.1, t.lon, 10.2) < 2000
GROUP BY date

请注意,此查询正在提取从输入点(10.1°,10.2°)到2000km之外的站点。然后应用滤波器来选择20km,50km,100km,200km和500km范围内的点。

您可以根据需要更改这些值。如果你想从另一个点获得平均温度,比如说(40°,30°),只需将值(10.1,10.2)改为(40,30),你就可以了。此外,如果您希望距离此点不同,则可以将表达式IF(distance(t.lat, 10.1, t.lon, 10.2) < 200更改为更符合您需求的范围。

请注意WHERE子句的条件为:

distance(t.lat, 10.1, t.lon, 10.2) < 2000

因此,这将过滤掉距离点(10.1,10.2)更远的所有站点超过2000公里。您也可以根据需要更改此值。

关于这一点的最后说明:我还带来了STDDEV_SAMP standard deviation of a sampling。这可能对您有一定价值,并且它可以让您了解平均值在均值周围散布的程度(通过采样数据大小效果校正)。如果我们不知道我们与正确值的接近程度,那么平均值本身就没那么有价值。

  

我能获得更好,免费的历史天气数据吗?

不知道。希望这个公共数据集对您来说足够好。

答案 1 :(得分:0)

根据您提供的信息,我不确定您是否可以在查询中计算最大/最小数据。在Legacy SQL中工作我可能会尝试嵌套多个查询,或者加入计算它们的查询,或者两者兼而有之。

您可能还可以在必要时编写一些可以调整搜索查询的内容,但我还没有得到您已经足够的结构来撰写建议。

对于其他问题:

获得平均值 - 而不是使用*来调用您必须单独调用哪些列进行平均以及忽略或分组的所有内容。

选择特定日期的过去7天 - 非常不幸的是,它似乎不是时间戳列,因此您必须强制使用。

在LegacySQL中我会写这样的东西:

SELECT dte, avg_temp, avg_cnt_temp
FROM 
(SELECT CAST(CONCAT(a.year, '-', a.mo, '-', a.da) AS timestamp) AS dte,
/* This is calling the separate year, month, and day strings as a 
datetime funtion so I can use date_add later */ 
AVG(a.temp) AS avg_temp, AVG(a.count_temp) AS avg_cnt_temp /* You'll 
want to include all of the data you're wanting to call here, I 
only tested with these two */
FROM [bigquery-public-data:noaa_gsod.gsod2016] AS a
JOIN [bigquery-public-data:noaa_gsod.stations] AS b
ON a.stn=b.usaf AND a.wban=b.wban
GROUP BY dte, mo, da)
WHERE dte >= (DATE_ADD('2016-12-31 00:00:00', -7, "DAY")) AND dte <= 
TIMESTAMP('2016-12-31 00:00:00') /* replace with your date */

我认为在标准SQL中你不会以同样的方式嵌套。

如果要跨站等组合数据,请不要呼叫电台标识符