Converting Google Earth Formatted Longitude/Latitude points to decimal degrees

I had cause, today, to mark the location of something in Google Earth and then provide the longitude/latitude values to a website that wanted them in decimal degrees.

Here is my location as a Google Placemark Location.

Google Placemark Properties Dialog

Note that the longitude/latitude values are coded as formatted degrees, minutes and seconds as follows:

Latitude Longitude
43� 0’50.60“S 147�12’18.20“E

I wrote a DMS2DD function in my PL/SQL COGO package many years ago. I decided to use it to do the conversion.

 SELECT round(cogo.dms2dd(' 43� 0''50.60"S'),9) AS latitude,
         round(cogo.dms2dd('147�12''18.20"E'),9) AS longitude
   FROM dual;
 --
 LATITUDE               LONGITUDE
 ---------------------- ----------------------
 -43.014055556          147.205055556
  
 1 ROWS selected
  

Here is the source code for the function.

     --
     -- Support function STRTOK
     -- (Added in response to Fabio Caroletta)
     --
     -- ----------------------------------------------------------------------------------------  
     -- @function   : StrTok
     -- @precis     : Simple string tokeniser.
     -- @version    : 1.0
     -- @usage      : function StrTok(
     --                                p_str in varchar2,
     --                                p_delim in varchar2
     --                              )
     --                        return stringarray deterministic;
     --               eg tokens := StrTok('299900, 5200000, 300000, 5200100',' ,'     -- @param      : p_Str   : String containing tokens for extraction.
     -- @paramtype  : p_Str   : string.
     -- @param      : p_delim : String containg one or more token separators.
     -- @paramtype  : p_Delim : String
     -- @return     : Tokens  : A list of tokens (string).
     -- @rtnType    : Tokens  : Table of tokens (like a varray).
     -- @note       : Does not throw exceptions
     -- @history    : Simon Greener, Feb 2005 - Original coding.
     --
     FUNCTION strtok(p_str IN varchar2, p_delim IN varchar2)
        RETURN STRINGARRAY
        DETERMINISTIC
       IS
         v_numtok  NUMBER;
         v_length  NUMBER;
         v_outside NUMBER;
         v_char    CHAR(1);
         v_strtok_vals STRINGARRAY;
       BEGIN
         -- initialise strtok_vals array
         v_strtok_vals := STRINGARRAY('');
         v_numtok := 0;
         v_length := LENGTH(p_str);
         v_outside := 1;
         FOR i IN 1..v_length loop
          v_char := SUBSTR(p_str,i,1);
          IF instr(p_delim, v_char) <> 0 THEN
            -- outside
            v_outside := 1;
            v_strtok_vals.EXTEND;
            v_strtok_vals(v_strtok_vals.LAST) := '';
          ELSE
            -- inside
            IF (v_outside = 1) THEN
              v_numtok := v_numtok + 1;
            END IF;
            v_strtok_vals(v_strtok_vals.LAST) := v_strtok_vals(v_strtok_vals.LAST) || v_char ;            v_outside := 0;
          END IF;
        END LOOP;
        -- Return value
        RETURN v_strtok_vals;
     END strtok;
     --
     -- Main function
     --
     FUNCTION DMS2DD(strDegMinSec IN varchar2)
     RETURN NUMBER
     IS
        i               NUMBER;
        intDmsLen       NUMBER; --Length of original string
        strCompassPoint CHAR(1);
        strNorm         varchar2(16); --Will contain normalized string
        strDegMinSecB   varchar2(100);
        blnGotSeparator INTEGER;      -- Keeps track of separator sequences
        arrDegMinSec    stringarray;  --  TYPE stringarray is table of varchar2(2048) ;        dDeg            NUMBER := 0;
        dMin            NUMBER := 0;
        dSec            NUMBER := 0;
        strChr          CHAR(1);
     BEGIN
        -- Remove leading and trailing spaces
        strDegMinSecB := REPLACE(strDegMinSec,' ',NULL);
        -- assume no leading and trailing spaces?
        intDmsLen := LENGTH(strDegMinSecB);
        blnGotSeparator := 0; -- Not in separator sequence right now
        -- Loop over string, replacing anything that is not a digit or a
        -- decimal separator with
        -- a single blank
        FOR i IN 1..intDmsLen LOOP
           -- Get current character
           strChr := SubStr(strDegMinSecB, i, 1);
           -- either add character to normalized string or replace
           -- separator sequence with single blank
           IF InStr('0123456789,.', strChr) > 0 THEN
              -- add character but replace comma with point
              IF (strChr <> ',') THEN
                 strNorm := strNorm || strChr;
              ELSE
                 strNorm := strNorm || '.';
              END IF;
              blnGotSeparator := 0;
           ElsIf InStr('neswNESW',strChr) > 0 THEN -- Extract Compass Point if present             strCompassPoint := strChr;
           ELSE
              -- ensure only one separator is replaced with a blank -
              -- suppress the rest
              IF blnGotSeparator = 0 THEN
                 strNorm := strNorm || ' ';
                 blnGotSeparator := 0;
              END IF;
           END IF;
        END Loop;
        -- Split normalized string into array of max 3 components
        arrDegMinSec := strtok(strNorm, ' ');
        --convert specified components to double
        i := arrDegMinSec.COUNT;
        IF i >= 1 THEN
           dDeg := TO_NUMBER(arrDegMinSec(1));
        END IF;
        IF i >= 2 THEN
           dMin := TO_NUMBER(arrDegMinSec(2));
        END IF;
        IF i >= 3 THEN
           dSec := TO_NUMBER(arrDegMinSec(3));
        END IF;
        -- convert components to value
        RETURN (CASE WHEN UPPER(strCompassPoint) IN ('S','W')
                     THEN -1
                     ELSE 1
                 END
                *
                (dDeg + dMin / 60 + dSec / 3600));
     END DMS2DD;

I hope this function is useful to someone.