/// <summary> /// Convert angle (in degrees) into a DMS string (using d, ', and "). /// </summary> /// <param name="angle">input angle (degrees)</param> /// <param name="trailing"><see cref="TrailingUnit"/> value indicating the trailing units of the string (this component is given as a decimal number if necessary).</param> /// <param name="prec">the number of digits after the decimal point for the trailing component.</param> /// <param name="ind"><see cref="HemisphereIndicator"/> value indicating additional formatting.</param> /// <param name="dmssep">if not <c>0</c>, use as the <see cref="DMS"/> separator character (instead of d, ', " delimiters).</param> /// <returns>formatted string</returns> /// <remarks> /// The interpretation of <paramref name="ind"/> is as follows: /// <list type="bullet"> /// <item><paramref name="ind"/> == <see cref="HemisphereIndicator.None"/>, /// signed result no leading zeros on degrees except in the units place, e.g., <c>-8d03'</c>.</item> /// <item><paramref name="ind"/> == <see cref="HemisphereIndicator.Latitude"/>, /// trailing N or S hemisphere designator, no sign, pad degrees to 2 digits, e.g., <c>08d03'S</c>. /// </item> /// <item><paramref name="ind"/> == <see cref="HemisphereIndicator.Longitude"/>, /// trailing E or W hemisphere designator, no sign, pad degrees to 3 digits, e.g., <c>008d03'W</c>. /// </item> /// <item><paramref name="ind"/> == <see cref="HemisphereIndicator.Azimuth"/>, /// convert to the range [0, 360°), no sign, pad degrees to 3 digits, e.g., <c>351d57'</c>. /// </item> /// </list> /// The integer parts of the minutes and seconds components are always given with 2 digits. /// </remarks> public static string Encode(double angle, TrailingUnit trailing, int prec, HemisphereIndicator ind = HemisphereIndicator.None, char dmssep = '\0') { // Assume check on range of input angle has been made by calling // routine (which might be able to offer a better diagnostic). if (!IsFinite(angle)) { return(angle < 0 ? "-inf" : (angle > 0 ? "inf" : "nan")); } // 15 - 2 * trailing = ceiling(log10(2^53/90/60^trailing)). // This suffices to give full real precision for numbers in [-90,90] prec = Min(15 + 0 - 2 * (int)trailing, prec); var scale = 1d; for (var i = 0; i < (int)trailing; ++i) { scale *= 60; } for (var i = 0; i < prec; ++i) { scale *= 10; } if (ind == HemisphereIndicator.Azimuth) { angle -= Floor(angle / 360) * 360; } int sign = angle < 0 ? -1 : 1; angle *= sign; // Break off integer part to preserve precision in manipulation of // fractional part. double idegree = Floor(angle), fdegree = (angle - idegree) * scale + 0.5; { // Implement the "round ties to even" rule var f = Floor(fdegree); fdegree = (f == fdegree && (f % 2) == 1) ? f - 1 : f; } fdegree /= scale; if (fdegree >= 1) { idegree += 1; fdegree -= 1; } Span <double> pieces = stackalloc[] { fdegree, 0, 0 }; for (var i = 1; i <= (int)trailing; ++i) { double ip = Floor(pieces[i - 1]), fp = pieces[i - 1] - ip; pieces[i] = fp * 60; pieces[i - 1] = ip; } pieces[0] += idegree; var s = new StringBuilder(); if (ind == HemisphereIndicator.None && sign < 0) { s.Append('-'); } var w = 0; switch (trailing) { case TrailingUnit.Degree: if (ind != HemisphereIndicator.None) { w = 1 + Min((int)ind, 2) + prec + (prec != 0 ? 1 : 0); } s.Append(pieces[0].ToFixedString(prec).PadLeft(w, '0')); // Don't include degree designator (d) if it is the trailing component. break; default: if (ind != HemisphereIndicator.None) { w = 1 + Min((int)ind, 2); } s.Append(pieces[0].ToString().PadLeft(w, '0')) .Append(dmssep != 0 ? dmssep : char.ToLower(dmsindicators_[0])); switch (trailing) { case TrailingUnit.Minute: w = 2 + prec + (prec != 0 ? 1 : 0); s.Append(pieces[1].ToFixedString(prec).PadLeft(w, '0')); if (dmssep != 0) { s.Append(char.ToLower(dmsindicators_[1])); } break; case TrailingUnit.Second: s.AppendFormat("{0:D2}", (int)pieces[1]) .Append(dmssep != 0 ? dmssep : char.ToLower(dmsindicators_[1])) .Append(pieces[2].ToFixedString(prec).PadLeft(2 + prec + (prec != 0 ? 1 : 0), '0')); if (dmssep == 0) { s.Append(char.ToLower(dmsindicators_[2])); } break; default: break; } break; } if (ind != HemisphereIndicator.None && ind != HemisphereIndicator.Azimuth) { s.Append(hemispheres_[(ind == HemisphereIndicator.Latitude ? 0 : 2) + (sign < 0 ? 0 : 1)]); } return(s.ToString()); }
/// <summary> /// Convert angle into a <see cref="DMS"/> string (using d, ', and ") selecting the trailing component based on the precision. /// </summary> /// <param name="angle">input angle (degrees)</param> /// <param name="prec">the precision relative to 1 degree.</param> /// <param name="ind"><see cref="HemisphereIndicator"/> value indicating additional formatting.</param> /// <param name="dmssep">if not <c>0</c>, use as the <see cref="DMS"/> separator character (instead of d, ', " delimiters).</param> /// <returns>formatted string</returns> /// <remarks> /// <paramref name="prec"/> indicates the precision relative to 1 degree, e.g., <paramref name="prec"/> = 3 gives a result accurate to 0.1' and /// <paramref name="prec"/> = 4 gives a result accurate to 1". <paramref name="ind"/> is interpreted as in /// <see cref="Encode(double, TrailingUnit, int, HemisphereIndicator, char)"/> with the additional facility that <see cref="HemisphereIndicator.Number"/> /// represents angle as a number in fixed format with precision <paramref name="prec"/>. /// </remarks> public static string Encode(double angle, int prec, HemisphereIndicator ind = HemisphereIndicator.None, char dmssep = '\0') => ind == HemisphereIndicator.Number ? angle.ToFixedString(prec) : Encode(angle, prec < 2 ? TrailingUnit.Degree : (prec < 4 ? TrailingUnit.Minute : TrailingUnit.Second), prec < 2 ? prec : (prec < 4 ? prec - 2 : prec - 4), ind, dmssep);