--------------------------------------------------------------------------------
-- UNIT_NAME: Maps
--
-- DESIGNER          : Guillermo HAZEBROUCK
--
-- CREATION_DATE     : 19 Oct 2020
--------------------------------------------------------------------------------
-- External
with Ada.Text_IO;
with Ada.Numerics;
with Ada.Numerics.Elementary_Functions;
with Ada.Numerics.Long_Elementary_Functions;
-- Local
with Utility.Strings;




--******************************************************************************
--
--******************************************************************************
package body Maps is

    Deg_Chr : constant Character := '*';
    Min_Chr : constant Character := ''';
    Seg_Chr : constant Character := '"';
    Dec_Chr : constant Character := '.';

    Deg_Str : constant String := (1 => Deg_Chr);
    Min_Str : constant String := (1 => Min_Chr);
    Seg_Str : constant String := (1 => Seg_Chr);
    Dec_Str : constant String := (1 => Dec_Chr);

   --===========================================================================
   --
   --===========================================================================
   function Sexagecimal_Image (Value : Long_Float; Format : Position_Formats; Latitude : Boolean) return String is

      Degrees : Long_Float := Long_Float'Floor (abs Value);
      Minutes : Long_Float := 60.0 * (abs Value - Degrees);
      Seconds : Long_Float := 60.0 * (Minutes - Long_Float'Floor (Minutes));
      Decimal : Long_Float := 10.0 * (Seconds - Long_Float'Floor (Seconds));

      function Complete_000 (Value : Long_Float) return String is
      begin

         if Value < 10.0 then

            return "00" & Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Value))));

         elsif Value < 100.0 then

            return "0" & Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Value))));

         else

            return Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Value))));

         end if;

      end Complete_000;

      function Complete_00 (Value : Long_Float) return String is
      begin

         if Value < 10.0 then

            return "0" & Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Value))));

         else

            return Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Value))));

         end if;

      end Complete_00;

   begin

      if Degrees > 180.0 then

         case Format is

            when Format_1 =>

               return "D*MM'SS.DD";

            when Format_2 =>

               if Latitude then

                  return "DDMMSS";

               else

                  return "DDDMMSS";

               end if;

         end case;

      end if;

      Minutes := Long_Float'Truncation (Minutes);

      case Format is

         when Format_1 =>

            return Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Degrees)))) & Deg_Str &
                   Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Minutes)))) & Min_Str &
                   Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Seconds)))) & Dec_Str &
                   Utility.Strings.Trim (Integer'Image (Integer (Long_Float'Floor (Decimal)))) & Seg_Str;

         when Format_2 =>

            if Latitude then

               return Complete_00 (Degrees) & Complete_00 (Minutes) & Complete_00 (Seconds);

            else

               return Complete_000 (Degrees) & Complete_00 (Minutes) & Complete_00 (Seconds);

            end if;

      end case;

   end Sexagecimal_Image;
   --------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function Sexagecimal_Value (Image : String; Format : Position_Formats) return Long_Float is

      J      : Natural    := 0;
      Sign   : Long_Float := 0.0;
      Result : Long_Float := 0.0;
      F      : constant Natural := Image'First - 1;

   begin

      case Format is

         when Format_1 =>

            for I in Image'First..Image'Last loop

               if Image (I) = 'N' or Image (I) = 'E' then

                  Sign :=  1.0;

                  J := I + 1;

               elsif Image (I) = 'W' or Image (I) = 'S' then

                  Sign := -1.0;

                  J := I + 1;

               elsif Image (I) = Deg_Chr and I > J then

                  Result := Long_Float'Value (Image (J..I - 1));

                  J := I + 1;

               elsif Image (I) = Min_Chr and I > J then

                  Result := Result + Long_Float'Value (Image (J..I - 1)) / 60.0;

                  J := I + 1;

               elsif Image (I) = Seg_Chr and I > J then

                  Result := Result + Long_Float'Value (Image (J..I - 1)) / 3600.0;

                  J := I + 1;

               end if;

            end loop;

         when Format_2 =>

            case Image (Image'Last) is

               when 'N' | 'S' =>

                  Result :=
                    Long_Float'Value (Image (F + 1..F + 2)) +
                    Long_Float'Value (Image (F + 3..F + 4)) / 60.0 +
                    Long_Float'Value (Image (F + 5..F + 6)) / 3600.0;

                  if Image (Image'Last) = 'S' then
                     Sign := -1.0;
                  end if;

               when 'E' | 'O' =>

                  Result :=
                    Long_Float'Value (Image (F + 1..F + 3)) +
                    Long_Float'Value (Image (F + 4..F + 5)) / 60.0 +
                    Long_Float'Value (Image (F + 6..F + 7)) / 3600.0;

                  if Image (Image'Last) = 'O' then
                     Sign := -1.0;
                  end if;

               when others =>

                  null;

            end case;

      end case;

      return Sign * Result;

   end Sexagecimal_Value;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function Lat_String (Value : Long_Float) return String is
   begin
      if Value < 0.0 then
         return "S";
      else
         return "N";
      end if;
   end Lat_String;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function Lon_String (Value : Long_Float) return String is
   begin
      if Value < 0.0 then
         return "W";
      else
         return "E";
      end if;
   end Lon_String;
   -----------------------------------------------------------------------------


   --===========================================================================
   --
   --===========================================================================
   function Lat_Image (Value : Position_Record; Format : Position_Formats := Format_1) return String is
   begin

      case Format is

         when Format_1 =>

            return Lat_String (Value.Lat) & Sexagecimal_Image (Value.Lat, Format_1, True);

         when Format_2 =>

            return Sexagecimal_Image (Value.Lat, Format_2, True) & Lat_String (Value.Lat);

      end case;

   end Lat_Image;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function Lon_Image (Value : Position_Record; Format : Position_Formats := Format_1) return String is
   begin

      case Format is

         when Format_1 =>

            return Lon_String (Value.Lon) & Sexagecimal_Image (Value.Lon ,Format_1, False);

         when Format_2 =>

            return Sexagecimal_Image (Value.Lon, Format_2, False) & Lon_String (Value.Lon);

      end case;

   end Lon_Image;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function Image (Value : Position_Record; Format : Position_Formats := Format_1) return String is
   begin

      return Lat_Image (Value, Format) & " " & Lon_Image (Value, Format);

   end Image;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function Value (Image : String; Format : Position_Formats := Format_1) return Position_Record is

      Result : Position_Record := (0.0, 0.0);
      J      : Positive := Image'First;

   begin

      for I in Image'First..Image'Last loop

         if Image (I) = ' ' then

            J := I;

            exit;

         end if;

      end loop;

      if J > Image'First and J < Image'Last then

         Result.Lat := Sexagecimal_Value (Image (Image'First..J-1), Format);

         Result.Lon := Sexagecimal_Value (Image (J+1..Image'Last),  Format);

      end if;

      return Result;

   end Value;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function To_Screen_Coordinates (This : Map_View_Record; Position : Position_Record) return Point_Record is

      Point : Point_Record;

   begin

      Point.X := Float (Position.Lon - This.Center.Lon) / (This.Zoom * This.Width)  + 0.5;

      Point.Y := Float (Position.Lat - This.Center.Lat) / (This.Zoom * This.Height) + 0.5;

      return Point;

   end To_Screen_Coordinates;
   -----------------------------------------------------------------------------




   --===========================================================================
   --
   --===========================================================================
   function To_Map_Coordinates (This : Map_View_Record; Point : Point_Record) return Position_Record is

      Position : Position_Record;

   begin

      Position.Lon := Long_Float ((Point.X - 0.5) * This.Zoom * This.Width)  + This.Center.Lon;

      Position.Lat := Long_Float ((Point.Y - 0.5) * This.Zoom * This.Height) + This.Center.Lat;

      return Position;

   end To_Map_Coordinates;
   -----------------------------------------------------------------------------




   --===========================================================================
   -- (See specification file)
   --===========================================================================
   function Distance (Position_A, Position_B : Position_Record) return Float is

      use Ada.Numerics;
      use Ada.Numerics.Elementary_Functions;
      use Ada.Numerics.Long_Elementary_Functions;

      Delta_Lat : Long_Float := Position_B.Lat - Position_A.Lat;
      Delta_Lon : Long_Float := Position_B.Lon - Position_A.Lon;
      Midle_Lat : Long_Float := (Position_B.Lat + Position_A.Lat) * Pi / 360.0;

      Delta_X, Delta_Y : Float;

   begin

      Delta_Y := Float (110.5800 * Delta_Lat);

      Delta_X := Float (111.3195 * Cos (Midle_Lat) * Delta_Lon);

      return Sqrt (Delta_X * Delta_X + Delta_Y * Delta_Y);

   end Distance;
   -----------------------------------------------------------------------------




   --===========================================================================
   -- (See specification file)
   --===========================================================================
   procedure Vector (Position_A,
                     Position_B  : Position_Record;
                     Distance    : out Float;
                     Bearing     : out Float) is

      use Ada.Numerics;
      use Ada.Numerics.Elementary_Functions;
      use Ada.Numerics.Long_Elementary_Functions;

      Delta_Lat : Long_Float := Position_B.Lat - Position_A.Lat;
      Delta_Lon : Long_Float := Position_B.Lon - Position_A.Lon;
      Midle_Lat : Long_Float := (Position_B.Lat + Position_A.Lat) * Pi / 360.0;

      Delta_X, Delta_Y : Float;

   begin

      Delta_Y := Float (110.5800 * Delta_Lat);

      Delta_X := Float (111.3195 * Cos (Midle_Lat) * Delta_Lon);

      Distance := Sqrt (Delta_X * Delta_X + Delta_Y * Delta_Y);

      Bearing  := 90.0 - Arctan (Delta_Y, Delta_X) * 180.0 / Pi;

      if Bearing < 0.0 then

         Bearing := Bearing + 360.0;

      end if;

   end Vector;
   -----------------------------------------------------------------------------

end Maps;
--------------------------------------------------------------------------------
