quinta-feira, 28 de outubro de 2010

Função para Calcular a Distancia entre Duas Coordenadas no SQL Server (Transact-SQL)

                Em sistemas de logística temos sempre que calcular a distancia entre pontos e geralmente temos tabelas com endereço dos pontos ou as coordenadas do endereço, sempre que temos que saber a distancia usamos ferramentas de roteirização para calcular o trajeto e informar a distancia. Esse tipo de solução pode ter um custo alto de processamento, velocidade e financeiro já que algumas ferramentas cobram por acesso a sua base.
                Para evitar custos desnecessários podemos fazer este calculo no banco de dados, ao invés de usar a ferramenta de roteirização. A vantagem de usar esta abordagem é que mais rápido, podemos usar o comando order by (sem precisar recorrer a código para ordenar pelo mais próximo ou mais distante) e não tem custo, porem poderemos ter problemas se os pontos estiverem muito próximos já que o calculo será feito em linha reta sem levar em consideração o trajeto.
                Primeiro vamos criar a função para calcular a distancia entre dois pares de coordenadas, estes pares devem estar em graus decimais. Esta função é a mesma que eu criei no artigo Função em Delphi para Calcular a Distância Entre Dois Pares de Coordenadas (Latitude e Longitude) convertida nas funções do Transact-SQL.
                Segue o script para criar a função:
-- =============================================
-- Author:        Wagner Carmo da Silva
-- Create date:   20/10/2010 17:24
-- Description:   Calcula a distância em quilometros
-- =============================================
CREATE FUNCTION [fun_CalcDistancia] 
(
     @latIni float, -- Latitude do ponto inicial
     @lonIni float, -- Longitude do ponto inicial
     @latFim float,-- Latitude do ponto final
     @lonFim float -- Longitude do ponto final
)
RETURNS float
AS
BEGIN
     DECLARE @Result AS FLOAT
     DECLARE @arcoA AS FLOAT
     DECLARE @arcoB AS FLOAT
     DECLARE @arcoC AS FLOAT
     DECLARE @auxPI AS FLOAT

     SET @auxPi = Pi() / 180
     SET @arcoA = (@lonFim - @lonIni) * @auxPi
     SET @arcoB = (90 - @latFim) * @auxPi
     SET @arcoC = (90 - @latIni) * @auxPi
     SET @Result = Cos(@arcoB) * Cos(@arcoC) + Sin(@arcoB) * Sin(@arcoC) * Cos(@arcoA)
     SET @Result = (40030 * ((180 / Pi()) * Acos(@Result))) /360

     RETURN Round(@Result,2)
END
                Esta função retorna o valor em quilômetros arredondando duas casas, se precisar aumentar a precisão do calculo basta alterar este trecho do código RETURN Round(@Result,2) , número 2 é a quantidade de casas decimais depois da virgula basta aumentar este número para aumentar as casas, se quiser deixar com precisão total basta remover a função round (RETURN @Result).
                Agora vou mostrar um select que mostra uso dessa função :

SELECT
     cd_Empresa,
     nr_Latitude,
     nr_Longitude,
     fun_CalcDistancia(-23.479483, -46.749340, nr_Latitude, nr_Longitude) AS nr_Distancia
FROM tbl_Empresas
ORDER BY fun_CalcDistancia(-23.479483, -46.749340, nr_Latitude, nr_Latitude)

                Repare que eu usei a função até mesmo para ordenar o resultado, neste caso ele traz o mais próximo, para inverter isto basta usar o desc depois do order by.
                Importante é não esquecer que o calculo é em linha reta, isso deve ser usado quando os pontos tem uma boa distancia, ou seja, o caminho não vai interferir muito na distancia do ponto.

Referências:

terça-feira, 19 de outubro de 2010

Função Para Converter Graus Decimais em Graus Minutos e Segundos no Javascript

                Quem trabalha com mapas vive precisando fazer conversões de coordenadas, com o crescimento das ferramentas de mapas para Web essas conversões devem ser feitas no Javascript, abaixo segue a função que eu desenvolvi para exibir as coordenadas que a ferramenta de mapa retorna em graus para exibir em graus minutos e segundos:
function showCoordenadasMinutos(gDec, x) {
    var graus;
    var minutos;
    var aux;
    var segundos;
    var milisegundos;
    var direcao;

    // Separa os graus
    graus = parseInt(gDec);

    // Pega a fração dos graus e converte em minutos
    aux = (graus - gDec) * 60;
    minutos = parseInt(aux);

    // Pega a fração dos minutos e converte em segundos
    aux = (aux - minutos) * 60;
    segundos = parseInt(aux);

    // Pega a fração dos segundos e converte em milisegundos
    milisegundos = parseInt((aux - segundos) * 60);

    // Essa parte eu verifico se é o eixo X ou Y para substituir o simbolo de negativo  pelas iniciais de norte ou sul para o eixo Y, leste ou oeste para o eixo X
    if (x) {
        // Eixo X
        if (graus < 0)
            direcao = "O";
        else
            direcao = "L";
    } else {
        // Eixo Y
        if (graus < 0)
            direcao = "S";
        else
            direcao = "N";
    }
    // Devolvo a string formatada, a função Math.abs é para retornar o valor absoluto // (retirar o valor negativo) já que estou usando a notação norte, sul, leste ou oeste
    return Math.abs(graus) + "° " + minutos + "' " + segundos + "." + milisegundos + "'' " + direcao;
}

Como eu precisava criar uma função para exibir os dados para o usuário eu substitui o valor negativo pelas inicias dos pontos cardeais e coloquei as aspas simples, se quiser deixar no padrão NMEA basta juntar o grau o minuto colocar o ponto e juntar o segundo e o milissegundos.
Se quiser usar esta função para retornar um valor de ponto flutuante no padrão NMEA substitua o retorno da função pelo debaixo :

return ((graus * 100) + minutos) + (((segundos * 100) + milisegundos) / 10000);
 

E remova o parâmetro x da função e o trecho de código que verifica qual é a direção da coordenada.