Code Optimization

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Code Optimization

24 Jul 2018, 09:32

Hello I have the following code thats job is to convert Any Degree Minute, or Degree Minute Second to Decimal Degrees. I have gotten it to work with most formats (may have missed one or two) and was looking for advice to improve the function I have made, and if anyone had any ideas for inserting multiple sets of coordinates. Right now since I have only been testing I have just been dealing with 1 set. Any help or thoughts welcome. Thanks

Code: Select all

Pos=1
Inputbox, CoordRaw
If !InStr(CoordRaw, "°")
{
	CoordRaw := RegExReplace(CoordRaw, "(?<=\d)\s(?=.\d+?(\,|\.|\s(N|S|E|W)))", "°")
	StringReplace, CoordRaw, CoordRaw, %A_Space%N, 'N, all
	StringReplace, CoordRaw, CoordRaw, %A_Space%S, 'S, all
	StringReplace, CoordRaw, CoordRaw, %A_Space%E, 'E, all
	StringReplace, CoordRaw, CoordRaw, %A_Space%W, 'W, all
	CoordRaw := RegExReplace(CoordRaw, "(?<=.\d)\s(?=.\d+?°)", "°")
	CoordRaw := RegExReplace(CoordRaw, "(?<=.°\d\d)°(?=.\d+?)", "'")
	CoordRaw := RegExReplace(CoordRaw, "(?<=.\d'\d\d)'(?=(N|S|E|W))", "@")
	CoordRaw := RegExReplace(CoordRaw, "(?<=.'\d\d\,\d)'(?=(N|S|E|W))", "@")
	CoordRaw := RegExReplace(CoordRaw, "°(?=\d\d'\s\d\d´·)", " ")
	CoordRaw := RegExReplace(CoordRaw, "(?<=\d\d)'(?=\s\d\d´·)", "°")
	CoordRaw := StrReplace(CoordRaw, "@", """")
}
While Pos := RegExMatch(CoordRaw, "\d+(º|°|-).+?(N|S|E|" Chr(1045) "|W)", m, Pos+StrLen(m))
{
	If InStr(m, °) {
		If A_Index = 1
			Lat := CoordinateConvert(m)
		If A_Index = 2
			Long := CoordinateConvert(m)
	}
}
MsgBox % Lat ", " Long
ExitApp
Return

;MsgBox % ArrayCoords[1] ":" ArrayCoords[2] ":" ArrayCoords[3]

CoordinateConvert(byref Input)
{
	Input := StrReplace(Input, ",", ".")	;Fix for European countries that use a , instead of a .
	Input := StrReplace(Input, Chr(186), "°")
	ArrayOfApostrophes := ["180", "700", "1370", "2036", "2037", "8217", "65287"]
	For, q,v in ArrayOfApostrophes
		Input := StrReplace(Input, Chr(v), "'") ;Fix so all ' are seen the same
	ArrayOfQuotes := ["733", "8221", "10078", "12317", "12318", "12319", "65282"]
	For, q,v in ArrayOfQuotes
		Input := StrReplace(Input, Chr(v), """") ;Fix so all " are seen the same
	If InStr(Input, """") { ;General Degree Minute Second
		ArrayCoords := StrSplit(Input, ["°","'",""""], A_Space)
		If InStr(ArrayCoords[4], "S") or InStr(ArrayCoords[4], "W")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600)*-1
		Else
			Return % ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600
	} Else If InStr(Input, "'.") { ;Turkey Specific
		ArrayCoords := StrSplit(Input, "°")
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "'.", ".")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "'.", ".")
			Return % ArrayCoords[1] + ArrayCoords[2]/60
		}
	} Else If InStr(Input, "'") { ;General Degree Minute
		ArrayCoords := StrSplit(Input, ["°","'"], A_Space)
		If InStr(ArrayCoords[3], "S") or InStr(ArrayCoords[3], "W")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		Else
			Return % ArrayCoords[1] + ArrayCoords[2]/60
	} Else If InStr(Input, "-") { ;Any Country that uses Dashes
		ArrayCoords := StrSplit(Input, "-", A_Space)
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", "")
			If ArrayCoords.MaxIndex() = 2 ;Sweden Specifc
				Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
			Else {	;Japan, South Korea, US
				ArrayCoords[3] := StrReplace(ArrayCoords[3], "S", "") , ArrayCoords[3] := StrReplace(ArrayCoords[3], "W", "")
				Return % (ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600)*-1
			}
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", "")
			If ArrayCoords.MaxIndex() = 2 ;Sweden Specifc
				Return % ArrayCoords[1] + ArrayCoords[2]/60
			Else {	;Japan, South Korea, US
				ArrayCoords[3] := StrReplace(ArrayCoords[3], "N", "") , ArrayCoords[3] := StrReplace(ArrayCoords[3], "E", "")
				Return % ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600
			}
		}
	} Else If InStr(Input, "´") { ;UK, India Specific
		ArrayCoords := StrSplit(Input, "°")
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "´·", ".")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "´·", ".")
			Return % ArrayCoords[1] + ArrayCoords[2]/60
		}
	}
}
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Code Optimization

24 Jul 2018, 09:35

not sure about your code, but this is the perfect type of function to use a unit testing suite for:

https://github.com/Uberi/Yunit

this way you can document all of the coordinate input formats that you are handling and your expected outputs, and then just run your test suite to make sure the function still works properly for all formats as you continue to improve on it

MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 10:15

So I am a little confused on how to get it to test properly. So I downloaded the testing suite and plugged in my function and data, then once I run the test I get an error on line 58

Code: Select all

#Include ..\Yunit.ahk
#Include ..\Window.ahk
#Include ..\StdOut.ahk
#Include ..\JUnit.ahk
#Include ..\OutputDebug.ahk

Yunit.Use(YunitStdOut, YunitWindow, YunitJUnit, YunitOutputDebug).Test(NumberTestSuite)

class NumberTestSuite
{
	Begin()
    {
        this.CoordRaw := "25°30'N 75°25'W"
    }
	Test_Fails_NoMessage() {
		Pos=1
		;Inputbox, CoordRaw
		CoordRaw := "25°30'N 75°25'W"
		If !InStr(CoordRaw, "°")
		{
			CoordRaw := RegExReplace(CoordRaw, "(?<=\d)\s(?=.\d+?(\,|\.|\s(N|S|E|W)))", "°")
			StringReplace, CoordRaw, CoordRaw, %A_Space%N, 'N, all
			StringReplace, CoordRaw, CoordRaw, %A_Space%S, 'S, all
			StringReplace, CoordRaw, CoordRaw, %A_Space%E, 'E, all
			StringReplace, CoordRaw, CoordRaw, %A_Space%W, 'W, all
			CoordRaw := RegExReplace(CoordRaw, "(?<=.\d)\s(?=.\d+?°)", "°")
			CoordRaw := RegExReplace(CoordRaw, "(?<=.°\d\d)°(?=.\d+?)", "'")
			CoordRaw := RegExReplace(CoordRaw, "(?<=.\d'\d\d)'(?=(N|S|E|W))", "@")
			CoordRaw := RegExReplace(CoordRaw, "(?<=.'\d\d\,\d)'(?=(N|S|E|W))", "@")
			CoordRaw := RegExReplace(CoordRaw, "°(?=\d\d'\s\d\d´·)", " ")
			CoordRaw := RegExReplace(CoordRaw, "(?<=\d\d)'(?=\s\d\d´·)", "°")
			CoordRaw := StrReplace(CoordRaw, "@", """")
		}
		While Pos := RegExMatch(CoordRaw, "\d+(º|°|-).+?(N|S|E|" Chr(1045) "|W)", m, Pos+StrLen(m))
		{
			If InStr(m, °) {
				If A_Index = 1
					Lat := CoordinateConvert(m)
				If A_Index = 2
					Long := CoordinateConvert(m)
			}
		}
		; MsgBox % Lat ", " Long
		; ExitApp
		; Return
		Yunit.assert(this.Lat == 25.500000)
		Yunit.assert(this.Long == -75.416667)
		;MsgBox % ArrayCoords[1] ":" ArrayCoords[2] ":" ArrayCoords[3]
	}
}

CoordinateConvert(byref Input)
{
	Input := StrReplace(Input, ",", ".")	;Fix for European countries that use a , instead of a .
	Input := StrReplace(Input, Chr(186), "°")
	ArrayOfApostrophes := ["180", "700", "1370", "2036", "2037", "8217", "65287"]
	For, q,v in ArrayOfApostrophes
		Input := StrReplace(Input, Chr(v), "'") ;Fix so all ' are seen the same
	ArrayOfQuotes := ["733", "8221", "10078", "12317", "12318", "12319", "65282"]
	For, q,v in ArrayOfQuotes
		Input := StrReplace(Input, Chr(v), """") ;Fix so all " are seen the same
	If InStr(Input, """") { ;General Degree Minute Second
		ArrayCoords := StrSplit(Input, ["°","'",""""], A_Space)
		If InStr(ArrayCoords[4], "S") or InStr(ArrayCoords[4], "W")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600)*-1
		Else
			Return % ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600
	} Else If InStr(Input, "'.") { ;Turkey Specific
		ArrayCoords := StrSplit(Input, "°")
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "'.", ".")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "'.", ".")
			Return % ArrayCoords[1] + ArrayCoords[2]/60
		}
	} Else If InStr(Input, "'") { ;General Degree Minute
		ArrayCoords := StrSplit(Input, ["°","'"], A_Space)
		If InStr(ArrayCoords[3], "S") or InStr(ArrayCoords[3], "W")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		Else
			Return % ArrayCoords[1] + ArrayCoords[2]/60
	} Else If InStr(Input, "-") { ;Any Country that uses Dashes
		ArrayCoords := StrSplit(Input, "-", A_Space)
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", "")
			If ArrayCoords.MaxIndex() = 2 ;Sweden Specifc
				Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
			Else {	;Japan, South Korea, US
				ArrayCoords[3] := StrReplace(ArrayCoords[3], "S", "") , ArrayCoords[3] := StrReplace(ArrayCoords[3], "W", "")
				Return % (ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600)*-1
			}
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", "")
			If ArrayCoords.MaxIndex() = 2 ;Sweden Specifc
				Return % ArrayCoords[1] + ArrayCoords[2]/60
			Else {	;Japan, South Korea, US
				ArrayCoords[3] := StrReplace(ArrayCoords[3], "N", "") , ArrayCoords[3] := StrReplace(ArrayCoords[3], "E", "")
				Return % ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600
			}
		}
	} Else If InStr(Input, "´") { ;UK, India Specific
		ArrayCoords := StrSplit(Input, "°")
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "´·", ".")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "´·", ".")
			Return % ArrayCoords[1] + ArrayCoords[2]/60
		}
	}
}
which is the line that contains

Code: Select all

Input := StrReplace(Input, Chr(v), "'") ;Fix so all ' are seen the same
Not sure what I am doing wrong. Am I supposed to split everything up by what I'm fixing or did I just plug my data in wrong?
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Code Optimization

24 Jul 2018, 11:00

MannyKSoSo wrote:So I am a little confused on how to get it to test properly.
Well it looks like you just put your entire script into the testing file. That's not how it should be done. You only want to test your conversion function.

I'm not sure about all of the regex replaces you are doing after you collect the user input.
Here's how I would do it. I would first break up your main script like so:

Image

first, i would put your main conversion function in its own file:
CoordinateConvert.ahk

Code: Select all

CoordinateConvert(byref Input)
{
	Input := StrReplace(Input, ",", ".")	;Fix for European countries that use a , instead of a .
	Input := StrReplace(Input, Chr(186), "°")
	ArrayOfApostrophes := ["180", "700", "1370", "2036", "2037", "8217", "65287"]
	For, q,v in ArrayOfApostrophes
		Input := StrReplace(Input, Chr(v), "'") ;Fix so all ' are seen the same
	ArrayOfQuotes := ["733", "8221", "10078", "12317", "12318", "12319", "65282"]
	For, q,v in ArrayOfQuotes
		Input := StrReplace(Input, Chr(v), """") ;Fix so all " are seen the same
	If InStr(Input, """") { ;General Degree Minute Second
		ArrayCoords := StrSplit(Input, ["°","'",""""], A_Space)
		If InStr(ArrayCoords[4], "S") or InStr(ArrayCoords[4], "W")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600)*-1
		Else
			Return % ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600
	} Else If InStr(Input, "'.") { ;Turkey Specific
		ArrayCoords := StrSplit(Input, "°")
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "'.", ".")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "'.", ".")
			Return % ArrayCoords[1] + ArrayCoords[2]/60
		}
	} Else If InStr(Input, "'") { ;General Degree Minute
		ArrayCoords := StrSplit(Input, ["°","'"], A_Space)
		If InStr(ArrayCoords[3], "S") or InStr(ArrayCoords[3], "W")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		Else
			Return % ArrayCoords[1] + ArrayCoords[2]/60
	} Else If InStr(Input, "-") { ;Any Country that uses Dashes
		ArrayCoords := StrSplit(Input, "-", A_Space)
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", "")
			If ArrayCoords.MaxIndex() = 2 ;Sweden Specifc
				Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
			Else {	;Japan, South Korea, US
				ArrayCoords[3] := StrReplace(ArrayCoords[3], "S", "") , ArrayCoords[3] := StrReplace(ArrayCoords[3], "W", "")
				Return % (ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600)*-1
			}
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", "")
			If ArrayCoords.MaxIndex() = 2 ;Sweden Specifc
				Return % ArrayCoords[1] + ArrayCoords[2]/60
			Else {	;Japan, South Korea, US
				ArrayCoords[3] := StrReplace(ArrayCoords[3], "N", "") , ArrayCoords[3] := StrReplace(ArrayCoords[3], "E", "")
				Return % ArrayCoords[1] + ArrayCoords[2]/60 + ArrayCoords[3]/3600
			}
		}
	} Else If InStr(Input, "´") { ;UK, India Specific
		ArrayCoords := StrSplit(Input, "°")
		If InStr(ArrayCoords[2], "S") or InStr(ArrayCoords[2], "W") {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "S", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "W", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "´·", ".")
			Return % (ArrayCoords[1] + ArrayCoords[2]/60)*-1
		} Else {
			ArrayCoords[2] := StrReplace(ArrayCoords[2], "N", "") , ArrayCoords[2] := StrReplace(ArrayCoords[2], "E", ""), ArrayCoords[2] := StrReplace(ArrayCoords[2], "´·", ".")
			Return % ArrayCoords[1] + ArrayCoords[2]/60
		}
	}
}
and break out the user input section into its own file:
UserInput.ahk

Code: Select all

Pos=1
Inputbox, CoordRaw
If !InStr(CoordRaw, "°")
{
	CoordRaw := RegExReplace(CoordRaw, "(?<=\d)\s(?=.\d+?(\,|\.|\s(N|S|E|W)))", "°")
	StringReplace, CoordRaw, CoordRaw, %A_Space%N, 'N, all
	StringReplace, CoordRaw, CoordRaw, %A_Space%S, 'S, all
	StringReplace, CoordRaw, CoordRaw, %A_Space%E, 'E, all
	StringReplace, CoordRaw, CoordRaw, %A_Space%W, 'W, all
	CoordRaw := RegExReplace(CoordRaw, "(?<=.\d)\s(?=.\d+?°)", "°")
	CoordRaw := RegExReplace(CoordRaw, "(?<=.°\d\d)°(?=.\d+?)", "'")
	CoordRaw := RegExReplace(CoordRaw, "(?<=.\d'\d\d)'(?=(N|S|E|W))", "@")
	CoordRaw := RegExReplace(CoordRaw, "(?<=.'\d\d\,\d)'(?=(N|S|E|W))", "@")
	CoordRaw := RegExReplace(CoordRaw, "°(?=\d\d'\s\d\d´·)", " ")
	CoordRaw := RegExReplace(CoordRaw, "(?<=\d\d)'(?=\s\d\d´·)", "°")
	CoordRaw := StrReplace(CoordRaw, "@", """")
}
While Pos := RegExMatch(CoordRaw, "\d+(º|°|-).+?(N|S|E|" Chr(1045) "|W)", m, Pos+StrLen(m))
{
	If InStr(m, °) {
		If A_Index = 1
			Lat := CoordinateConvert(m)
		If A_Index = 2
			Long := CoordinateConvert(m)
	}
}
MsgBox % Lat ", " Long
ExitApp
Return

#Include CoordinateConvert.ahk

and then i would put the tests in their own file:
Tests.ahk

and you would "assert" that the values equal what you expect. You haven't given any input/output examples, so I'm just guessing. Change those assertions for yourself

Code: Select all

#Include Yunit\Yunit.ahk
#Include Yunit\Window.ahk
#Include CoordinateConvert.ahk

Yunit.Use(YunitWindow).Test(ConvertTestSuite)

class ConvertTestSuite
{   
    Test_Basic()
    {
        Yunit.assert( CoordinateConvert("50° 10'") = "50.16667°" )
    }
    
    Test_Apostrophes()
    {
        Yunit.assert( CoordinateConvert("50° 10´") = "50.16667°" )    
        Yunit.assert( CoordinateConvert("50° 10ʼ") = "50.16667°" )
    }
}
then you would keep adding more and more tests (using more descriptive names than just "Test_Basic") and as you keep going you will have a full testing suite for all of the inputs and outputs you expect. Early on setting this work up is a little time consuming, but once you have a large function with lots of edge cases, your testing suite will become extremely useful as you try to fix up your function and add support for new formats. Then you don't have to worry about breaking something when you add something new, because you can just run your tests each time. Like what happens when you want to accept multiple coordinates? You will start coding, but you also want to be sure that you don't break all the conversions for the individual coord formats as well. Your test suite can save you as you work on it.

If you give some more input formats that you accept, then i can show you how to add more tests if you can't figure it out

MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 11:16

So imagine you had a coordinate like 20°50'30"N 60°36'21"W, this would be your standard coordinate pair you would want to convert, but not everyone uses the same format. For example European countries use commas for dots and a middle dot for decimals. Where my function comes in is to fix those coordinates and convert them to Decimal degrees. Also I tried your suggestion and for some reason keep getting Line 58 error. I excluded the regex stuff just for testing (since that transforms spaces to degree minute seconds).
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Code Optimization

24 Jul 2018, 11:33

MannyKSoSo wrote:So imagine you had a coordinate like 20°50'30"N 60°36'21"W, this would be your standard coordinate pair you would want to convert, but not everyone uses the same format. For example European countries use commas for dots and a middle dot for decimals. Where my function comes in is to fix those coordinates and convert them to Decimal degrees. Also I tried your suggestion and for some reason keep getting Line 58 error. I excluded the regex stuff just for testing (since that transforms spaces to degree minute seconds).
Here, download this .zip that is attached, I fixed the tests. See the code and run the Tests.ahk file
Attachments
conversion.zip
(22.24 KiB) Downloaded 15 times

MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 11:41

So lets take my example coordinates 20°50'30"N 60°36'21"W. These coordinates are in degree minutes seconds (a very common format). Then to covert them into a proper format (Decimal Degrees) you do some basic math and your output should be Latitude: 20.841667
Longitude: -60.605833
So the reason why it was giving me line 58 is because it was off by 1 decimal point. Which is good, what I want to see. But I think I get it now, you are testing by Yunit.assert of the conversion to an expected output.
guest3456
Posts: 3463
Joined: 09 Oct 2013, 10:31

Re: Code Optimization

24 Jul 2018, 11:43

Yep exactly. Play around with it. Feed the assertions all the different formats that you accept. And verify that the output is what you want. Then when you add to your function new features, or if you try to shorten the code to make it more clear, you can keep running your tests as you go, to make sure you don't break anything

User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Code Optimization

24 Jul 2018, 13:06

- I would imagine that this function could be made much simpler. And the simpler it is, the easier it is to fix bugs and add functionality in future.
- Could you give some (or a full of set of) input/output examples?
- Also, I would make it so that it would test for various formats, and if no matching format is found, it would raise an error (or perhaps return a blank string). Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 13:27

Here is a list of Common Examples that I have pulled together

Code: Select all

50° 10'N : 50.166667    <---Left is the input text, Right is the output
20°50'30"N : 20.841667
41°39'.724N : 41.662067
35-55-37.3N : 35.927028
60-17,106N : 60.285100
51° 13´·70N : 51.228333
21° 06´·18N : 21.103000
50° 10´ : 50.166667
50° 10ʼ : 50.166667
The last two are to show that sometimes a different character can appear.
So the formula to change Degree Minutes Second, and Degree Minutes is Degree + Minute/60 + Seconds/3600 and if your second is not there then you can ignore the last part or place a zero in there.
Also ideally I would like to have something similar as well

Code: Select all

50 47,41 N : 50.790167
20 44 10 N : 20.736111
So a user can input their own degree minute second or degree minute quickly if needed (and for some people this is standard). The only requirements that should be needed is a cardinal direction otherwise everything will be North East.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Code Optimization

24 Jul 2018, 14:17

- Are the N/S forms the same as the E/W forms?
- Can you have multiple letters? E.g. NE/NW/SE/SW.
- If no letter is present does that imply N/E/NE?
- Also, do any coordinates ever use '-' (hyphen) to mean negative, rather than as a separator?
- If there are any other common forms please mention them. Thanks.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 14:35

The standard format is N/S then E/W. The only difference is that North and East will always be positive while South and West will always be negative. The Cardinal direction should always come at the end of their respective coordinate and will not contain more than 1 letter. If no letter is present one can assume North and East, but this depends on where you are, for example since I live in the US I might assume no cardinal directions mean North and West. As for a negative sign I have not seen anyone use a - to represent South or West. They will simply put the cardinal direction at the end.
As for any more common forms the basics is this
Degree°Minute' Second" Cardinal Direction <------ This is a common standard, and some countries may not use a ° ' or " and instead will use spaces/a divider to represent the coordinate
Degree° Degree Minutes' Cardinal Direction <------- This is also common (just ignoring the seconds), and there are two variations to the Degree Minutes. If its a European country a , will be used instead of a . or even a middle .

The Degree Minute Second and Degree Minutes are the only coordinates you can expect, but the variations in style like 20-50-30.0N vs 20°50'30"N even though these too look very different, they are both still Degree Minutes Seconds and use the same formula to get Decimal Degree.

Decimal Degree's are simply put as 50.00000 Where everything before the decimal is the degree (makes it easy right!) and the Minute and Second are the only things (technically) being converted in the formula.
Also Decimal Degree's do not use Cardinal Directions and instead will use a - to determine N/S/E/W. But since we are transforming to Decimal Degrees we only need to know whether the original coordinate's location was either South or West.
Hopefully that explains everything.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Code Optimization

24 Jul 2018, 15:13

- Many thanks for the additional details.
- This is more or less what I had in mind: replace many of the special characters at the start, but leave commas/dots (decimal points) alone. Do various strict RegExMatch checks, and if one is satisfied, perform the calculations and return the value.
- If you have any queries I'll be glad to have a look, and you're welcome to change the function however you want e.g. to make it more like your earlier function.
- Btw I noticed this in the OP: InStr(m, °), which should be: InStr(m, "°").

Code: Select all

q:: ;convert coordinates
vText := " ;continuation section
(Comments
50° 10'N : 50.166667
20°50'30""N : 20.841667 ;note: " needed escaping
41°39'.724N : 41.662067
35-55-37.3N : 35.927028
60-17,106N : 60.285100
51° 13´·70N : 51.228333
21° 06´·18N : 21.103000
50° 10´ : 50.166667
50° 10ʼ : 50.166667
)"

vOutput := ""
Loop, Parse, vText, `n, `r
{
	oTemp := StrSplit(A_LoopField, " : ")
	vCoord := CoordinateConvert(oTemp.1)
	vCoord1 := Round(oTemp.2, 6) ;note: rounding needed for the comparison lower down
	vCoord2 := Round(vCoord, 6)
	vOutput .= (vCoord1 = vCoord2) "`t" A_LoopField "`t" vCoord "`r`n"
}
Clipboard := vOutput
MsgBox, % vOutput
return

;==================================================

CoordinateConvert(vText)
{
	static oSQ := [180, 700, 1370, 2036, 2037, 8217, 65287]
	static oDQ := [733, 8221, 10078, 12317, 12318, 12319, 65282]

	;replace characters
	vText := StrReplace(vText, Chr(1045), "E")
	vText := StrReplace(vText, Chr(186), "°")
	for _, vValue in oSQ
		vText := StrReplace(vText, Chr(vValue), "'")
	for _, vValue in oDQ
		vText := StrReplace(vText, Chr(vValue), Chr(34))

	vMult := (vText ~= "[SW]$") ? -1 : 1 ;ends with S/W
	if (vText ~= "[NESW]$") ;ends with N/E/S/W
		vText := SubStr(vText, 1, -1)
	if (vText ~= "[A-Za-z]") ;contains letter
		return
		;return "[ERROR]" vText

	if RegExMatch(vText, "O)^(\d+)°\s*(\d+)'$", oMatch) ;e.g. 50° 10'
		vNum := oMatch.1 + oMatch.2/60
	else if RegExMatch(vText, "O)^(\d+)°(\d+)'(\d+)\x22$", oMatch) ;e.g. 20°50'30"
	|| RegExMatch(vText, "O)^(\d+)-(\d+)-([\d\.]+)$", oMatch) ;e.g. 35-55-37.3
		vNum := oMatch.1 + oMatch.2/60 + oMatch.3/3600
	else if RegExMatch(vText, "O)^(\d+)°(\d+)'([\d\.]+)$", oMatch) ;e.g. 41°39'.724
		vNum := oMatch.1 + oMatch.2/60 + oMatch.3/60
	else if RegExMatch(vText, "O)^(\d+)°\s*(\d+)'·(\d+)$", oMatch) ;e.g. 51° 13'·70 ;e.g. 21° 06'·18
		vNum := oMatch.1 + oMatch.2/60 + Format("{}", "0." oMatch.3)/60
	else if RegExMatch(vText, "O)^(\d+)-([\d,]+)$", oMatch) ;e.g. 60-17,106
		vNum := oMatch.1 + Format("{}", StrReplace(oMatch.2, ",", "."))/60
	else
		return
		;return "[ERROR]" vText
	return vNum * vMult
}

;==================================================
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 17:54

One thing I am slightly confused about is this RegEx

Code: Select all

else if RegExMatch(vText, "O)^(\d+)°(\d+)'([\d\.]+)$", oMatch) ;e.g. 41°39'.724
With the example being '.724 how did it end up matching \d\. since the digit came before the period? <---- Shows how much I'm missing out on regex
Also I added another check to the whole thing. Since N and S coordinates cannot exceed 90° and E and W coordinates cannot exceed 180° I added this check

Code: Select all

If !(TestText[1] <= 90) && (vText ~= "[NS]$") || !(TestText[1] <= 180) && (vText ~= "[EW]$")
		return "[ERROR]" vText
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Code Optimization

24 Jul 2018, 18:01

- [\d\.]+ checks for one or more characters that are a digit or a dot. [\d\.] is a character class.
- See Classes of characters:
Regular Expressions (RegEx) - Quick Reference | AutoHotkey
https://autohotkey.com/docs/misc/RegEx-QuickRef.htm
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 19:11

So I have taken the changes into account, and this is what I have adjusted for what you have provided.
The first thing I changed was I commented out a line in the conversion : else if RegExMatch(vText, "O)^(\d+)°(\d+)'([\d\.]+)$", oMatch) ;e.g. 41°39'.724 as the only major difference between the two was the middle dot verses the period (as the ' should always come after the decimal or right before it) and changed the following line to contain the period (which did not affect the results).
On the last else if statement I added a variation for a period in case a period or a dot shows up, it will still convert.
I also added custom error statements that will throw the user an error depending on which part of the function when wrong and where.
The last change was that I added another check in the beginning to make sure the coordinate was less than the max ie N/S cannot be greater than 90 and E/W cannot be greater than 180 since circles :P
If anyone wants to add a change or recommend a different format I am open ears!

Code: Select all

;q:: ;convert coordinates
vText := " ;continuation section
(Comments
50° 10'N : 50.166667
20°50'30""N : 20.841667 ;note: " needed escaping
41°39'.724N : 41.662067
35-55-37.3N : 35.927028
60-17,106N : 60.285100
51° 13´·70N : 51.228333
21° 06´·18N : 21.103000
50° 10´ : 50.166667
50° 10ʼ : 50.166667
)"
vOutput := ""
Loop, Parse, vText, `n, `r
{
	oTemp := StrSplit(A_LoopField, " : ")
	Try  vCoord := CoordinateConvert(oTemp.1)
	Catch e
		MsgBox, 16, Error in Converting, % "Failed at " e.What " on line " e.Line "`r`nWith error message: " e.Message
	vCoord1 := Round(oTemp.2, 6) ;note: rounding needed for the comparison lower down
	vCoord2 := Round(vCoord, 6)
	vOutput .= (vCoord1 = vCoord2) "`t" A_LoopField "`t" vCoord "`r`n"
}
Clipboard := vOutput
MsgBox, % vOutput
return

;==================================================

CoordinateConvert(vText)
{
	static oSQ := [180, 700, 1370, 2036, 2037, 8217, 65287]
	static oDQ := [733, 8221, 10078, 12317, 12318, 12319, 65282]

	;replace characters
	vText := StrReplace(vText, Chr(1045), "E"), vText := StrReplace(vText, Chr(186), "°")
	for _, vValue in oSQ
		vText := StrReplace(vText, Chr(vValue), "'")
	for _, vValue in oDQ
		vText := StrReplace(vText, Chr(vValue), Chr(34))

	vMult := (vText ~= "[SW]$") ? -1 : 1 ;ends with S/W so coordinate is negative
	TestText := StrSplit(vText, ["°", "-"])
	If !(TestText[1] <= 90) && (vText ~= "[NS]$") || !(TestText[1] <= 180) && (vText ~= "[EW]$")	;coordinate cannont exceed 90 on the y-axis and 180 on the x-axis
		Throw Exception(vText " is not between " failed := (vText ~= "[NS]$") ? "0-90°" : "0-180°", -1)
	if (vText ~= "[NESW]$") ;ends with N/E/S/W
		vText := SubStr(vText, 1, -1)
	if (vText ~= "[A-Za-z]") ;contains letter
		Throw Exception(vText "Contains letter(s) other than N/E/S/W", -1)

	if RegExMatch(vText, "O)^(\d+)°\s*(\d+)'$", oMatch) ;e.g. 50° 10'
		vNum := oMatch.1 + oMatch.2/60
	else if RegExMatch(vText, "O)^(\d+)°(\d+)'(\d+)\x22$", oMatch) || RegExMatch(vText, "O)^(\d+)-(\d+)-([\d\.]+)$", oMatch) ;e.g. 35-55-37.3
		vNum := oMatch.1 + oMatch.2/60 + oMatch.3/3600
	;else if RegExMatch(vText, "O)^(\d+)°(\d+)'([\d\.]+)$", oMatch) ;e.g. 41°39'.724   <----Commented out since This and the next one are combined using [·\.]
		;vNum := oMatch.1 + oMatch.2/60 + oMatch.3/60
	else if RegExMatch(vText, "O)^(\d+)°\s*(\d+)'[·\.](\d+)$", oMatch) ;e.g. 51° 13'·70 ;e.g. 21° 06'·18
		vNum := oMatch.1 + oMatch.2/60 + Format("{}", "0." oMatch.3)/60
	else if RegExMatch(vText, "O)^(\d+)-([\d(,|\.)]+)$", oMatch) ;e.g. 60-17,106	<-----Added \. to the list in case a period ever shows up
		vNum := oMatch.1 + Format("{}", StrReplace(oMatch.2, ",", "."))/60
	else
		Throw Exception(vText " Format unknown", -1)
	return vNum * vMult
}

;==================================================
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Code Optimization

24 Jul 2018, 19:20

- That's great. No doubt you'll come up with further improvements in the future.
- In theory, I could imagine even 20 or more RegExMatch checks, I wouldn't mind using more checks for greater safety/clarity.
- One thing I had wondered was whether it was safe to replace · with . at the beginning, or not. But as I mentioned, sometimes it's better to play it safe, and not try to be too clever.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA
MannyKSoSo
Posts: 440
Joined: 28 Apr 2018, 21:59

Re: Code Optimization

24 Jul 2018, 19:27

Yeah, that is why I only commented it out, so in case its needed I can always add it back in later. But it just comes down to a difference in how countries have their numbers. Like in the UK/Europe the decimal is a comma and the US its a period, so its a little annoying when it comes to that, but its manageable as long as you know what they are tying to do.
As for more check I can definitely see more coming up as need (since every country wants to be different), but that is also why I had the except throw "Format Unknown" so if later on I'm using it I know that I need to add more to it.
User avatar
jeeswg
Posts: 6902
Joined: 19 Dec 2016, 01:58
Location: UK

Re: Code Optimization

24 Jul 2018, 19:36

It's interesting because often, I, or other people, will be dealing with one source, where everything will be in the same format. I suppose you're having to deal with multiple different sources for something. Perhaps CSVs/spreadsheets. No ISO standards etc? Cheers.
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: TAC109 and 344 guests