I've tried googling, but with limited luck - what I want to do is use VBA to read in coordinates from a serial GPS dongle (or bluetooth Android phone mimicking the former), log a "visit" record, and find the nearest matching record on a table of our clients. Has anyone seen an opensource script that will allow this?
Thanks in advance.
PG
For the serial port data acquisition see the Serial Port Communications page at my web site. Then you can write records to a table using a SQL Insert query or a DAO Recordset with an AddNew. However finding the nearest matching record will likely require using of geometry to figure out the reading through the table looking for the closest location I'd have to refresh my memory as to the exact equations required.
I'm also thinking that to speed up the search you might want to index the latitude and longitude and start your search at nearby lat/longs. That is limit the initial record set to plus/minus, for example, 0.1 of a lat/long which, just guessing would be about 10 kms square.
Here's a rough rundown of what you will need to do.
Lookup Longitude & Latitude For Your Clients' Addresses:
I posted a question a while back on SO asking how to get GPS Coordinates for an address. You can see that question here. There's actually two functions there for you, one to use Google Maps API and another that uses rpc.geocoder.us. Take your pick. Just be aware that each of them having limitations. Google has licensing restrictions as well as maximum queries per day. Geocoder.us has a limit of one query every 15 seconds and I don't remember what their maximum queries per day is, if they even have a limit.
Obviously, you will need to retrieve the Longitude and Latitude for your addresses beforehand, and store this information along with the address. You could possibly try to come up with a zip code for the area and lookup the addresses by that but it could be horribly inaccurate in sprawling urban areas, especially if you have a lot of customers concentrated in a single zip code. You'll need to take Tony's advice here and query your GPS coordinate data using a between statement to get addresses in the near vicinity.
Get GPS Data from the GPS Device
As Tony has already pointed out, you'll need to use something like an ActiveX control, DLL, or API call to perform serial communications with your GPS Device. I have used MS's communication ActiveX control in the past for getting GPS data and it did work satisfactory.
I don't know where my code is to retrieve the incoming data from the GPS device. It's not exactly trivial if you have never programmed serial communications before. You usually have an OnComm event that fires on incoming data. If I remember correctly you loop until an EOF (End of File) code or bit is found which indicates the end of a data stream. If you use MS's Communications Control you can see some sample code here: http://support.microsoft.com/kb/194922
I think I had to use 4800 in my settings instead of 9600 but your requirements may be different depending what type of device you are using.
Extract The Right Data From Your Incoming GPS Data
Step two is to extract the data you need from the incoming GPS Data. You may even find that you have to change it to match the GPS Data you have stored (see below).
In my sample NMEA sentences (above), the Longitude and Latitude are both in the following format:
ddmm.mmmm
For some programs or API's you may have to convert it into Degrees.Degrees, or to word it another way, dd.dddd. The formula to convert it is:
dd.dddd = ddd + mm.mmmm/60
So, for example, if we want to convert my sample data above to exact Longitude and Latitude in degrees, here's what it would look like:
3731.9404 ----> 37 + 31.9404/60 = 37.53234 degrees
10601.6986 ----> 106 + 1.6986/60 = 106.02831 degrees
Here are some functions I wrote back in 2007 to extract certain parts of the data from the NMEA sentence:
Public Function ExtractLatitude(strNMEAString As String, Optional strNMEAStringType As String = "GPRMC") As String
'This function extracts the latitude from an NMEA string and converts it to Decimal Degrees (as a string).
'To use this function you must specify what string type you are passing in, either GPRMC or GPGGA
Dim aryNMEAString() As String
aryNMEAString() = Split(strNMEAString, ",")
Dim dblMinutes As Single, dblLatitude As Single
Select Case strNMEAStringType
Case "GPRMC"
'Latitude is the Number 3 place in the array (4th place in the string)
If aryNMEAString(2) = "A" Then 'A represents a valid string
dblMinutes = (CDbl(Mid(aryNMEAString(3), 3, 7)) / 60)
dblLatitude = CDbl(Left(aryNMEAString(3), 2)) + dblMinutes
ExtractLatitude = CStr(dblLatitude)
End If
Case "GPGGA"
'Latitude is the Number 2 place in the array (3rd place in the string)
If CDbl(aryNMEAString(2)) <> 0 Then 'If string is invalid it will be 0
dblMinutes = (CDbl(Mid(aryNMEAString(2), 3, 7)) / 60)
dblLatitude = CDbl(Left(aryNMEAString(2), 2)) + dblMinutes
ExtractLatitude = CStr(dblLatitude)
End If
End Select
End Function
Public Function ExtractLongitude(strNMEAString As String, Optional strNMEAStringType As String = "GPRMC") As String
'This function extracts the longitude from an NMEA string and converts it to Decimal Degrees (as a string).
'To use this function you must specify what string type you are passing in, either GPRMC or GPGGA
Dim aryNMEAString() As String
aryNMEAString() = Split(strNMEAString, ",")
Dim dblMinutes As Single, dblLongitude As Single
Select Case strNMEAStringType
Case "GPRMC"
'Latitude is the Number 3 place in the array (4th place in the string)
If aryNMEAString(2) = "A" Then
dblMinutes = (CDbl(Mid(aryNMEAString(5), 4, 7)) / 60)
dblLongitude = CDbl(Left(aryNMEAString(5), 3)) + dblMinutes
ExtractLongitude = CStr(dblLongitude)
End If
Case "GPGGA"
'Latitude is the Number 2 place in the array (3rd place in the string)
If CDbl(aryNMEAString(4)) <> 0 Then
dblMinutes = (CDbl(Mid(aryNMEAString(4), 4, 7)) / 60)
dblLongitude = CDbl(Left(aryNMEAString(4), 3)) + dblMinutes
ExtractLongitude = CStr(dblLongitude)
End If
End Select
End Function
Public Function ExtractSpeed(strGPRMC As String) As Integer
'Expects a GPRMC NMEA Sentence
Dim aryGPRMC() As String, dblSpeed As Double
aryGPRMC() = Split(strGPRMC, ",")
If aryGPRMC(7) <> "" Then dblSpeed = CDbl(aryGPRMC(7))
'Convert knots to MPH
ExtractSpeed = CInt(dblSpeed * 1.15077945)
End Function
Public Function ExtractHeading(strGPRMC As String) As Double
'Expects a GPRMC NMEA Sentence
Dim aryGPRMC() As String
aryGPRMC() = Split(strGPRMC, ",")
If aryGPRMC(8) <> "" Then ExtractHeading = CDbl(aryGPRMC(8))
End Function
Public Function ExtractSatelliteCount(strGPGGA As String) As Integer
'Expects a GPGGA NMEA Sentence
Dim aryGPGGA() As String
aryGPGGA() = Split(strGPGGA, ",")
ExtractSatelliteCount = CInt(aryGPGGA(7))
End Function
Related
In MS Access I have a table with a Short Text field named txtPMTaskDesc in which some records contains numbers, and if they do, at different positions in the string. I would like to recover these numbers from the text string if possible for sorting purposes.
There are over 26000 records in the table, so I would rather handle it in a query over using VBA loops etc.
Sample Data
While the end goal is to recover the whole number, I was going to start with just identifying the position of the first numerical value in the string. I have tried a few things to no avail like:
InStr(1,[txtPMTaskDesc],"*[0-9]*")
Once I get that, I was going to use it as a part of a Mid() function to pull out it and the character next to it like below. (its a bit dodgy, but there is never more than a two-digit number in the text string)
IIf(InStr(1,[txtPMTaskDesc],"*[0-9]*")>0,Mid([txtPMTaskDesc],InStr(1,[txtPMTaskDesc],"*[0-9]*"),2)*1,0)
Any assistance appreciated.
If data is truly representative and number always preceded by "- No ", then expression in query can be like:
Val(Mid(txtPMTaskDesc, InStr(txtPMTaskDesc, "- No ") + 5))
If there is no match, a 0 will return, however, if field is null, the expression will error.
If string does not have consistent pattern (numbers always in same position or preceded by some distinct character combination that can be used to locate position), don't think can get what you want without VBA. Either loop through string or explore Regular Expressions aka RegEx. Set reference to Microsoft VBScript Regular Expressions x.x library.
Function GetNum(strS AS String)
Dim re As RegExp, Match As Object
Set re = New RegExp
re.Pattern = "[\d+]+"
Set Match = re.Execute(strS)
GetNum = Null
If Match.Count > 0 Then GetNum = Match(0)
End Function
Input of string "Fuel Injector - No 1 - R&I" returns 1.
Place function in a general module and call it from query.
SELECT table.*, GetNum(Nz(txtPMTaskDesc,"")) AS Num FROM table;
Function returns Null if there is no number match.
Well, does the number you want ALWAYS have a - No xxxx - format?
If yes, then you could have this global function in VBA like this:
Public Function GNUM(v As Variant) As Long
If IsNull(v) Then
GNUM = 0
Exit Function
End If
Dim vBuf As Variant
vBuf = Split(v, " - No ")
Dim strRes As String
If UBound(vBuf) > 0 Then
strRes = Split(vBuf(1), "-")(0)
GNUM = Trim(strRes)
Else
GNUM = 0
End If
End Function
Then your sql will be like this:
SELECT BLA, BLA, txtPMTaskDesc, GNUM([txtPMTaskDesc] AS TaskNum
FROM myTable
So you can create/have a public VBA function, and it can be used in the sql query.
It just a question if " - No -" is ALWAYS that format, then THEN the number follows this
So we have "space" "-" "space" "No" "space" "-" -- then the number and the " -"
How well this will work depends on how consistent this text is.
I've been searching and searching and my Google-fu has failed me. I'm trying to convert an encoded number from base-32 to decimal using either expressions or a macro, but I'm not finding anything. I know Excel has the "Decimal" function, I've been hoping that I could stumble onto something similar.
I'm reluctant to use VBA as I don't want to spend time re-learning the language right now and I'm worried that my organization will flag it as potentially dangerous (which could kill my attempts at making any databases).
With an input of "16O9E55"
I expect a result of 1300543653.
I should clarify that this is "base32hex" according to Wikipedia. It's 0-9, A-V. It's only 7 characters of base-32 that needs to convert to 10 digits of decimal. My use case is decoding a barcode into the data I need.
I doubt this can be accomplished without VBA. Consider code adapted from https://www.excelbanter.com/excel-worksheet-functions/150198-formulat-convert-base-32-decimal.html
Public Function Base32ToDec(Num As String) As Variant
Static Digits As String
Dim i As Integer
Dim myIndex As Integer
Dim myStr As String
Digits = "0123456789ABCDEFGHIJKLMNOPQRSTUV"
For i = Len(Num) To 1 Step -1
myStr = Mid(Num, i, 1)
myIndex = InStr(Digits, myStr) - 1
Base32ToDec = Base32ToDec + myIndex * 32 ^ (Len(Num) - i)
Next i
End Function
According to Wikipedia, Base32 uses a 32-character set comprising the twenty-six upper-case letters A–Z, and the digits 2–7. The variant base32hex starts with 0 - 9 and uses the letters A to V.
If only numbers with a limited range have been encoded, you can decode them easily with VBA, otherwise you would have to return an array of bytes and process it further.
You write, that you have up to 10 decimal digits. The question is, what the maximum number is. The Long type can store numbers up to 2,147,483,647. This are ten digits; however, with 10 digits you could store a number as big as 9,999,999,999.
Therefore, the following function returns the number as Double. If you know that your number will never exceed 2,147,483,647, then you can exchange the Double type by Long for the sum variable and the function return type.
Public Function DecodeBase32hex(ByVal encoded As String) As Double
Dim ch As String
Dim sum As Double
Dim d As Long, i As Long
For i = 1 To Len(encoded)
ch = Mid$(encoded, i, 1)
If ch >= "A" And ch <= "Z" Then
d = Asc(ch) - Asc("A") + 10
ElseIf ch >= "0" And ch <= "9" Then
d = Asc(ch) - Asc("0")
Else
Exit For 'E.g. padding charachters
End If
sum = 32 * sum + d
Next i
DecodeBase32hex = sum
End Function
Test in Access' immediate window:
?DecodeBase32hex("16O9E55")
1300543653
It is possible to create User Defined Functions in MySQL just as we do in excel, if Yes How to do the same.
Also, If above is possible then how do i correlate (have same usage) the MySql Function with my existing excel function pasted below:
Public Function HexToText(Text As Range) As String
Dim i As Integer
Dim DummyStr As String
For i = 1 To Len(Text) Step 2
If Val("&H" & (Mid(Text, i, 2))) > 31 Then DummyStr = DummyStr &
Chr(Val("&H" & (Mid(Text, i, 2))))
DoEvents
Next i
HexToText = DummyStr
End Function
Please note that my Hex Data also contains Null (00) probable causing error while using Unhex
One example Values in Hex Column:
596f757220496e7465726e6574206163636f756e7420776974682044484c2042524f414442414e44204e4554205b505d204c494d4954454420686176696e6720757365726e616d652020414a57414454524156454c2077696c6c20657870697265206f6e20323031362d31302d30332032303a30303a313620506c656173652
Error Result Achieved via above UnHex command :
��W"��FW&�WB66�V�Bv�F�D��%$�D$�B�UB��ĔԕDTB�f��rW6W&��R�tEE$dT�v���W��&R��#b��2#��b�V6R
Actual Result should have been something like:
Your Internet account with DHL BROADBAND NET [P] LIMITED having username AJWDTRAVEL will expire on 2016-10-03 20:00:16
The Context
I have an app in excel VBA for making read-only queries on a remote database.
Queries are executed from UDF's. My app passes an array of data from the recordset object to the function and Excel's fast process for writing an array to a cell range is invoked.
The Challenge
The app must be able to optionally return field names at the top of the dataset. This is presenting a huge performance challenge for me. The only way I know of to append or prepend to a 2D array in VBA is to loop through the entire array. Normally, I'm spared such a loop by passing the recordset.getRows() object directly to my UDF. However, when combining the list of fields and the result of the query with the looping method (the only method I'm aware of) I double or triple my calculation time for sizable queries.
I benchmarked this: for a query of 2k rows and 5 fields, average calc time without field names included is 4.3 seconds, vs. 9.8 seconds with field names
My first try was to combine the field names and recordset on the server using a UNION clause in my select statement (my server is MySQL). This does not work, however, since UNION forces data-type equality, implicitly converting my numerical data to strings. To convert them back I'd have to loop through the array, negating any efficiency gained.
My Question
Is there any object method of the recordset object or of VBA arrays that could be called upon to prepend a row to a large array without looping through the entire large array? The field names are all known before the MySQL query is executed.
My loop for joining the arrays is below. Define a new array arr of length of the recordset + 1, then loop through it, first adding the fields, then each row of the recordset array:
For r = LBound(arr, 1) To UBound(arr, 1)
If r = LBound(arr, 1) Then
arr(r) = fieldArray
Else
arr(r) = Application.Index(rs_array, r - 1, 0)
End If
Next
Using Application.Index is possibly the slowest way to combine your arrays: use a regular nested loop instead and you won't even notice any hit -
Sub TT()
Dim a(1 To 2000, 1 To 10)
Dim b(1 To 2000, 1 To 10)
Dim cc(1 To 2000)
Dim r, c, t
t = Timer
For r = 1 To 2000
For c = 1 To 10
b(r, c) = a(r, c)
Next c
Next r
Debug.Print "Loop", Timer - t '>> 0.015625 sec
t = Timer
For r = 1 To 2000
cc(r) = Application.Index(a, r, 0)
Next r
Debug.Print "Index", Timer - t '>> 4.195313 sec
End Sub
After applying the unpivot procedure, I have an Amount column that has blanks and other characters ( like "-"). I would like to convert those non-numberic values to zero. I use replace procedure but it only converts one at the time.
Also, I tried to use the following script
/**
Public Overrides Sub Input()_ProcessInputRows(ByVal Row As Input()Buffer)
If Row.ColumnName_IsNull = False Or Row.ColumnName = "" Then
Dim pattern As String = String.Empty
Dim r As Regex = Nothing
pattern = "[^0-9]"
r = New Regex(pattern, RegexOptions.Compiled)
Row.ColumnName = Regex.Replace(Row.ColumnName, pattern, "")
End If
End Sub
**/
but i'm getting error.I don't much about script so maybe I placed in the wrong place. The bottom line is that I need to convert those non-numberic values.
Thank you in advance for your help.
I generally look at regular expressions as a great way to introduce another problem into an existing one.
What I did to simulate your problem was to write a select statement that added 5 rows. 2 with valid numbers, the rest were an empty string, string with spaces and one with a hyphen.
I then wired it up to a Script Component and set the column as read/write
The script I used is as follows. I verified there was a value there and if so, I attempted to convert the value to an integer. If that failed, then I assigned it zero. VB is not my strong suit so if this could have been done more elegantly, please edit my script.
Public Overrides Sub Input0_ProcessInputRow(ByVal Row As Input0Buffer)
' Ensure we have data to work with
If Not Row.ColumnName_IsNull Then
' Test whether it's a number or not
' TryCast doesn't work with value types so I'm going the lazy route
Try
' Cast to an integer and then back to string because
' my vb is weak
Row.ColumnName = CStr(CType(Row.ColumnName, Integer))
Catch ex As Exception
Row.ColumnName = 0
End Try
End If
End Sub