初学者在这里徘徊......我正在研究这个井间距项目,该项目查看纬度/长度并确定下一个最近的井。我想我可能正在创建一个无限循环,或者程序只是永远运行(它循环遍历15,000行)。我的主要努力一直在努力确保将每个位置与数据集中的每个位置进行比较。从那里我采取第二个最低距离(因为当它与自身相比时,最低距离将为零)。
Sub WellSpacing()
Dim r As Integer, c As Integer, L As Integer, lastrow As Integer
Dim lat1 As Double, lat2 As Double, long1 As Double, long2 As Double
Dim distance As Double, d1 As Double, d2 As Double, d3 As Double
Dim PI As Double
PI = Application.WorksheetFunction.PI()
L = 2
r = 3
c = 10
lastrow = Sheets("Test").Cells(Rows.Count, "J").End(xlUp).Row
For L = 2 To lastrow
For r = 2 To lastrow
lat1 = Sheets("Test").Cells(L, c)
long1 = Sheets("Test").Cells(L, c + 1)
lat2 = Sheets("Test").Cells(r, c)
long2 = Sheets("Test").Cells(r, c + 1)
d1 = Sin((Abs((lat2 - lat1)) * PI / 180 / 2)) ^ 2 + Cos(lat1 * PI / 180) * Cos(lat2 * PI / 180) * Sin(Abs(long2 - long1) * PI / 180 / 2) ^ 2
d2 = 2 * Application.WorksheetFunction.Atan2(Sqr(1 - d1), Sqr(d1))
d3 = 6371 * d2 * 3280.84
Sheets("Working").Cells(r - 1, c - 9) = d3
Next r
Sheet2.Activate
Range("A:A").Sort Key1:=Range("A1"), Order1:=xlAscending
distance = Sheet2.Range("A2")
Sheets("Test").Cells(L, c + 2) = distance
Sheet2.Range("A:A").Clear
Sheet1.Activate
Next L
End Sub
答案 0 :(得分:2)
我最近一直在处理地理位置数学(又名coordinate geometry)并编写了一个子程序来完成你正在寻找的相同的事情。
您的代码可能不会创建无限循环,但计算数千个坐标之间的距离可能 非常处理器密集型,甚至对代码进行微小更改也可能巨大的对处理时间的影响。
有许多算法可用于确定最近点,但最简单的编码(因此可能最适合一次性使用)被称为强力方法。
For p1 = 1 to numPoints
For p2 = p1 + 1 to numPoints
...calculate {distance}
...if {distance} < minDistance then minDist = {distance}
Next p2
Next p1
使用此方法,距离将计算在 x * ( n - 1 ) / 2
点之间。
例如, 5分列表需要 10次比较:
- 第1点↔第2点
- 第1点↔第3点
- 第1点↔第4点
- 第1点↔第5点
- 第2点↔第3点
- 第2点↔第4点
- 第2点↔第5点
- 第3点↔第4点
- 第3点↔第5点
- 第4点↔第5点
醇>
由于额外的点会增加执行时间指数,此方法可能会产生一些冗长的处理时间,尤其是在速度较慢的机器上或点数过多时。
我用于计算点之间的距离和比较点列表之间的距离的方法远不是[代码更重]最有效的替代方案,但它们为我的“一次性”需求而努力。
根据我的目的,我会在Excel和Excel之间切换(几乎相同的代码)。访问,但访问速度要快得多,因此您可能希望将列表移动到表中并按此方式执行。
我比较的其中一个点有 252项,这需要使用这种“简单代码”方法 31,628个别比较。在 Excel 中,该过程在 1.12秒中完成,访问只需 0.16秒。
在我们开始使用更长的积分列表之前,这似乎不是很大的区别:我的另一个列表(更接近你的大小)有 12,000点,这需要使用 Brute Force 方法进行71,994,000次计算。在访问中,该过程在 8.6分钟中完成,因此我估计在 Excel 中需要大约一小时。
当然,所有这些时间都基于我的操作系统,处理能力,Office版本等.VBA并不适合这种级别的计算,你可以采取的一切措施来减少代码长度差异,包括评论状态栏更新,即时窗口输出,关闭屏幕更新等。
这段代码有点凌乱&amp;没有评论,因为我为了自己的目的把它打了一遍,但它对我有用。如果您对其工作原理有任何疑问,请与我们联系。所有计算均以公制为单位,但可以轻松转换。
Sub findShortestDist_Excel()
Const colLatitude = "C" ' Col.C = Lat, Col.D = Lon
Dim pointList As Range, pointCount As Long, c As Range, _
arrCoords(), x As Long, y As Long
Dim thisDist As Double, minDist As Double, minDist_txt As String
Dim cntCurr As Long, cntTotal As Long, timerStart As Single
timerStart = Timer
Set pointList = Sheets("Stops").UsedRange.Columns(colLatitude)
pointCount = WorksheetFunction.Count(pointList.Columns(1))
'build array of numbers found in Column C/D
ReDim arrCoords(1 To 3, 1 To pointCount)
For Each c In pointList.Columns(1).Cells
If IsNumeric(c.Value) And Not IsEmpty(c.Value) Then
x = x + 1
arrCoords(1, x) = c.Value
arrCoords(2, x) = c.Offset(0, 1).Value
End If
Next c
minDist = -1
cntTotal = pointCount * (pointCount + 1) / 2
'loop through array
For x = 1 To pointCount
For y = x + 1 To pointCount
If (arrCoords(1, x) & arrCoords(2, x)) <> (arrCoords(1, y) & arrCoords(2, y)) Then
cntCurr = cntCurr + 1
thisDist = Distance(arrCoords(1, x), arrCoords(2, x), _
arrCoords(1, y), arrCoords(2, y))
'check if this distance is the smallest yet
If ((thisDist < minDist) Or (minDist = -1)) And thisDist > 0 Then
minDist = thisDist
'minDist_txt = arrCoords(1, x) & "," & arrCoords(2, x) & " -> " & arrCoords(1, y) & "," & arrCoords(2, y)
End If
'Application.StatusBar = "Calculating Distances: " & Format(cntCurr / cntTotal, "0.0%")
End If
Next y
'DoEvents
Next x
Debug.Print "Minimum distance: " & minDist_txt & " = " & minDist & " meters"
Debug.Print "(" & Round(Timer - timerStart, 2) & "sec)"
Application.StatusBar = "Finished. Minimum distance: " & minDist_txt & " = " & minDist & "m"
End Sub
请注意, 上述过程取决于以下 (Access与Excel的版本略有不同):
Public Function Distance(ByVal lat1 As Double, ByVal lon1 As Double, _
ByVal lat2 As Double, ByVal lon2 As Double) As Double
'returns Meters distance in Excel (straight-line)
Dim theta As Double: theta = lon1 - lon2
Dim Dist As Double: Dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta))
Dist = rad2deg(WorksheetFunction.Acos(Dist))
Distance = Dist * 60 * 1.1515 * 1.609344 * 1000
End Function
Function deg2rad(ByVal deg As Double) As Double
deg2rad = (deg * WorksheetFunction.PI / 180#)
End Function
Function rad2deg(ByVal rad As Double) As Double
rad2deg = rad / WorksheetFunction.PI * 180#
End Function
...和Microsoft Access的替代代码:
Sub findShortestDist_Access()
Const tableName = "Stops"
Dim pointCount As Long, arrCoords(), x As Long, y As Long
Dim thisDist As Double, minDist As Double
Dim cntCurr As Long, cntTotal As Long, timerStart As Single
Dim rs As Recordset
timerStart = Timer
Set rs = CurrentDb.OpenRecordset("SELECT * FROM " & tableName)
With rs
.MoveLast
.MoveFirst
pointCount = .RecordCount
'build array of numbers found in Column C/D
ReDim arrCoords(1 To 2, 1 To pointCount)
Do While Not .EOF
x = x + 1
arrCoords(1, x) = !stop_lat
arrCoords(2, x) = !stop_lon
.MoveNext
Loop
.Close
End With
minDist = -1
cntTotal = pointCount * (pointCount + 1) / 2
SysCmd acSysCmdInitMeter, "Calculating Distances:", cntTotal
'loop through array
For x = 1 To pointCount
For y = x + 1 To pointCount
cntCurr = cntCurr + 1
thisDist = Distance(arrCoords(1, x), arrCoords(2, x), _
arrCoords(1, y), arrCoords(2, y))
'check if this distance is the smallest yet
If ((thisDist < minDist) Or (minDist = -1)) And thisDist > 0 Then
minDist = thisDist
End If
SysCmd acSysCmdUpdateMeter, cntCurr
Next y
DoEvents
Next x
SysCmd acSysCmdRemoveMeter
Debug.Print "Minimum distance: " & minDist_txt & " = " & minDist & " meters"
Debug.Print "(" & Round(Timer - timerStart, 2) & "sec)"
End Sub
请注意 上述过程取决于以下 ...(Access可以更快地处理质量计算,但我们必须自己构建一些函数内置于Excel)
Const pi As Double = 3.14159265358979
Public Function Distance(ByVal lat1 As Double, ByVal lon1 As Double, _
ByVal lat2 As Double, ByVal lon2 As Double) As Double
'returns Meters distance in Access (straight-line)
Dim theta As Double: theta = lon1 - lon2
Dim dist As Double
dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) _
* Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta))
dist = rad2deg(aCos(dist))
Distance = dist * 60 * 1.1515 * 1.609344 * 1000
End Function
Function deg2rad(ByVal deg As Double) As Double
deg2rad = (deg * pi / 180#)
End Function
Function rad2deg(ByVal rad As Double) As Double
rad2deg = rad / pi * 180#
End Function
Function aTan2(x As Double, y As Double) As Double
aTan2 = Atn(y / x)
End Function
Function aCos(x As Double) As Double
On Error GoTo aErr
If x = 0 Or Abs(x) = 1 Then
aCos = 0
Else
aCos = Atn(-x / Sqr(-x * x + 1)) + 2 * Atn(1)
End If
Exit Function
aErr:
aCos = 0
End Function
另一种计算近点的方法称为平面情况。我还没有看到任何现成的代码示例,我不需要它就足以打扰它编码,但它的要点是:
阅读有关维基百科上的 Closest pair of points problem 的更多信息。
答案 1 :(得分:1)
我建议使用数组,如@Qharr所说。我还希望通过包含一些逻辑步骤来加速这个过程,避免在每组点上进行复杂的数学运算。
我的意思是你可以先做一个粗略的估计,看是否打算做实际的计算。我去看看当前位置的纬度或长度是否比最后一个点更接近,但你可以做任何你想做的事。
我会将您的代码更改为:
Sub WellSpacing()
Dim R As Integer, C As Integer, L As Integer, LastRow As Integer, Shortest() As Integer
Dim Lats() As Double, Longs() As Double, Distances() As Double
Dim Distance As Double, D1 As Double, D2 As Double, D3 As Double
Dim PI As Double
On Error Resume Next
PI = Application.WorksheetFunction.PI()
L = 2
R = 3
C = 10
LastRow = Sheets("Test").Cells(Rows.Count, 10).End(xlUp).Row
ReDim Lats(1 To (LastRow - 1)) As Double
ReDim Longs(1 To (LastRow - 1)) As Double
ReDim Distances(1 To (LastRow - 1)) As Double
ReDim Shortest(1 To (LastRow - 1)) As Integer
For L = 2 To LastRow
Lats(L - 1) = Sheets("Test").Range("J" & L).Value
Longs(L - 1) = Sheets("Test").Range("K" & L).Value
Next L
For L = 1 To (LastRow - 1)
'This is a method of setting an initial value that can't be obtained through the caclucations (so you will know if any calcs have been done or not).
Distances(L) = -1
For R = 1 To (LastRow - 1)
'This minimises your calculations by 15,000 to begin with
If R = L Then GoTo Skip_This_R
'This skips checking the previous distances if it is the first calculation being checked.
If Distances(L) = -1 Then GoTo Skip_Check
'If there has already been a distance calculated, this does a rough check of whether the Lat or Long is closer. If neither
'the Lat or Long are closer than the current closest, then it will skip it. This reduces the code by 7 lines for most pairs.
If Abs(Lats(L) - Lats(R)) < Abs(Lats(L) - Lats(Shortest(L))) Or Abs(Longs(L) - Longs(R)) < Abs(Longs(L) - Longs(Shortest(L))) Then
Skip_Check:
D1 = Sin((Abs((Lats(R) - Lats(L))) * PI / 180 / 2)) ^ 2 + Cos(Lats(L) * PI / 180) * Cos(Lats(R) * PI / 180) * Sin(Abs(Longs(R) - Longs(L)) * PI / 180 / 2) ^ 2
D2 = 2 * Application.WorksheetFunction.Atan2(Sqr(1 - D1), Sqr(D1))
D3 = 6371 * D2 * 3280.84
If D3 < Distances(L) Or Distances(L) = -1 Then
Distances(L) = D3
'This stores the index value in the array of the closest Lat/Long point so far.
Shortest(L) = R
End If
End If
Skip_This_R:
Next R
'This puts the resulting closest distance into the corresponding cell.
Sheets("Test").Range("L" & (L + 1)).Value = Distances(L)
'This clears any previous comments on the cell.
Sheets("Test").Range("L" & (L + 1)).Comments.Delete
'This adds a nice comment to let you know which Lat/Long position it is closest to.
Sheets("Test").Range("L" & (L + 1)).AddComment "Matched to Row " & (Shortest(L) + 1)
Next L
End Sub