Option Compare Database Option Explicit 'modVincenty Private Const PI = 3.14159265358979 Private Const EPSILON As Double = 0.000000000001 Public Function distVincenty(ByVal lat1 As Double, ByVal lon1 As Double, ByVal Lat2 As Double, ByVal Lon2 As Double) As Double '----------------------------------------------------------------------------------------------------------------------------------------------- ' Fuente : https://access-global.net/vba-google-maps-api-calcular-la-distancia-entre-dos-coordenadas-vincenty '----------------------------------------------------------------------------------------------------------------------------------------------- ' Título : distVincenty ' Autor original : Alba Salvá ' Fecha : 21/02/2020 ' Propósito : Conocer la distancia geodésica entre dos puntos especificados por latitud/longitud usando la ' fórmula inversa de Vincenty para elipsoides ' Retorno : devuelve la distancia en m (con una preción hasta milímetros) ' Argumento/s : La sintaxis del procedimiento o función consta del siguiente argumento: ' Parte Modo Descripción '----------------------------------------------------------------------------------------------------------------------------------------------- ' LatitudInicio Obligatorio Latitud del punto 1 ' LongitudInicio Obligatorio Longitud del punto 1 ' LatitudFin Obligatorio Latitud del punto 2 ' LongitudFin Obligatorio Longitud del punto s '----------------------------------------------------------------------------------------------------------------------------------------------- ' Mas información : el código ha sido adaptado a VBA del javascript publicado en: ' http://www.movable-type.co.uk/scripts/latlong-vincenty.html ' fórmula inversa de Vincenty - T Vincenty, "Direct and Inverse Solutions of Geodesics on the ' Ellipsoid with application of nested equations", Survey Review, vol XXII no 176, 1975 'Referencia Adicional: http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf '----------------------------------------------------------------------------------------------------------------------------------------------- 'Test : Para adaptar este código en tu aplicación puedes basarte en este procedimiento test. Copiar el bloque siguiente al ' portapapeles y pega en el editor de VBA. Descomentar la línea que nos interese y pulsar F5 para ver su funcionamiento. ' 'Sub distVincenty_test() ' 'Debug.print distVincenty(latitudorigen, longitudorigen, latituddestino, longituddestino) ' 'End Sub '----------------------------------------------------------------------------------------------------------------------------------------------- Dim low_a As Double Dim low_b As Double Dim f As Double Dim L As Double Dim U1 As Double Dim U2 As Double Dim sinU1 As Double Dim sinU2 As Double Dim cosU1 As Double Dim cosU2 As Double Dim lambda As Double Dim lambdaP As Double Dim iterLimit As Integer Dim sinLambda As Double Dim cosLambda As Double Dim sinSigma As Double Dim cosSigma As Double Dim sigma As Double Dim sinAlpha As Double Dim cosSqAlpha As Double Dim cos2SigmaM As Double Dim c As Double Dim uSq As Double Dim upper_A As Double Dim upper_B As Double Dim deltaSigma As Double Dim s As Double ' resultado final redondeado a 3 decimales (mm). Dim P1 As Double Dim P2 As Double Dim P3 As Double 'Ver http://es.wikipedia.org/wiki/World_Geodetic_System (en inglés) 'para information sobre los parámetros de varios Elipsoides de otros estándares. ' 'low_a y low_b en metros ' === GRS-80 === ' low_a = 6378137 ' low_b = 6356752.314245 ' f = 1 / 298.257223563 ' ' === Airy 1830 === Mayor precisión para Inglaterra y el norte de Europa ' low_a = 6377563.396 ' low_b = 6356256.910 ' f = 1 / 299.3249646 ' ' === Internacional 1924 === ' low_a = 6378388 ' low_b = 6356911.946 ' f = 1 / 297 ' ' === Modelo Clarke 1880 === ' low_a = 6378249.145 ' low_b = 6356514.86955 ' f = 1 / 293.465 ' ' === GRS-67 === ' low_a = 6378160 ' low_b = 6356774.719 ' f = 1 / 298.247167 '=== Parámetros Elipsoide WGS-84 === El más usado en todo el mundo, incluidos los sistemas GPS low_a = 6378137 ' +/- 2m low_b = 6356752.3142 f = 1 / 298.257223563 '==================================== L = toRad(Lon2 - lon1) U1 = Atn((1 - f) * Tan(toRad(lat1))) U2 = Atn((1 - f) * Tan(toRad(Lat2))) sinU1 = Sin(U1) cosU1 = Cos(U1) sinU2 = Sin(U2) cosU2 = Cos(U2) lambda = L lambdaP = 2 * PI iterLimit = 100 ' se puede disminuir hasta 20 si se desea. While (Abs(lambda - lambdaP) > EPSILON) And (iterLimit > 0) iterLimit = iterLimit - 1 sinLambda = Sin(lambda) cosLambda = Cos(lambda) sinSigma = Sqr(((cosU2 * sinLambda) ^ 2) + ((cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) ^ 2)) If sinSigma = 0 Then distVincenty = 0 'puntos coincidentes Exit Function End If cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda sigma = Atan2(cosSigma, sinSigma) sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma cosSqAlpha = 1 - sinAlpha * sinAlpha If cosSqAlpha = 0 Then 'verificamos di es división por cero cos2SigmaM = 0 '2 puntos en el ecuador Else cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha End If c = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha)) lambdaP = lambda 'Los cálculos originales son muy complejos para VBA 'por ello, se han dividido en varias partes para evitar problemas. 'la implementación original para el cálculo de Lambda ' lambda = L + (1 - C) * f * sinAlpha * _ (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * (cos2SigmaM ^ 2)))) 'calculamos porciones P1 = -1 + 2 * (cos2SigmaM ^ 2) P2 = (sigma + c * sinSigma * (cos2SigmaM + c * cosSigma * P1)) 'completo el cálculo lambda = L + (1 - c) * f * sinAlpha * P2 Wend If iterLimit < 1 Then MsgBox "Se ha alcanzado el lÃmite de iteraciones," & vbCrLf & _ "algo no ha idocomo se esperaba.", vbExclamation, "Cálculo por método Vincenty" Exit Function End If uSq = cosSqAlpha * (low_a ^ 2 - low_b ^ 2) / (low_b ^ 2) 'Los cálculos originales son muy complejos para VBA 'por ello, se han dividido en varias partes para evitar problemas. ' 'la implementación original para el cálculo de upper_A 'upper_A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))) 'calculo una parte de la ecuación P1 = (4096 + uSq * (-768 + uSq * (320 - 175 * uSq))) 'completo el cálculo upper_A = 1 + uSq / 16384 * P1 'por extraño que parezca, upper_B calcula sin ningún problema upper_B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq))) 'Los cálculos originales son muy complejos para VBA 'por ello, se han dividido en varias partes para evitar problemas. ' 'la implementación original para el cálculo de deltaSigma 'deltaSigma = upper_B * sinSigma * (cos2SigmaM + upper_B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ^ 2) _ - upper_B / 6 * cos2SigmaM * (-3 + 4 * sinSigma ^ 2) * (-3 + 4 * cos2SigmaM ^ 2))) 'el cálculo de la fórmula de deltaSigma se divide en 3 partes 'para prevenir el error de overflow que puede ocurrir P1 = (-3 + 4 * sinSigma ^ 2) * (-3 + 4 * cos2SigmaM ^ 2) P2 = upper_B * sinSigma P3 = (cos2SigmaM + upper_B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM ^ 2) - upper_B / 6 * cos2SigmaM * P1)) 'completo el cálculo de deltaSigma deltaSigma = P2 * P3 'calculo la distancia s = low_b * upper_A * (sigma - deltaSigma) 'redondeo la distancia a milímetros distVincenty = Round(s, 3) End Function Function Convert_Degree(Decimal_Deg) As Variant '----------------------------------------------------------------------------------------------------------------------------------------------- ' Fuente : http://support.microsoft.com/kb/213449 '----------------------------------------------------------------------------------------------------------------------------------------------- ' Título : Convert_Degree ' Propósito : converts a decimal degree representation to deg min sec ' as 10.46 returns 10° 27' 36" ' Retorno : el valor en grados del valor que le pasamos ' Argumento/s : La sintaxis del procedimiento o función consta del siguiente argumento: ' Parte Modo Descripción '----------------------------------------------------------------------------------------------------------------------------------------------- ' decimal_Deg Obligatorio Valor decimal obtenido '----------------------------------------------------------------------------------------------------------------------------------------------- Dim degrees As Variant Dim minutes As Variant Dim seconds As Variant With Application 'Set degree to Integer of Argument Passed degrees = Int(Decimal_Deg) 'Set minutes to 60 times the number to the right 'of the decimal for the variable Decimal_Deg minutes = (Decimal_Deg - degrees) * 60 'Set seconds to 60 times the number to the right of the 'decimal for the variable Minute seconds = Format(((minutes - Int(minutes)) * 60), "0") 'Returns the Result of degree conversion '(for example, 10.46 = 10º 27' 36") Convert_Degree = " " & degrees & "º " & Int(minutes) & "' " _ & seconds + Chr(34) End With End Function Function Convert_Decimal(Degree_Deg As String) As Double '----------------------------------------------------------------------------------------------------------------------------------------------- ' Fuente : http://support.microsoft.com/kb/213449 '----------------------------------------------------------------------------------------------------------------------------------------------- ' Título : Convert_Decimal ' Propósito : Converts text angular entry to decimal equivalent, as: ' 10° 27' 36" returns 10.46 ' alternative to "°" is permitted: Use "~" instead, as: ' 10~ 27' 36" also returns 10.46 ' Retorno : el valor en grados del valor que le pasamos ' Argumento/s : La sintaxis del procedimiento o función consta del siguiente argumento: ' Parte Modo Descripción '----------------------------------------------------------------------------------------------------------------------------------------------- ' decimal_Deg Obligatorio Valor decimal obtenido '----------------------------------------------------------------------------------------------------------------------------------------------- ' Importante : Declare the variables to be double precision floating-point. '----------------------------------------------------------------------------------------------------------------------------------------------- Dim degrees As Double Dim minutes As Double Dim seconds As Double ' '----------------------------------------------------------------- 'modificación por JLatham 'permite usar el símolo "~" symbol en vez de "°" para indicar grados 'dado que "~" está disponible en teclados no espaañoles y "°" se tiene 'que introducir por [Alt] [0] [1] [7] [6]. Degree_Deg = Replace(Degree_Deg, "~", "°") '----------------------------------------------------------------- ' Set degree to value before "º" of Argument Passed. degrees = Val(Left(Degree_Deg, InStr(1, Degree_Deg, "º") - 1)) ' Set minutes to the value between the "º" and the "'" ' of the text string for the variable Degree_Deg divided by ' 60. The Val function converts the text string to a number. minutes = Val(Mid(Degree_Deg, InStr(1, Degree_Deg, "º") + 2, _ InStr(1, Degree_Deg, "'") - InStr(1, Degree_Deg, "º") - 2)) / 60 ' Set seconds to the number to the right of "'" that is ' converted to a value and then divided by 3600. seconds = Val(Mid(Degree_Deg, InStr(1, Degree_Deg, "'") + _ 2, Len(Degree_Deg) - InStr(1, Degree_Deg, "'") - 2)) / 3600 Convert_Decimal = degrees + minutes + seconds End Function Private Function toRad(ByVal degrees As Double) As Double toRad = degrees * (PI / 180) End Function Private Function Atan2(ByVal X As Double, ByVal Y As Double) As Double '----------------------------------------------------------------------------------------------------------------------------------------------- ' Fuente : http://en.wikibooks.org/wiki/Programming:Visual_Basic_Classic/Simple_Arithmetic#Trigonometrical_Functions '----------------------------------------------------------------------------------------------------------------------------------------------- ' Título : Atan2 ' Propósito : Converts text angular entry to decimal equivalent, as: ' 10° 27' 36" returns 10.46 ' alternative to "°" is permitted: Use "~" instead, as: ' 10~ 27' 36" also returns 10.46 ' Retorno : la arcotangente de las coordenadas que le pasamos a la función ' Argumento/s : La sintaxis del procedimiento o función consta del siguiente argumento: ' Parte Modo Descripción '----------------------------------------------------------------------------------------------------------------------------------------------- ' decimal_Deg Obligatorio Valor decimal obtenido '----------------------------------------------------------------------------------------------------------------------------------------------- ' Importante : Si reutilizas este código, ten en cuenta que X e Y se han invertido respecto al uso tópico '----------------------------------------------------------------------------------------------------------------------------------------------- If Y > 0 Then If X >= Y Then Atan2 = Atn(Y / X) ElseIf X <= -Y Then Atan2 = Atn(Y / X) + PI Else Atan2 = PI / 2 - Atn(X / Y) End If Else If X >= -Y Then Atan2 = Atn(Y / X) ElseIf X <= Y Then Atan2 = Atn(Y / X) - PI Else Atan2 = -Atn(X / Y) - PI / 2 End If End If End Function