Postgres集结函数,用于计算风速(矢量幅值)和风向(矢量方向)的矢量平均值

时间:2019-02-22 14:57:17

标签: sql postgresql aggregate-functions

我有一个包含两列wind_speedwind_direction的表。我想要一个自定义的聚集函数,该函数将返回平均值wind_speedwind_directionwind_speedwind_direction共同定义了一个向量,其中wind_speed是向量的大小,wind_direction是向量的方向。 avg_wind_direction函数应返回平均值wind_speed作为幅度,wind_direction作为平均值矢量的方向。

SELECT avg_wind_direction(wind_speed, wind_direction)
FROM sometable
GROUP BY location;

相关问题:Custom PostgreSQL aggregate for circular average

2 个答案:

答案 0 :(得分:1)

因此,我已经能够创建一个进行向量平均的聚集函数。假设矢量与极坐标不同,在极坐标中,角度在度中。

DROP AGGREGATE IF EXISTS vector_avg(float, float) CASCADE;
DROP TYPE IF EXISTS vector_sum CASCADE;
DROP TYPE IF EXISTS avg_vector CASCADE;

CREATE TYPE vector_sum AS (x float, y float, count int);
CREATE TYPE avg_vector AS (magnitude float, direction float);

CREATE OR REPLACE FUNCTION sum_vector (vectors vector_sum, magnitude float, direction float)
  RETURNS vector_sum LANGUAGE sql STRICT AS
'SELECT vectors.x + (magnitude * cos(direction * (pi() / 180))), vectors.y + (magnitude * sin(direction  * (pi() / 180))), vectors.count + 1';

CREATE OR REPLACE FUNCTION avg_vector_finalfunc(vectors vector_sum) RETURNS avg_vector AS
$$
DECLARE
        x float;
        y float;
BEGIN
    BEGIN
        IF vectors.count = 0 THEN
            RETURN (NULL, NULL)::avg_vector;
        END IF;

        x := (vectors.x/vectors.count); 
        y := (vectors.y/vectors.count);

        -- This means the vector is null vector
        -- Please see: https://math.stackexchange.com/a/3682/10842
        IF x = 0 OR y = 0 THEN
            RETURN (0, 0)::avg_vector;
        END IF;

        RETURN (sqrt(power(x, 2) + power(y, 2)), atan(y/x) * (180 / pi()))::avg_vector;
    EXCEPTION WHEN others THEN 
        RETURN (NULL, NULL)::avg_vector;
    END;
END;
$$
LANGUAGE 'plpgsql'
RETURNS NULL ON NULL INPUT;

CREATE AGGREGATE vector_avg (float, float) (
   sfunc     = sum_vector
 , stype     = vector_sum
 , finalfunc = avg_vector_finalfunc
 , initcond  = '(0.0, 0.0, 0)'
);

测试:

DROP TABLE t;
CREATE TEMP TABLE t(speed float, direction float);
INSERT INTO t VALUES (23, 334), (20, 3), (340, 67);

测试:

SELECT (vector_avg(speed, direction)).magnitude AS speed, (vector_avg(speed, direction)).direction AS direction FROM t;

结果:

+-----------------+-------------------+
| speed           | direction         |
+=================+===================+
| 108.44241888507 | 0.972468335643555 |
+-----------------+-------------------+

删除所有行:

DELETE FROM t;
SELECT (vector_avg(speed, direction)).magnitude AS speed, (vector_avg(speed, direction)).direction AS direction FROM t;

结果:

+---------+-------------+
| speed   | direction   |
+=========+=============+
| <null>  | <null>      |
+---------+-------------+

答案 1 :(得分:1)

首先抱歉,如果我违反这里的任何张贴规则,第一次张贴者以及所有这些内容。

我的diy气象站希望将以上答案与timescaledb一起添加到postgres中,但事实证明该功能不是并行安全的。同样,使用atan不会产生正确的答案。

所以这是我的修改版本,我认为应该是并行安全的,而应使用atan2。

DROP AGGREGATE IF EXISTS vector_avg(float, float) CASCADE;
DROP TYPE IF EXISTS vector_sum CASCADE;
DROP TYPE IF EXISTS avg_vector CASCADE;

CREATE TYPE vector_sum AS (x float, y float, count int);
CREATE TYPE avg_vector AS (magnitude float, direction float);

CREATE OR REPLACE FUNCTION sum_vector (vectors vector_sum, magnitude float, direction float)
  RETURNS vector_sum LANGUAGE sql PARALLEL SAFE STRICT AS
'SELECT vectors.x + (magnitude * cos(radians(direction))), vectors.y + (magnitude * sin(radians(direction))), vectors.count + 1';

CREATE OR REPLACE FUNCTION combine_sum (part1 vector_sum , part2 vector_sum)
  RETURNS vector_sum LANGUAGE sql PARALLEL SAFE STRICT AS
'SELECT (part1.x+part2.x)/2,(part1.y+part2.y)/2,part1.count+part2.count';

CREATE OR REPLACE FUNCTION avg_vector_finalfunc(vectors vector_sum)
RETURNS avg_vector
AS
$$
DECLARE
        x float;
        y float;
        d float;
BEGIN
    BEGIN
        IF vectors.count = 0 THEN
            RETURN (NULL, NULL)::avg_vector;
        END IF;

        x := (vectors.x/vectors.count);
        y := (vectors.y/vectors.count);

        -- This means the vector is null vector
        -- Please see: https://math.stackexchange.com/a/3682/10842
        IF x = 0 OR y = 0 THEN
            RETURN (0, 0)::avg_vector;
        END IF;

         d:=degrees(atan2(y,x));

        -- atan2 returns negative result for angles > 180

        IF d < 0 THEN
          d := d+360;
        END IF;

        RETURN (sqrt(power(x, 2) + power(y, 2)), d )::avg_vector;
    EXCEPTION WHEN others THEN
        RETURN (NULL, NULL)::avg_vector;
    END;
END;
$$
LANGUAGE 'plpgsql'
PARALLEL SAFE
RETURNS NULL ON NULL INPUT;

CREATE AGGREGATE vector_avg (float, float) (
   sfunc     = sum_vector
 , stype     = vector_sum
 , combinefunc = combine_sum
 , finalfunc = avg_vector_finalfunc
 , initcond  = '(0.0, 0.0, 0)'
 , PARALLEL  = SAFE

通过一个非常小的样本进行测试:

psql -d weather -c "select * from windavgtest;"
             time              | direction | speed 
-------------------------------+-----------+-------
 2019-08-01 16:51:53.199357+00 |       170 |     1
 2019-08-01 16:51:54.388392+00 |       170 |     1
 2019-08-01 16:51:55.335034+00 |       170 |     1
 2019-08-01 16:51:56.362812+00 |       170 |     1
 2019-08-01 16:52:07.191919+00 |       190 |     1
 2019-08-01 16:52:08.250756+00 |       190 |     1
 2019-08-01 16:52:09.193265+00 |       190 |     1
 2019-08-01 16:52:10.224283+00 |       190 |     1
(8 rows)

产量:

psql -d weather -c  "select round((vector_avg(speed, direction)).direction) AS wdirection from windavgtest;
"
 wdirection 
------------
        180
(1 row)