Re: Delphi angles, degrees, minutes, seconds

[ Related Articles and Replies ] [ DelphiLand FAQ ] [ Delphi Tutorials ]

Posted by webmaster Guido on December 23, 2002 at 18:11:52:

In Reply to: Re: Delphi Books & Math Unit posted by Lionel on December 16, 2002 at 04:07:49:

: Thanks,
: I have used this format and it works fine but I need a better way to enter an angle with out having so many edit boxes. For example I would like to enter degrees,minutes,seconds in one edit box and use the following format:
: Enter DD.MMSS in one edit box and then convert it to decimal degrees and then convert to radians. I saw this code someplace but can't find it now. As I remender, the code showed this done using the Trunc and Frac function but I have not been able to get my code to work. Any ideas?
: X:=DMS;(Entered as DD.MMSS)
: D:=Trunc(X);pull degrees out
: M:=Frac(X)*100;remainder
: M:=Trunc(M);pull out minutes
: S:=Frac(X)*100;
: S:=Frac(S)*100; pull out ss
: //-------------------------------
: FloatToStr(D) Display Degrees
: FloatToStr(M) Display Minutes
: FloatToStr(S) Display Seconds
: //------------------------------
: DecDeg:=D+(M/60)+(S/3600); Compute DecDeg
: Radians:=DecDeg*pi/180; Compute Radians
: ************************************************

Let's try the conversion part of your formulas:

procedure TForm1.btnCalculateClick(Sender: TObject);
  Degrees, Minutes, Error: integer;
  DD_MMSS, DecFraction1, MM_SS, DecFraction2, Seconds, DecimalDegrees: real;
  Val(Edit1.Text, DD_MMSS, Error); // try to convert text to real number

  if Error <> 0 then // error during conversion?
    ShowMessage('Invalid format, please enter the angle as DD.MMSS')
  else begin
    Degrees := Trunc(DD_MMSS); // part before decimal separator
    DecFraction1 := Frac(DD_MMSS); // remove part before dec. separator
    MM_SS := DecFraction1 * 100;   // "shift left" 2 decimal places
    Minutes := Trunc(MM_SS);       // part before dec. separator
    DecFraction2 := Frac(MM_SS); // remove part before dec. separator
    Seconds := DecFraction2 * 100;  // "shift left" 2 decimal places
    DecimalDegrees := Degrees + Minutes / 60 + Seconds / 3600;
    lblDeg.Caption := IntToStr(Degrees);
    lblMin.Caption := IntToStr(Minutes);
    lblSec.Caption := FloatToStr(Seconds);
    lblDecDeg.Caption := FloatToStr(DecimalDegrees);

This is only correct for some input values, but it's wrong for most inputs. For example, if you input 2.0100:
  - the program says: 2 degrees 0 minutes 100 seconds (2.02777... decimal degrees)
  - it should be: 2 degrees 1 minute 0 seconds (2.01666... decimal degrees)

That's because for most inputs, the result of the Frac function is either just a little bit too big or too small (because internally the program works with binary numbers, and most *decimal* fractions numbers can not be converted exactly to *binary* fractions).
Now, if the Frac result is too big, that's no problem, because this error disappears after multiplying with 100 and "cutting off" the fraction. But if the fraction is returned for example as 0.0099999... instead of 0.01 this becomes 0.999999... after multiplying with 100. And next, the Trunc gives you 0 instead of 1, so there go the minutes...

But if we loose a minute in this way, we get it back in the seconds part, because of these two lines:
  DecFraction2 := Frac(MM_SS); // gives 0.99999... instead of 0
  Seconds := DecFraction2 * 100; // gives 99.99999... instead of 0

If only it would give 60 seconds (or 59.9999...) then at least the final "decimal degrees" value would be correct. But we can't solve this by simply multiplying the seconds with 0.6 because that would also affect the "correct" conversions. It would work if we only multiply the faulty results with 0.6 ;-) but that's running in circles ;-)

So, back to the drawing table. You could adapt the current code by rounding the numbers to a certain number of decimal places, at some critical points in the code. But that would require quite some more analysis, and finally it would defeat the original purpose, a short and simple conversion method.

The most reliable way to work with this "DD.MMSS" format, is to extract the *strings* for the degrees, minutes and seconds, next convert these 3 strings to 3 numbers. That's more code, because it would also require some more error checking, and you would have to set some rules as to what would happen if you enter less than 4 digits in the decimal part: give an error message, or accept this? For example, "10.2" would mean 20 minutes (not 2), and "10.203" would mean 30 seconds (not 3).

I've sent you a personal email, with an example project that demonstrates the initial code plus a better method.


Note for interested readers:

In the traditional notation, fractions of degrees are broken down into minutes and seconds. Each minute represents 1/60th of a degree and each second represents 1/60th of a minute (or 1/3600th of a degree). This is called the "DMS" notation (degrees, minutes, seconds).

In the little program above, a special "custom" shorthand notation DD.MMSS is used. Attention, this is not an official notation! It's used only as a method for quickly entering angles with a minimal amount of keystrokes!

So, remember that entering 10.4518 in the edit-box does NOT mean:
  10 + 4/10 + 5/100 + 1/1000 + 8/10000 degrees,
  10 degrees + 45 minutes + 18 seconds (which equals 10.755 decimal degrees).

Related Articles and Replies:

[ Related Articles and Replies ] [ DelphiLand FAQ ] [ Delphi Tutorials ]