quarta-feira, 24 de agosto de 2011

Função "aggregate" para cálcular mediana no PostgreSQL

O Postgres não tem uma função que calcule a mediana dos valores de um determinada coluna, ou seja, fazer algo do tipo:

SELECT median(col) FROM tabela;

Mas em http://wiki.postgresql.org/wiki/Aggregate_Median está o código necessário para isso. É pequeno e não precisa entender nada dele pra utilizar. Basta copiar e executar no seu console dentro do banco de dados onde você quer que a função funcione. Se essa função tiver que funcionar em diversos bancos, é preciso executar esse código em cada um deles.

CREATE OR REPLACE FUNCTION _final_median(numeric[])
RETURNS numeric AS
$$
SELECT AVG(val)
FROM (
SELECT val
FROM unnest($1) val
ORDER BY 1
LIMIT 2 - MOD(array_upper($1, 1), 2)
OFFSET CEIL(array_upper($1, 1) / 2.0) - 1
) sub;
$$
LANGUAGE 'sql' IMMUTABLE;

CREATE AGGREGATE median(numeric) (
SFUNC=array_append,
STYPE=numeric[],
FINALFUNC=_final_median,
INITCOND='{}'
);

segunda-feira, 15 de agosto de 2011

Calculando zoom automático em mapas

Como não consegui fazer o Open Layers funcionar passando direto a resolução do mapa, precisei calcular o zoom de maneira dinâmica.

Queria que meu mapa aparecesse em uma janela de largura 600px e altura 430px, com o zoom que fizesse uma certa feature aproveitar esse espaço da melhor maneira, ficando totalmente visível nessa área.

Encontrei a seguinte fórmula:
Map resolution = 156543.04 meters/pixel * cos(latitude) / (2 ^ zoomlevel)

Fui jogando as coisas de um lado pro outro:
(2 ^ zoomlevel) = 156543.04 meters/pixel * cos(latitude)/Map resolution

E enfim, considerando que se 2^x = a, então x = (ln a/ln 2):
zoomlevel = ln(156543.04 meters/pixel * cos(latitude)/Map resolution)/ln 2

Como latitude, usei a função st_ymin (no meu caso, seria praticamente igual a usar o st_ymax) do PostGIS, passando a feature como parâmetro. Para calcular a resolução, tive que descobrir se a feição era maior no eixo x ou no eixo y usando a seguinte query:
select st_distance_sphere(st_makepoint(st_xmax(the_geom), st_ymin(the_geom)), st_makepoint(st_xmin(the_geom), st_ymin(the_geom))) as dist_x,
st_distance_sphere(st_makepoint(st_xmin(the_geom), st_ymax(the_geom)), st_makepoint(st_xmin(the_geom), st_ymin(the_geom))) as dist_y
from <tabela> where <cláusula where>


Como já sei o tamanho da janela onde ficará o mapa (600 x 430), então se a feature é maior no eixo x, a resolução é dist_x/600. Se a feature é maior no eixo y, a resolução é dist_y/430.

Encontrei a fórmula para calcular o zoom em:
http://msdn.microsoft.com/en-us/library/aa940990.aspx

Obrigado ao Cristiano Pereira da Silva (ardagh) pela ajuda com a matemática.