Fórmula de Haversine em Clojure

12 respostas Resolvido
felipeguerra

Boa tarde!

Quanto tempo que não acesso e nem posto nada nesse fórum que tanto me ajudou!

Como sempre estou aprendendo uma linguagem nova, vou voltar a solicitar ajuda…

Alguém poderia me ajudar com o paradigma funcional? Fiz um código para uma funcionalidade, mas não sei se está utilizando os conceitos e APIs da linguagem corretamente:

(defn calculate-distance
  "Calcula distância entre duas coordenadas geográficas"
  ([] nil)
  ([x] nil)
  ([source to]
    (def raio 6372.795477598)
    (let [convert-to-rad (fn[value] (/ (* value Math/PI) 180))
         delta-latitude (fn [ltdSource ltdTo] (convert-to-rad (- ltdTo ltdSource)))
         delta-longitude (fn [lgtSource lgtTo] (convert-to-rad (- lgtTo lgtSource)))
         a (fn [delta-lgt delta-ltd source-ltd to-ltd] (+ (Math/pow (Math/sin (/ delta-ltd 2)) 2) 
                                                          (* (Math/cos source-ltd) (Math/cos to-ltd) (Math/sin (/ delta-lgt 2)) (Math/sin (/ delta-lgt 2)) ))
                                                        )
         b (fn [value] (* 2 (Math/atan2 (Math/sqrt value) (Math/sqrt (- 1 value)))))
         c (fn [value] (* (* raio value) 1000))
         ]
     (if (or (not (and (map? source) (map? to))) 
             (not (and (and (and (contains? to :to/lgt) (contains? to :to/ltd)) (contains? source :source/lgt)) (contains? source :source/ltd)))
          )
      (throw (new Exception "Invalid Parameters"))
      ;faz o cálculo
      (c (b (a (delta-longitude (val (find source :source/lgt)) (val (find to :to/lgt)))
               (delta-latitude (val (find source :source/ltd)) (val (find to :to/ltd)))
               (convert-to-rad (val (find source :source/ltd)))
               (convert-to-rad (val (find to :to/ltd))))))
     )
   )
  )
)

12 Respostas

pfk66
1- Não se usa def dentro da função. Apenas let.

2- Não tem pq lançar exceção se os parâmetros estiverem inválidos.

3- Nao tem porque criar funções que  existem na biblioteca Math (toRadians/toDegrees)

4- Nao tem porque criar funções pra subtrair 2 números (delta-lat/delta-long)

5- Nao tem porque criar funções que serão usadas apenas uma vez (a, b, c).

6- Se for criar funções, evite criar funções anônimas se ela tiver mais de uma linha.
felipeguerra

Muito obrigado pelas respostas.

Uma dúvida: essa fórmula de Haversine é razoavelmente grande, eu deveria deixá-la num único bloco dentro dessa função que criei, mesmo que cause dificuldade pra leitura?

pfk66

Se você tirar todos esses fns que são completamente desnecessários, e o método convert-rad que já existe na biblioteca java.Math, já vai eliminar varios parenteses e ficar bem mais simples. :slight_smile:

Se usar desestruturação associativa pode eliminar tb o if. Ao invés de verificar se é map, e se possui as coordenadas, você especifica um valor default caso o valor passado como parâmetro não consiga ser desestruturizado.

(defn distance [{lat1 :latitude lon1 :longitude :or {lat1 0.0 lon1 0.0}} {lat2 :latitude lon2 :longitude :or {lat2 0.0 lon2 0.0}}]
...
)

Usei 0.0 como default caso não tenha alguma das coordenadas, mas poderia ter usado qualquer outro valor.

Com essas modificações só vai sobrar a essência da função, que na verdade é bem simples, calcular o delta das coordenadas, aplica tudo numa formula e retorna o resultado.

felipeguerra

Uma versão intermediária, para que eu possa chegar num código Clojurable

(ns br.com.reactive-poc.calc)

(def raio 6372.795477598)

(defn calculate-distance
"Calcula distância entre duas coordenadas geográficas"
([source to]
  {:pre [every? (every-pred map? #(contains? % :lgt) #(contains? % :ltd)) [source to]]}
  (let [delta-latitude (Math/toRadians (- (:source/ltd source) (:to/ltd to)))
        delta-longitude (Math/toRadians (- (:source/lgt source) (:to/lgt to)))
        ]
    (* (* raio (* 2 (Math/atan2 (Math/sqrt (+ (Math/pow (Math/sin (/ delta-latitude 2)) 2) 
                                              (* (Math/cos (Math/toRadians (:source/ltd source))) (Math/cos (Math/toRadians (:to/ltd to))) (Math/sin (/ delta-longitude 2)) (Math/sin (/ delta-longitude 2))))) 
                                (Math/sqrt (- 1 (+ (Math/pow (Math/sin (/ delta-latitude 2)) 2) 
                                                   (* (Math/cos (Math/toRadians (:source/ltd source))) (Math/cos (Math/toRadians (:to/ltd to)) ) (Math/sin (/ delta-longitude 2)) (Math/sin (/ delta-longitude 2))))))))) 1000)
    )
  )
)
pfk66

Legal. Mas lembre que o tamanho recomendado de cada linha em Lisp é de 80 caracteres.

felipeguerra

Então, você chegou aonde eu queria: qual é a estratégia que um formatador de código adotaria?

pfk66

O que seria um formatador de código?

felipeguerra

Reformulando: como deve ser formatado/apresentado códigos em Clojure que, na minha percepção, são confusos com o uso desses parênteses que tendem ao infinito?

pfk66
Solucao aceita

Normal, que nem texto e cada função seria um parágrafo.
A única recomendação geral que conheço é usar 80 caracteres max por linha porque melhora muito a legibilidade do código.

pfk66

Nem tinha visto que você identa os parentes finais. Você editou o código?
Se não esta provado, com o tempo a pessoa deixa de perceber os parenteses. :slight_smile:

)
  )
)

O mais comum é fechar tudo junto mesmo.

(defn calculate-distance
"Calcula distância entre duas coordenadas geográficas"
([source to]
  {:pre [every? (every-pred map? #(contains? % :lgt) #(contains? % :ltd)) [source to]]}
  (let [delta-latitude (Math/toRadians (- (:source/ltd source) (:to/ltd to)))
        delta-longitude (Math/toRadians (- (:source/lgt source) (:to/lgt to)))
        ]
    (* (* raio (* 2 (Math/atan2 (Math/sqrt (+ (Math/pow (Math/sin (/ delta-latitude 2)) 2) 
                                              (* (Math/cos (Math/toRadians (:source/ltd source))) (Math/cos (Math/toRadians (:to/ltd to))) (Math/sin (/ delta-longitude 2)) (Math/sin (/ delta-longitude 2))))) 
                                (Math/sqrt (- 1 (+ (Math/pow (Math/sin (/ delta-latitude 2)) 2) 
                                                   (* (Math/cos (Math/toRadians (:source/ltd source))) (Math/cos (Math/toRadians (:to/ltd to)) ) (Math/sin (/ delta-longitude 2)) (Math/sin (/ delta-longitude 2))))))))) 1000))))

ps: paredit tb ajuda com os parenteses se você usa emacs.

felipeguerra

Meu caro, dá uma olhada no produto final dos meus estudos: https://github.com/felipeguerra19/clojure-websocket-react-geolocation

pfk66

O que é um serviço “reativo”?

Nunca ouvi esse termo antes no contexto de clojure, talvez clojurescript… não tenho certeza.

Criado 23 de fevereiro de 2017
Ultima resposta 28 de mar. de 2017
Respostas 12
Participantes 2